Upgrade to Pro — share decks privately, control downloads, hide ads and more …

FastAPI の並行処理モデルを完全に理解する

FastAPI の並行処理モデルを完全に理解する

2026.08.21 @ PyCon JP 2026

Avatar for Yuki Ishikawa

Yuki Ishikawa

August 21, 2026

More Decks by Yuki Ishikawa

Other Decks in Programming

Transcript

  1. FastAPI はわかりやすい (と思う) FastAPI アプリケーションの実装は「関数」がベースとなっているのでシンプルで説明しやすい 型ヒントを定義すればそれが API 仕様になる 素朴な関数にデコレータをつけるだけで API

    エンドポイント (※) が定義できる ややこしい依存は Depends で切り出せる ※ 「API エンドポイント」という呼称は正確ではなく、 少なくとも FastAPI 公式ドキュメントでは「パスオペレーション」と呼ばれているが、 このトーク内では「API エンドポイント」と呼ばせていただく (呼び慣れないので…)
  2. def と async def の使い分けはややこしいと思う FastAPI のエンドポイント (パスオペレーション関数) は「def」「async def」どちらでも定義できるが、

    「何が違うのか?」「なぜ2種類の方法があるのか?」「どう使い分けるのか?」を説明するのはややこしい このテーマについて 「FastAPI はどのように たくさんの HTTP リクエストを 捌いているのか?」 という観点から考えていく
  3. 素朴な HTTP サーバを考えてみる (1/2) Python の http モジュールを使ってめちゃくちゃ素朴な HTTP サーバを作ってみる

    これを server.py って名前で保存して python server.py したら動く 簡単ですごい
  4. 素朴な HTTP サーバを考えてみる (2/2) この素朴な HTTP サーバは同時に1リクエストしか捌けない ”I/O 待ち” で

    CPU が暇している間も他のリクエストを捌いたりしない ある HTTP リクエスト HTTP サーバがリクエストを受信してからレスポンスを返すまで リクエストを パース Redis に接続して セッション情報を取得 何らかの 処理 RDB に接続して ユーザ情報を取得 何らかの 処理 レスポンスを 組み立て 外部リソースと通信している間、HTTP サーバは特に何も処理をしていない (I/O 待ち) 別の HTTP リクエストは待たせたままだけど CPU は暇している 別の HTTP リクエスト 終了 ひとつ終わったので、 待たせていた次のリクエストに 着手する 次のリクエストの処理 他の HTTP リクエストを処理している間、ずっと待たされている 処理が開始すらされない
  5. 複数のタスクを同時に処理する方法 複数のタスク (今回の場合は HTTP リクエスト処理) を同時に処理する方法はいくつかあるが、 代表的なものとして以下の3種類を紹介する マルチプロセス プロセス プロセス

    マルチスレッド プロセス プロセス スレッド タスク タスク タスク リソース リソース リソース イベントループ プロセス スレッド タスク タスク スレッド タスク リソース (メモリなど) OS プロセスを複数起動して処理する方式 OS スレッドを複数使用して処理する方式 • 並行処理ではなく並列処理できる (※ 次スライド) • プロセスと比べると軽量 • メモリなどのリソースをプロセスごとに占有するため 複数プロセスを立てるとリソース消費が大きい • データ連携するためにはプロセス間通信が必要となる (今回のトピックだとあまり関係ないかもしれない) • データアクセスがスレッドセーフでない場合に race condition が発生し、結果として データの不整合やバグを引き起こす可能性がある スレッド タスク タスク タスク リソース (メモリなど) シングルスレッドでコンテキストを切り替えながら 複数タスクを処理する方式 • タスク側から制御を返す (await) までは他のタスクは 実行されないため race condition が起きにくい ※ あとで詳しく説明 このトークでは主に「マルチスレッド」と「イベントループ」について扱っていく
  6. 補足: 並列処理と並行処理 並列処理と並行処理は異なる 並列処理 / Parallelism 並行処理 / Concurrency タスク

    1 タスク 1 タスク 2 タスク 2 タスク 3 タスク 3 タスク 4 タスク 4 タスク 5 タスク 5 2プロセスで処理する例 1プロセス2スレッドで処理する例 “本当に” 同時に実行している 同時に実行している “ように見える” • 上記例では同時に2つの CPU を使用している • 上記例では同時に1つの CPU しか使用していない • プロセス内で複数のタスクをコンテキストスイッチしている • 「マルチプロセス」「マルチスレッド (GIL なし)」がこれに該当する ※ 今日は GIL の話はしない • 「マルチスレッド (GIL あり)」「イベントループ」がこれに該当する
  7. 「Web の並行処理はスレッドで扱うもの」という時代 もともと (90年代〜00年代) Web アプリケーションは「1リクエスト = 1スレッド」で捌くことで 多くのリクエストを並行処理するという考え方が標準的だった Python

    + Gunicorn プロセス (master) Web ブラウザ (クライアント) # WSGI (Web Server Gateway Interface, うぃずぎー) Python + Gunicorn プロセス (worker 1) スレッドプール Python で Web サービスを作るうえで 「アプリケーション実装」と「HTTP サーバ」を分離する ために策定されたインタフェース スレッド HTTP Gunicorn WSGI WSGI アプリケーション (Flask など) スレッド Python + Gunicorn プロセス (worker 2) スレッドプール スレッド スレッド # WSGI アプリケーション • WSGI 関数を提供するのみで、HTTP を喋らない • 代表的なフレームワークは Django や Flask # WSGI サーバ • WSGI サーバは HTTP を喋り、HTTP リクエストを WSGI アプリケーションに流す • 代表的なサーバ実装は Gunicorn や uWSGI
  8. C10K 問題 90年代、インターネットが普及して HTTP サーバが扱う接続の数が増えてきたことで 「1リクエスト = 1スレッド」という方式で扱える同時接続数の限界が見えてきた ① 1万

    (=10k) 件の 同時接続が来る HTTP サーバ リクエスト スレッド リクエスト スレッド リクエスト スレッド リクエスト スレッド ︙ ︙ リクエスト スレッド リクエスト スレッド リクエスト スレッド リクエスト スレッド HTTP サーバ ④ ならば、少ないスレッドで 多くの接続を扱いたい (I/O 多重化) ② これらを捌くには1万個の スレッドが必要になる 「1リクエスト = 1スレッド」が 前提なので ③ しかしスレッドはそんなに 大量に用意できない (C10K 問題) スレッドを増やせば増やすほど メモリなどのリソースを消費するため ︙ スレッド ⑤ この頃の I/O 多重化 は スケールできないので C10K 問題の解決にならない ※ 次スライドで説明
  9. あたらしい I/O 多重化のための仕組み C10K 問題を解決すべく epoll という新しいシステムコールが考案された (2002年頃) → (Python

    ではなく) OS のレベルで I/O 多重化に対する新しい仕組みが作られた select / poll (従来) ① Web サーバは 「どの接続にデータが届いているか」を 頻繁に確認したい OS プロセス fd OS プロセス fd Web サーバ プログラム fd たくさんの 接続 epoll fd fd fd fd fd ︙ fd fd fd データ届いた? poll() システムコール ② select / poll システムコールは 毎回すべての fd を確認するため 計算量が O(n) fd たくさんの 接続 ︙ fd fd fd ① データが届いた fd は OS カーネルが ready list に登録する Web サーバ プログラム データが届いた fd のリスト (ready list) epoll_wait() システムコール ② 「データが届いている fd」 だけをすぐに知ることができる → 計算量が O(1) に近い fd 接続数が増えると確認負荷が増える → C10K 問題が解決しない プロセスやスレッドを増やさなくても 大量の I/O 接続を扱うことができる → C10K 問題が解決する
  10. イベントループ epoll を使ってアプリケーションの並行処理を実現する方式が生まれた 無限ループを回してイベントを監視するので「イベントループ」 イベントループの仕組み (※ 擬似コード) 準備が完了した接続の一覧を epoll で取得する

    (Python を例にすると) • ここで Python プログラムが実行される • Python 側で await すると、このループに戻ってくる # イベントループを実装したソフトウェアの例 • Nginx • Node.js (libuv) → libuv は uvloop (Python のイベントループ実装) でも使われている
  11. Python のイベントループ実装 Python でもイベントループを扱う流れになり、標準モジュール asyncio が追加された • (00年代後半) epoll を土台としたサードパーティ製の非同期フレームワークが登場

    (Twisted, Tornado, gevent) • (2014年) Python 3.4 にて asyncio モジュールが追加 • (2015年) Python 3.5 にて async/await 構文が導入 • (2016年) Django Channels から派生して ASGI 仕様が策定 • (2017年) Uvicorn, Starlette が公開 • (2018年) FastAPI が公開 → asyncio の普及が加速 (した気がする)
  12. Web サーバの並行処理モデルもイベントループに対応 Python のアプリケーションインタフェースもイベントループに対応したものが作られた # ASGI Python + Uvicorn プロセス

    (master) Web ブラウザ (クライアント) (Asynchronous Server Gateway Interface, あずぎー) Python + Uvicorn プロセス (worker 1) • WSGI の非同期を考慮したバージョンなインタフェース • イベントループで並行処理をする前提 スレッド イベントループ HTTP Uvicorn # ASGI アプリケーション ASGI ASGI アプリケーション (FastAPI など) • ASGI 関数を提供するのみで、HTTP を喋らない • 代表的なフレームワークは FastAPI # ASGI サーバ Python + Uvicorn プロセス (worker 2) • ASGI サーバは HTTP を喋り、HTTP リクエストを ASGI アプリケーションに流す • 代表的なサーバ実装は Uvicorn
  13. FastAPI は ASGI フレームワーク FastAPI は ASGI フレームワークなので、リクエストはシングルスレッド・イベントループで並行処理される Python +

    Uvicorn プロセス (master) Web ブラウザ (クライアント) Python + Uvicorn プロセス (worker 1) スレッド イベントループ HTTP Uvicorn ASGI ASGI アプリケーション (FastAPI など) Python + Uvicorn プロセス (worker 2) さっきの図
  14. 冒頭の問い Q. def と async def はどう使い分ける? A. FastAPI は

    ASGI フレームワークなので、 async def を使うのが正解! 〜 完 〜 (?)
  15. イベントループだけだと困るケースがある 問題ない 場合 asyncio に対応しているライブラリの I/O 処理 → ノンブロッキング I/O

    asyncio に対応しているライブラリの (略) → ノンブロッキング I/O 終了 await している間、他のタスクを実行 (リクエストを処理) することができる アカン 場合 asyncio に対応しているライブラリの I/O 処理 → ノンブロッキング I/O asyncio に対応してないライブラリの (略) ブロッキング I/O (同期処理) この間はイベントループ中の 他のタスクが一切実行できない (停止する) I/O を同期処理するヤツがいると、イベントループ全体が止まる → サーバ全体のスループットが著しく落ちる (エラーが出るわけではないので、適切にモニタリングしていないとすぐには気付けないのがまた厄介) 終了
  16. FastAPI は同期 I/O 処理するための方法を提供している async def ではなく def で定義したパスオペレーション関数 (同期エンドポイント)

    は イベントループではなく FastAPI が管理するスレッドにて実行される Python + Uvicorn プロセス (master) Web ブラウザ (クライアント) Python + Uvicorn プロセス (worker 1) スレッド イベントループ HTTP Uvicorn ASGI ASGI アプリケーション (FastAPI など) スレッドプール (FastAPI が管理) スレッド スレッド スレッド スレッド スレッド 処理する存在 (スレッド) が複数いるので、 ひとつのスレッド内で同期 I/O 処理が あっても他のスレッドが別のリクエストを捌ける
  17. “I/O を同期処理するヤツ” とは 具体例を挙げてしまうと boto3 (AWS SDK) • 「AWS リソースにアクセスする」ための

    SDK なので、当然だが I/O が発生しまくる ライブラリ • AWS のシェアが高いので boto3 もよく使われる人気ライブラリ • 非同期対応はかなり昔から要望は出ているが、今のところ対応される気配はなさそう • あとからサポートするのが大変という事情はわかる • しかしここまで何も進んでいないと「AWS は Python をサポートする気が薄いのでは」と思わざるを得ない • aiobotocore というサードパーティモジュールはある • aiohttp を作っている aio-libs が提供している • 対応サービスが限定的 (S3 / DynamoDB / SQS / etc...)
  18. SDK を提供する場合は両方サポートしないといけない? サービスの開発者向け Python SDK を提供する場合はその多くで通信 (I/O) が発生するため、 「同期関数と非同期関数を両方サポートしなければならないか?」と言う話になるが... #

    大手クラウドベンダ # 生成 AI プロバイダ • AWS (同期のみ) • OpenAI (両方対応) ※ Stainless • Azure (両方対応) • Anthropic (両方対応) ※ Stainless • Google Cloud (多くのサービスは両方対応) • Google GenAI (両方対応) 現状は「両方サポートしたほうがよい」という状況になっている OpenAPI Spec から Python SDK を自動生成する Stainless のようなサービスも存在するので、 (サービス提供者は) 便利な仕組みに乗っかって両方サポートしていくというのがベストプラクティスかもしれない
  19. FastAPI 公式見解は https://fastapi.tiangolo.com/async/ 1. 非同期ライブラリを使用するなら async def 2. 同期 IO

    ライブラリを使用せざるを得ない場合は def 3. デフォルトでは async def
  20. 色分け問題 (function coloring problem) 内部で使用される関数にひとつでも非同期関数があると、それを呼び出している関数も非同期関数に しなければならないため、「全体を同期関数で作って、一部だけ非同期関数を使う」は容易ではない 関数 関数 関数 関数

    関数 関数 関数 関数 それを呼び出している 上位の関数は すべて非同期関数として 定義しなければならない 関数 関数 関数 処理の全体のほとんどを 同期処理で記述していても、 たった一箇所で 非同期関数を使用するだけで、 関数 関数 関数 関数 関数 関数 関数
  21. で、結局どう使い分ければ? 使用するライブラリによるので、アプリを作る時点で「どちらにするか」を決める • ごちゃまぜにするのは事故りそうなのでアプリ全体で統一したほうが良いと思う • 基本的には非同期関数 (async def) を使えば良いと思う 同期関数

    (def) に寄せる場合 非同期関数 (async def) に寄せる場合 # こういうケースで採用する # こういうケースで採用する • boto3 めちゃくちゃ使う場合 • 非同期ライブラリを使用したい • スレッド数の調整やスレッドセーフなどを意識せずに開発したい # 注意点 • 非同期ライブラリは使用できない ※ 非同期しか対応していないライブラリはほぼないので そこまで困ることは多くないかもしれない • スレッド数の調整は必要になる • 少なすぎると CPU が暇してるのにリクエストが捌けなくなる • 多すぎるとリソースを逼迫してスループットが落ちる # 注意点 • 絶対にブロッキング I/O を発生させない ※ どうしてもブロッキング I/O 処理が必要な場合は そこだけスレッドに逃がすようにする
  22. 他のプログラミング言語に「色分け問題」はあるか C10K 問題とその対処は OS レベルの話なので、他のプログラミング言語にとっても状況は同じだが、 「イベントループ」という新しい並行処理手法に対する対応方針は言語ごとに異なる 同様の問題があるらしい言語 イベントループを隠蔽している言語 • C#

    • Java (仮想スレッド) • Rust • Ruby (Fiber Scheduler) • Go (Goroutine) 上記のいずれの言語も、イベントループを使用する際に そもそも問題が存在しない言語 JavaScript / TypeScript 「実行環境がブラウザ (UI)」から始まっているので、 そもそも最初からシングルスレッド・ノンブロッキング IO 前提 処理を中断する (スケジューラに制御を戻す) タイミングを 隠蔽する何らかの仕組みがある → async /await のような構文を必要としない → 色分け問題が発生しない
  23. Python の色分け問題は解決できる? Mark Shannon 氏 (CPython コミッタ) による提案 (2025年3月) https://discuss.python.org/t/add-virtual-threads-to-python/91403

    • async/await の代替として、Java を参考にした「仮想スレッド」の導入を提案 • OS スレッドではなく、CPython VM 内でスレッドを実現する • 大量の仮想スレッドを、少数の OS スレッドで実行する (M:N スケジューリング) • (Goroutine にも似ている?) 議論は継続中で PEP 化には至っておらず、すぐには実現しなさそう 「色分け問題」について、いち Python ユーザとしては「そういうもの」として受け入れて使っていくのがよい
  24. 言いたかったこと FastAPI がリクエストを処理する仕組み (def と async def の違い) を 理解していないと、パフォーマンスが悪くなってしまうことがあるので気をつけて

    • FastAPI のエンドポイントを定義するときは def と async def で振る舞いが変わる • async def のときに同期 IO するとイベントループ全体が止まるから超気をつけて • def と async def どちらを使うかはアプリ全体で統一したほうがいいと思う ※ WSGI / ASGI サーバのチューニングの話も入れようと思っていたけど、入らなかった (申し訳ない)
  25. Yuki Ishikawa / @hoto17296 ちゅらデータ株式会社 Web アプリケーションエンジニア (何でも屋) 名古屋のあたりに在住 ありがたいことに

    PyCon JP 登壇は3回目 • PyCon JP 2021 「Python をフル活用した工場への AI 導入 & データ活用基盤構築事例」 • PyCon JP 2024 「ORM と向き合う」 • このとき「Prisma Client Python は良いぞ」って話をしたらその数カ月後に開発停止になってしまった… 気軽に話しかけてね!! • このトークに対する質問とかコメントとか反論とかお待ちしています • 今日明日はずっと会場内にいるし今日の After Party にもいます • X / Twitter でメンション飛ばしてくれてもいいです