Python の async は『書ける』と『使える』が違う
Python の asyncio は手軽に書ける一方、ブロッキングAPIや GIL の理解不足で性能が出ない事例も多い領域です。本記事では編集部の視点で、本番運用に耐える書き方を公開情報をもとに整理します。Rust 非同期実践 もご参考に。
asyncio の基本概念
(1) event loop:単一スレッドで多重化。(2) coroutine:async def で定義。(3) await:他コルーチンに譲る点。(4) Task:asyncio.create_task() で並行実行。(5) asyncio.gather():複数 Task を待つ。シングルスレッドでの並行性で、CPU バウンドではない用途に最適です。
ブロッキングAPI の罠
(1) requests / time.sleep / open() は危険:イベントループ停止。(2) 非同期版を選ぶ:httpx / aiohttp / aiofiles。(3) asyncio.to_thread:既存同期コードを別スレッドへ。(4) ProcessPoolExecutor:CPU 重い処理用。(5) クライアントライブラリの互換性を必ず確認。
FastAPI の活用
(1) async def エンドポイント:自動で非同期実行。(2) 同期エンドポイントは別スレッド:FastAPI が自動処理。(3) Pydantic v2:高速バリデーション。(4) 依存性注入:Depends() で再利用。(5) OpenAPI 自動生成:型から仕様生成。REST API設計 もご参考に。
本番運用の構成
(1) uvicorn + gunicorn:ASGI サーバ。(2) worker 数 = CPU * 2 + 1(目安)。(3) graceful shutdown:SIGTERM 対応。(4) ヘルスチェック:/health エンドポイント。(5) 監視:Prometheus / OpenTelemetry 連携。Observability 実践 も合わせて。
パフォーマンス最適化
(1) DB クライアント:asyncpg / SQLAlchemy 2.0 async。(2) コネクションプール:必須。(3) orjson:標準 JSON より高速。(4) Pydantic v2 への移行:v1 より大幅高速。(5) uvicorn の --loop uvloop:イベントループ高速化。
失敗しがちなパターン
(1) 同期と非同期コードの混在:イベントループ詰まり。(2) 巨大データの JSON.dumps:GIL がブロック。(3) asyncio.wait_for の使い忘れ:タイムアウトせず無限待ち。(4) try/except の網羅不足:未捕捉例外でTaskが消える。(5) テストが書きにくい:pytest-asyncio で対応。対策は、(1)非同期ライブラリ統一、(2)stream/orjson 活用、(3)タイムアウト必須、(4)gather(return_exceptions=True)、(5)pytest-asyncio 導入、です。