0Pricing
Python Academy · レッスン

本番環境での非同期 I/O パターン

HTTP クライアント、キュー、実際のパイプラインに asyncio を適用します。

「本番環境での非同期 I/O パターン」はCoddyKit上の無料Python Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPython Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Python Academyコースには全4レッスンが含まれています。

イベントループのベストプラクティス

常にasyncio.run()を唯一のエントリーポイントとして使用します。同期コードと非同期コードを不用意に混在させないでください。また、時間のかかるCPU処理でイベントループを決してブロックしないでください。

import asyncio

async def main():
    # Never: time.sleep() — blocks the event loop
    # Always: await asyncio.sleep()
    await asyncio.sleep(1)

asyncio.run(main())

接続プール

リクエストごとに新しい接続を開くのではなく、接続を再利用します。asyncpgやaiohttpなどのライブラリには、組み込みの接続プールが用意されています。

# pip install asyncpg
import asyncpg, asyncio

async def main():
    pool = await asyncpg.create_pool(
        dsn="postgresql://user:pass@host/db",
        min_size=5, max_size=20
    )
    async with pool.acquire() as conn:
        row = await conn.fetchrow("SELECT 1")
    await pool.close()

asyncio.Queueによるプロデューサー・コンシューマー

asyncio.Queueを使用すると、並行して実行されるプロデューサーとコンシューマーのタスクを分離できます。

import asyncio

async def producer(q):
    for i in range(5):
        await q.put(i)
        await asyncio.sleep(0.5)
    await q.put(None)  # sentinel

async def consumer(q):
    while (item := await q.get()) is not None:
        print(f"processing {item}")
        q.task_done()

async def main():
    q = asyncio.Queue()
    await asyncio.gather(producer(q), consumer(q))

asyncio.run(main())

セマフォによるレート制限

外部APIに過剰な負荷をかけたり、レート制限に達したりしないよう、外向きのリクエスト数を制限します。

import asyncio, aiohttp

SEM = asyncio.Semaphore(10)  # 10 concurrent requests max

async def fetch(session, url):
    async with SEM:
        async with session.get(url) as r:
            return await r.json()

async def main(urls):
    async with aiohttp.ClientSession() as s:
        return await asyncio.gather(*[fetch(s, u) for u in urls])

指数バックオフによる再試行

一時的な障害に適切に対処するため、再試行可能な処理をバックオフのロジックでラップします。

import asyncio

async def retry(coro_fn, retries=3, base=1):
    for attempt in range(retries):
        try:
            return await coro_fn()
        except Exception as e:
            if attempt == retries - 1: raise
            delay = base * 2 ** attempt
            await asyncio.sleep(delay)

グレースフルシャットダウン

長時間実行されるタスクではasyncio.CancelledErrorを捕捉し、OSシグナルを処理して正常にシャットダウンします。

import asyncio, signal

async def server():
    stop = asyncio.Event()
    loop = asyncio.get_running_loop()
    loop.add_signal_handler(signal.SIGINT, stop.set)
    await stop.wait()
    print("Shutting down gracefully")

ストリーミングレスポンス

非同期ジェネレーターを使用すると、大きなレスポンス全体をメモリに読み込まずにストリーミングできます。

import asyncio, aiohttp

async def stream(url):
    async with aiohttp.ClientSession() as s:
        async with s.get(url) as r:
            async for chunk in r.content.iter_chunked(4096):
                process(chunk)

async def process(chunk):
    await asyncio.sleep(0)
    print(len(chunk), "bytes")

FastAPIのバックグラウンドタスク

FastAPIのBackgroundTasksは、レスポンスの送信後に実行する処理をキューに追加するため、エンドポイントの応答性を維持できます。

from fastapi import FastAPI, BackgroundTasks

app = FastAPI()

def send_email(to: str, body: str):
    # heavy synchronous work
    pass

@app.post("/register")
async def register(email: str, bg: BackgroundTasks):
    bg.add_task(send_email, email, "Welcome!")
    return {"status": "registered"}

ハートビートパターン

create_taskを使用して、メインのサーバーループと並行して定期的なバックグラウンドタスクを実行します。

import asyncio

async def heartbeat():
    while True:
        print("alive")
        await asyncio.sleep(30)

async def main():
    asyncio.create_task(heartbeat())
    await serve_forever()

非同期コードのプロファイリング

非同期処理に対応したプロファイリングには、yappiまたはpy-spyを使用します。asyncioのデバッグモードを有効にすると、遅いコールバックに関する警告を確認できます。

# Enable debug to catch slow callbacks:
# asyncio.run(main(), debug=True)

# PYTHONASYNCIODEBUG=1 python server.py
# logs: Executing <Task coroutine=...> took 0.150 seconds

よくある落とし穴の回避

重要なルールは次のとおりです。イベントループ上でブロッキング処理を呼び出さないこと、コルーチンを必ずawaitすること、シャットダウン時にタスクをキャンセルすること、接続プールを使用すること、セマフォで同時実行数を制限することです。

import asyncio

async def bad():
    import time
    time.sleep(2)        # blocks the entire event loop!

async def good():
    await asyncio.sleep(2)  # yields control

確認問題

並行して実行されるプロデューサーとコンシューマーを分離するasyncioのプリミティブはどれですか。

まとめ

本番環境での非同期処理パターンでは、接続プールを使用し、Semaphoreで同時実行数を制御し、再試行とバックオフを実装し、大きなデータをストリーミングし、シグナル受信時に正常にシャットダウンし、create_taskで定期的なタスクを実行します。

よくある質問

「本番環境での非同期 I/O パターン」レッスンは無料ですか?

はい。「本番環境での非同期 I/O パターン」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Python Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Python Academyコースには全4レッスンが含まれています。

「本番環境での非同期 I/O パターン」で何を学びますか?

HTTP クライアント、キュー、実際のパイプラインに asyncio を適用します。 ブラウザで直接実行するハンズオンコードでPython Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Python Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのPython Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「本番環境での非同期 I/O パターン」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このPython Academyレッスンでコードを書いて実行できますか?

はい。すべてのPython Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. コルーチンとイベントループ
  2. await、タスク、ギャザリング
  3. 非同期コンテキストマネージャーとイテレーター
  4. 本番環境での非同期 I/O パターン
← Python Academyに戻る