BackEnd

FastAPIのPath Operationをasync defにするときはブロッキングに気をつけよう!

投稿日:

はじめに

今回は、FastAPIのAPIのエンドポイントを定義する方法である Path Operation に渡す関数を async def にした時の挙動について調べました。この手の記事はすでにたくさんありますが、今回自分で調べて裏をとりたかったため、調べた内容をまとめてみました。

Path Operationと並行処理

FastAPIでAPIを定義するには、Path Operationデコレータを使います。以下がFastAPIの最小限のコードです。

@app.get("/") といったデコレータに関数を登録することで、指定されたパスとメソッドへのリクエストに対応する関数を定義できます。
この関数は async def にすることもできます。

どちらのコードでも、実際に動かしてみると一見挙動に違いは見られません。しかし、実はこの関数の呼び出し方が異なるのです。

通常の def は、APIリクエストがあった時に直接呼び出されるのではなく、FastAPIのスレッドプールによって呼び出され、 awaitされます。こうすることで、同期関数で重たい処理があっても、他のリクエストをブロックすることはなく、「並列」処理されます。

ところが、 async def はイベントループから呼び出されます。イベントループは「並行」処理なので、シングルスレッドでタスクを切り替えながら実行されます。そのため、同期で重たい処理やI/O待ちなどが発生してしまうと、制御が解放されず、他のタスクが実行待ちのままとなってしまいます。

結果として、他のAPIリクエストを受け付けられなくなってしまい、ブロッキングしてしまうというわけです。

挙動の確認

以下のコードを使って実験してみます。

このサンプルでは、asyncpgを使ってDBにSELECTしつつ、requestsで外部APIにリクエストしています。asyncpgは非同期なので、このAPIの関数も async def にせざるを得ません。

にもかかわらず、requestsは同期処理となっているため、外部APIのレスポンス待ちが発生し、I/Oブロックしてしまいます。

対応方法

この問題の対応方法を3パターン考えてみました。

asyncioを使って自力で非同期化する

組み込みモジュールであるasyncioを使い、イベントループ内で関数を実行することで、制御を解放します。

このコードを使うと、 GET /req_very_slow_task のレスポンスを待っている最中にも、 GET / はブロッキングされず、すぐレスポンスが返ってきます。

今回は、requestsを使いましたが、例えばboto3でも同じ手法で非同期化することができます。

async対応済みのパッケージを使う

先ほどの例ではrequestsを使いましたが、同じHTTPクライアントである httpx は初めから非同期をサポートしています。(ちなみに、requestsはメンテナンスが止まっているので、今現在では同期の場合でもhttpxがおすすめです。)

httpxを使って、次のようにコードを変更します。

httpx.AsyncClient を使うことで、自力でasyncioを使わなくても簡単に非同期化することができています。そのため、先ほどよりコードがすっきりしました。

その他の同期パッケージも、他の代替えがある場合にはそれを使用することで、自力でasyncioを使わなくとも非同期化することができます。

やりたいこと 同期 非同期
HTTPクライアント httpx or requestsなど httpxなど
AWS クライアント boto3 aioboto3
※全機能が使えるのはs3のみ
ファイルI/O 組み込みの open() 関数 aiofiles
PostgreSQL psycopgなど psycopg or asyncpg など
MySQL mysqlclient or PyMySQL など asyncmy or aiomysql
※開発は止まっている模様

async defをやめる

アプリケーション全体でasyncpgを使うと決めた場合、おそらくほとんどのエンドポイントが async def になると思いますが、それは至るとこでI/Oバウンドなどによるブロッキングが起きないか、気をつけなければならないことになります。

そこまでパフォーマンスにシビアでなければ、アプリケーション全体で使用するであろう処理を同期にすることで、コード全体をすっきりとさせ、やるべきことに集中することができるかもしれません。

冒頭で話した通り、同期の def もスレッドプール内のスレッドによって実行されるため、他のAPIをブロッキングすることはありません。もちろん、スレッドの切り替え(コンテキストスイッチ)が発生することから、asyncよりかはパフォーマンスが劣るみたいですが、パフォーマンスをどこまで切り詰めるかと、コードのわかりやすさのバランスが重要だと思います。

さいごに

非同期は奥が深いですね。CPUをどのように使うのか、並列と並行ではアプローチが異なることも含めて、改めて勉強になりました。

おすすめ書籍

エキスパートPythonプログラミング 改訂4版 (アスキードワンゴ) ハイパーモダンPython ―信頼性の高いワークフローを構築するモダンテクニック 動かして学ぶ!Python FastAPI開発入門

blog-page_footer_336




blog-page_footer_336




-BackEnd
-,

執筆者:

免責事項

このブログは、記事上部に記載のある投稿日時点の一般的な情報を提供するものであり、投資等の勧誘・法的・税務上の助言を提供するものではありません。仮想通貨の投資・損益計算は複雑であり、個々の取引状況や法律の変更によって異なる可能性があります。ブログに記載された情報は参考程度のものであり、特定の状況に基づいた行動の決定には専門家の助言を求めることをお勧めします。当ブログの情報に基づいた行動に関連して生じた損失やリスクについて、筆者は責任を負いかねます。最新の法律や税務情報を確認し、必要に応じて専門家に相談することをお勧めします。


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


関連記事

PHPerだけどKotlinを勉強したって良いよね その2〜コンストラクタ編〜

1 はじめに2 コンストラクタ2.1 プライマリコンストラクタ2.2 セカンダリコンストラクタ2.3 ニックネームのみを入力した人2.4 ニックネームと email を入力した人2.5 Faceboo ...

rails

Rails 6の変更点と新機能

1 はじめに2 概要3 Rubyのサポートバージョン4 Webpacker4.1 Webpackerの設定4.2 Webpackerでのビルド4.3 ビルドしたJavaScriptファイルを読み込む4 ...

rails

RailsでERBからJavaScriptにhashを渡す方法

1 はじめに2 カスタムデータ属性とは3 実装例3.1 コントローラの実装3.2 ビューの実装3.3 実行結果4 さいごに はじめに 以前、選択したプルダウンメニューに応じて別のプルダウンメニューの内 ...

laravel logo

Laravel Admin でCSVインポートを実装する

1 はじめに2 下準備2.1 Laravel Adminの導入2.2 Laravel Excelの導入3 Laravel Excelの実装3.1 Importクラスの作成3.2 モデルのfillabl ...

Go言語

Golangのsyncパッケージによる同期・排他制御

1 はじめに2 sync.WaitGroup3 sync.Mutex4 sync.RWMutex5 sync.Map6 sync.Once7 sync.Pool8 さいごに9 おすすめ書籍 はじめに ...

フォロー

blog-page_side_responsive

2024年9月
1234567
891011121314
15161718192021
22232425262728
2930  

アプリ情報

私たちは無料アプリもリリースしています、ぜひご覧ください。 下記のアイコンから無料でダウンロードできます。