Python | Web フレームワーク:背景タスク

Python
スポンサーリンク

概要(背景タスク=「レスポンスの後で、裏でこっそり動く仕事」)

背景タスクは、

「HTTP レスポンスはすぐ返したいけど、
そのあとにやりたい処理がまだ残っている」

ときに使う仕組みです。

例えば、

ログを外部サービスに送る
完了メールを送信する
重い集計処理を“結果だけ返してあとでゆっくり”やる

といった、「ユーザーにはすぐ OK を返したいが、裏ではまだ仕事がある」ケースで役立ちます。

FastAPI には BackgroundTasks という簡易な仕組みが用意されていて、
「レスポンスを返した後に実行する関数」を登録できます。

ここから、概念 → 基本コード → 実用パターン → 限界と注意点
の順でかみ砕いていきます。


なぜ背景タスクが必要になるのか(同期処理だけだと起きる問題)

全部レスポンスの前にやると「待ち時間地獄」になる

まず、背景タスクを使わない“素直な”コードを考えます。

例えば、「ユーザー登録 → メール送信」の処理。

from fastapi import FastAPI

app = FastAPI()

def send_welcome_email(email: str):
    # 実際には SMTP や外部APIでメール送信
    import time
    time.sleep(5)  # メール送信に時間がかかる想定

@app.post("/signup")
def signup(email: str):
    # ユーザーをDBに保存したり、いろいろやる
    send_welcome_email(email)
    return {"message": "signed up"}
Python

このコードだと、

ユーザー「/signup を叩く」
サーバー「ユーザー保存 → メール送信(5秒待つ) → レスポンス」

になるので、ユーザーは 5 秒以上待たされます。

「メール送信が終わるまでレスポンスを返さない」
というのは、体感としてかなりストレスです。

重要なのは、「ユーザー視点では、“ユーザー登録が成功した”と分かればいい」ことです。
メール送信がいつ終わるかまでは、あまり気にしません。

そこで出てくるのが背景タスクです。


FastAPI の BackgroundTasks の基本構造

関数と引数を「後で実行してね」と登録するだけ

FastAPI には BackgroundTasks という仕組みがあり、
エンドポイントの引数に受け取ることで使えます。

先ほどのサインアップを、背景タスクで書き直してみます。

from fastapi import FastAPI, BackgroundTasks

app = FastAPI()

def send_welcome_email(email: str):
    import time
    time.sleep(5)
    print(f"Welcome email sent to {email}")

@app.post("/signup")
def signup(email: str, background_tasks: BackgroundTasks):
    # ここで DB にユーザーを保存する、などの処理をする
    background_tasks.add_task(send_welcome_email, email)
    return {"message": "signed up"}
Python

このようにすると、

サーバー「ユーザー保存 → 背景タスク登録 → すぐレスポンス返す」
レスポンス返却後「裏で send_welcome_email(email) が実行される」

という動きになります。

ポイントは、

背景タスクの関数(send_welcome_email)は普通の関数
background_tasks.add_task(関数, 引数...) で「後で実行してね」と登録する
レスポンスはタスク実行を待たずに返される

という流れです。

ここで、「関数そのものを登録する」という感覚を掴んでおいてください。
「今すぐ呼び出す」のではなく、「後で呼び出してもらうように登録する」というイメージです。


もう少し実務寄りの例(ログ・通知・外部連携を背景タスクに逃がす)

例1:重いログ送信を背景に回す

例えば、「エンドポイントの処理結果を外部のログサービスに送る」処理があり、
それが微妙に重いとします。

def send_log_to_external_service(user_id: int, action: str):
    import time
    time.sleep(2)
    print(f"Logged: user={user_id}, action={action}")
Python

これを毎回同期的にやると、
ユーザーは余計な待ち時間を払うことになります。

背景タスクにするとこうなります。

from fastapi import FastAPI, BackgroundTasks

app = FastAPI()

@app.post("/purchase")
def purchase_item(user_id: int, item_id: int, background_tasks: BackgroundTasks):
    # ここで購入処理(DB 更新など)を行う
    background_tasks.add_task(send_log_to_external_service, user_id, f"purchase:{item_id}")
    return {"message": "purchased", "item_id": item_id}
Python

ユーザー視点では、

購入処理が終わった時点でレスポンス
ログ送信はその後に裏で行われる

という動きになります。

重要なのは、「ユーザーに必要な処理」と「後からでいい処理」を分けることです。
背景タスクに向いているのは、「結果に影響しない、後始末や付随的な処理」です。

例2:完了通知(メール・Slack)なども背景タスク向き

何か重い処理が完了したことを、
Slack やメールで通知したいことがあります。

処理本体は同期的に動かし、
通知だけ背景タスクに逃がすパターンも多いです。

def notify_slack(message: str):
    # Slack API を叩く処理
    ...

@app.post("/job")
def run_job(background_tasks: BackgroundTasks):
    # 重い処理
    result = "something done"
    background_tasks.add_task(notify_slack, f"Job finished: {result}")
    return {"status": "accepted"}
Python

「ユーザーに OK と返すタイミング」と
「通知を送るタイミング」を切り分けることで、
レスポンスの体感速度を改善できます。


BackgroundTasks の「できること」と「限界」をちゃんと理解する

できること:レスポンス後に同じプロセス内で処理を続ける

BackgroundTasks は、非常にシンプルな仕組みです。

レスポンスを返した後に、
同じ Uvicorn / Gunicorn プロセス内で関数を実行する

これだけです。

つまり、

あくまで「同じアプリプロセスの中で、後回しにする」だけで、
別プロセスや別マシンに送るわけではない

ということです。

このため、

ちょっと重い I/O(メール、API 呼び出し、ログ送信など)
ユーザー体験に直結しない処理

には向いています。

限界1:サーバープロセスが落ちるとタスクも消える

BackgroundTasks は「アプリの中で」動いているので、

サーバーが再起動した
プロセスがクラッシュした

といった場合、その時点で走っていた背景タスクは消えます。

キューに溜めて再実行するような仕組みはありません。

「絶対に失敗させたくない重いバッチ」や
「時間を指定して実行するタスク」などには、

Celery
RQ
dramatiq

のようなジョブキュー/ワーカーの仕組みを使う方が安全です。

BackgroundTasks は、

軽めで、多少失敗しても致命的でない処理を
「とりあえず後ろで流す」ための簡便な手段

ととらえるのがちょうどいいです。

限界2:長時間かかるタスクには不向き

BackgroundTasks に、
数分〜数十分かかるような処理を入れるのはあまりおすすめしません。

ワーカー数の上限やタイムアウトに引っかかったり、
プロセスのライフサイクルに巻き込まれたりします。

イメージとしては、

数百ミリ秒〜数秒くらい
長くても十数秒以内

に終わる処理を想定しておくのが無難です。

それ以上の「ガチ重いバッチ」は、
別途バッチ基盤やジョブキューで扱うほうが、
設計として健全です。


背景タスクと asyncio(非同期)との関係を軽く整理しておく

sync 関数も async 関数も両方登録できる

BackgroundTasks に登録する関数は、
基本は普通の sync 関数で問題ありません。

内部で requestssmtplib を使っても構いません。

もしアプリ全体を非同期化していて、

非同期のメール送信関数
非同期の外部 API クライアント

などを使いたい場合は、
async 関数を登録することもできます(FastAPI が面倒を見てくれます)。

ただし、初心者の段階では、

背景タスクにはまず「普通の関数」を渡す
async を使うのは、その後でいい

くらいの感覚で十分です。

重要なのは、「いつ実行されるのか」の理解であって、
最初から非同期構文を複雑にしないことです。


まとめ(背景タスクは「応答を早くするための後片付けエリア」)

FastAPI の背景タスク(BackgroundTasks)を整理すると、こうなります。

  • 背景タスクは、「HTTP レスポンスを返したあとに、同じプロセス内で実行される仕事」を登録する仕組みで、ユーザーの待ち時間を減らすための道具。
  • background_tasks.add_task(func, *args, **kwargs) と書くだけで、「この関数をあとで実行してね」と登録できる。登録されたタスクはレスポンス後に裏で動く。
  • メール送信・ログ送信・通知・外部サービス連携など、「結果には影響しないがやっておきたい処理」を背景タスクに逃がすと、レスポンスが体感的に速くなる。
  • BackgroundTasks はあくまで「同じアプリプロセス内での後回し」であり、プロセスが落ちるとタスクも消える。長時間・重要バッチにはキュー系(Celery 等)のほうが向いている。
  • 「今すぐユーザーに返すべき仕事」と「後でよい仕事」を切り分け、その「後でよい仕事」を BackgroundTasks に追い出す感覚を身につけると、Web API の体験が一段レベルアップする。

タイトルとURLをコピーしました