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開発入門

page_footer_responsive




-BackEnd
-,

執筆者:

免責事項

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


comment

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

CAPTCHA


関連記事

laravel logo

Laravelで非同期実行する

1 はじめに1.1 動作環境2 準備2.1 デーブルの作成2.2 .envの修正3 ジョブの作成4 ジョブのディスパッチ5 キューワーカーを起動6 より細かな制御6.1 特定のキューにディスパッチする ...

Laravelのchunkメソッドとcursorメソッドのメモリ使用量

1 はじめに2 テスト用のデータ準備3 get()4 chunk()5 chunkById()6 cursor()7 さいごに8 おすすめ書籍 はじめに テーブルの全レコードに一括で処理を行うバッチを ...

aws

ALB+EC2な環境でhttpをhttpsにリダイレクトする

1 はじめに1.1 前提条件2 ALBの設定3 Nginxの設定3.1 注意点4 さいごに はじめに httpsに対応済みのWebサイトの場合、httpでアクセスされた時にhttpsでリダイレクトする ...

laravel logo

Inertia使ってみた①

1 はじめに2 Inertiaとは3 ルーティング4 LaravelからReactに値渡し5 レスポンス5.1 初回5.2 page object5.3 2回目以降5.4 リロード時6 さいごに7 お ...

Go言語

Go言語の基礎〜Go 1.11 開発環境構築とパッケージバージョン管理〜

1 はじめに2 Go言語(Golang)とは2.1 シンプルな構文2.2 コンパイル言語2.3 並行処理2.4 その他の特徴3 Go開発環境の構築3.1 Goのインストール3.1.1 1. homeb ...

フォロー

blog-page_side_responsive

2024年9月
1234567
891011121314
15161718192021
22232425262728
2930  

アプリ情報

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