Contents
非同期と同期処理の技術的基礎
アプリケーションのパフォーマンスを左右する「async/await」と「def」の定義違いやイベントループの働きを理解することは、適切な選択の第一歩です。
async/awaitとdefの定義違い
同期処理(sync)はdefで関数を定義し、順次実行されます。一方で、非同期処理(async)はasync defで定義され、待機中にも他のタスクを処理可能にします。
def:コルーチンではないため、I/O待ち中はプロセスがブロックされるasync def:イベントループによって非同期操作を制御し、並行性を実現
重要なポイント:
awaitは非同期処理内で使用され、I/O操作(例: DBアクセス)中に他のタスクを実行可能にします。ただし、CPU集約型の処理には意味がありません(FastAPI公式)。
イベントループの動作原理
非同期処理では「イベントループ」という仕組みにより、タスクを切り替えて実行します。この設計により、I/O待ち中に他の処理を進めることが可能になります。
| 項目 | 説明 |
|---|---|
| 同期処理 | プロセスが一連の操作に完全に集中し、待機中は何もできない |
| 非同期処理 | イベントループがタスクを管理し、待機中のリソースを次のタスクに割り当て |
実際の挙動例:同期処理では「注文待ち→調理開始」と一方向に進むのに対し、非同期では「注文待ち中も他の客の注文を受ける」ことが可能(FastAPI公式)。
リソース消費比較(I/O待ち時)
I/O操作が多いアプリケーションでは、非同期処理のリソース効率が顕著になります。
同期処理時のプロセス待機状態
同期処理はI/O待ち中も1つのリクエストを完全にブロックします。例えば、30個のファイル読み込みリクエストを受けた場合、各リクエストが終了するまで次の処理が実行されません。
- リソース消費:高(待機中のCPUは無駄に使用される)
- 応答速度:遅延しやすい
非同期処理による並行実行
非同期では、I/O待ち中に他のリクエストを同時に処理可能です。これにより、リソースの有効活用が可能です。
- リソース消費:低(待機中のリクエストは次のタスクに移譲される)
- 応答速度:複数リクエスト同時処理で改善
比較表:
| 項目 | 同期処理 | 非同期処理 |
|---|---|---|
| I/O待ち時のリソース使用 | 1つのプロセスに集中 | 複数のタスクを並行実行可能 |
| リクエスト応答時間 | タスク数増加で著しく遅延 | 網羅的な処理が可能 |
パフォーマンス視点での実装判断基準
リクエストの種類や量に応じて、async/syncを選択する必要があります。
並行処理(Concurrency)との関係性
非同期処理は「並行性」を実現しますが、「並列性」とは異なります。同一スレッド内でタスクを切り替えるため、CPUコア数に応じた性能向上は見込めません。
- 並行処理(Concurrency)の特徴:
- 非同期処理により1つのスレッドで複数タスクを管理
- I/O待ち時のリソース効率が高められる
- 並列処理(Parallelism)の特徴:
- マルチスレッド/マルチプロセスでの同時実行
- CPU集約型タスクに有効
ベンチマーク例:100リクエストを同期処理と非同期処理で比較すると、非同期処理の方が応答時間が30%改善するケースがあります(FastAPI公式)。
実装例による性能差検証
以下に、FastAPIで非同期処理を活用した実装例とそのメリットを示します。
非同期処理の良い例
|
1 2 3 4 5 6 7 8 9 10 11 |
from fastapi import FastAPI import httpx app = FastAPI() @app.get("/async") async def async_api(): async with httpx.AsyncClient() as client: response = await client.get("https://example.com/data") return {"data": response.text} |
同期処理の例
|
1 2 3 4 5 6 |
@app.get("/sync") def sync_api(): import requests response = requests.get("https://example.com/data") return {"data": response.text} |
性能比較:非同期処理は、複数リクエストが同時に処理されるため、I/O待ち時間が短縮されます。ただし、CPU集約型の計算には適しません(FastAPI公式)。
DBアクセスにおける最適な選択肢
データベース操作では、同期処理と非同期処理の使い分けが重要です。
同期処理が有効なケース
- トランザクション処理:複数の操作を一括で実行する必要がある場合
- データ整合性が重視される場面(例: 予約処理)
理由:非同期では、DBのコミットやロールバックがタイミングによって破綻する可能性があります。
非同期処理が有効なケース
- レポート生成やバッチ処理(結果は即時性が不要)
- 外部APIとの通信(例: タイムスタンプを取得する非同期クエリ)
実装例:
asyncpgやmotorなどの非同期DBライブラリを使用し、I/O待ち中も他のリクエストを処理可能にします。
FastAPIにおける実装ガイドライン
アプリケーションの特性に応じて、async/awaitとdefの使い分けを柔軟に行いましょう。
async/awaitの適切な適用範囲
- I/O待ちが多いリクエスト:非同期処理が効果的
- 外部API呼び出し(例:
httpx.AsyncClient) - ファイル読み込み・書き込み
- CPU集約型の処理:同期処理を推奨
- 数値計算や画像処理など
混合構成時の注意点
FastAPIでは、非同期と同期の関数が同時に存在する「混合構成」も可能です。ただし、以下に注意が必要です。
- 非同期関数内で同期関数を呼び出す場合は、ブロッキング操作によるパフォーマンス低下が発生
- 逆の場合(同期関数で非同期処理)は、
asyncio.to_thread()などを使って適切に扱う
まとめ
本記事で述べた通り、FastAPI async と sync の違い 比較の要点を整理すると以下になります。
- I/O待ちが多いリクエストには非同期処理が有効
- 同期処理は、トランザクション処理などデータ整合性が必要な場面で活用する
- 非同期処理ではイベントループの仕組みを理解し、リソースの無駄使いを防ぐ
パフォーマンス向上を目指す際は、「実装する処理がI/O待ちなのかCPU集約型か」に焦点を当てた選択を心がけましょう。