Skip to content

FastAPIの実行モデルと前提

FastAPIの実行モデルと、実務で事故を防ぐための前提条件を詳しく解説します。

実行モデルとリソースの物理的制約

Section titled “実行モデルとリソースの物理的制約”

コンピュータ資源は有限であり、性能ではなく制約を前提に設計することが基本です。

CPU・メモリよりも先に枯渇するリソース:

  1. DB・外部APIのコネクション数

    • 接続プールの上限(例: SQLAlchemyのpool_size=5
    • 接続リークは数時間後にシステム全体を停止させる
  2. イベントループのブロック

    • 同期I/O操作により、イベントループがブロックされる
    • すべてのリクエストが処理できなくなる
  3. ファイル記述子

    • OSレベルの制限(通常1024〜65536)
    • ファイルやソケットを適切にクローズしないと枯渇
  4. メモリリーク

    • オブジェクトの参照が保持される
    • グローバル変数やクラス変数の不適切な使用

実際の事故例:

10:00:00 - アプリケーション起動(接続プール: 5/5)
10:00:01 - リクエスト1受信(接続取得: 6/5 → 待機)
10:00:02 - リクエスト2受信(接続取得: 7/5 → 待機)
...
10:30:00 - 接続が解放されず、すべてのリクエストが待機状態
10:30:01 - タイムアウトエラーが大量発生
10:30:02 - システム全体が応答不能

実行モデル:

Pythonコード (.py)
↓ インタープリタで実行
ネイティブコード(実行時)

重要な特徴:

  1. 動的型付け: 実行時に型が決定される
  2. ガベージコレクション: 自動メモリ管理(ただし、参照が保持されている場合は動作しない)
  3. GIL(Global Interpreter Lock): 同時に1つのスレッドのみが実行される(非同期処理で並行性を実現)
  4. 非同期処理: async/awaitによる非同期処理(同期I/O操作は避ける)

FastAPIのトランザクション管理:

# SQLAlchemyでのトランザクション管理
from sqlalchemy.orm import Session
async def create_order(db: Session, order_data: OrderData) -> Order:
# トランザクション内の処理
order = Order(**order_data.dict())
db.add(order)
for item in order_data.items:
inventory = db.query(Inventory).filter_by(product_id=item.product_id).first()
inventory.stock -= item.quantity
db.commit()
return order

特徴:

  • 明示的なトランザクション境界: db.commit()で明示
  • 手動ロールバック: エラー時に手動でロールバック
  • 依存性注入: FastAPIの依存性注入により、セッションを管理

他言語との比較:

// Java: 宣言的トランザクション管理
@Transactional
public Order createOrder(OrderData orderData) {
Order order = orderRepository.save(new Order(orderData));
return order;
}

FastAPIの非同期処理:

# async/awaitを使用
from fastapi import FastAPI
import httpx
app = FastAPI()
@app.post("/orders")
async def create_order(order_data: OrderData):
# 非同期処理
async with httpx.AsyncClient() as client:
response = await client.post("https://api.example.com/charge", json={
"orderId": order.id,
"amount": order_data.amount,
})
return response.json()

特徴:

  • 信頼できる非同期: async/awaitによる制御
  • エラーハンドリング: try-exceptでエラーを処理
  • 再実行: 手動で実装する必要がある

他言語との比較:

// Node.js: Promise/async-await
async function createOrder(orderData: OrderData) {
const response = await fetch('https://api.example.com/charge', {
method: 'POST',
body: JSON.stringify(orderData),
});
return await response.json();
}
環境特徴主なリスク
Serverless (Lambda/Vercel)短寿命・自動スケールコールドスタート、接続バースト、DBパンク、実行時間制限(Lambda: 15分、Vercel: 300秒)
常駐プロセス (Uvicorn)長寿命・安定動作メモリリーク、イベントループのブロック、接続リーク

制約:

# ❌ 悪い例: Serverless環境で問題のあるコード
from fastapi import FastAPI
app = FastAPI()
@app.post("/orders")
async def create_order(order_data: OrderData):
# 問題: 長時間実行される可能性がある
# 問題: トランザクションが長時間保持される
# 問題: 接続プールが適切に管理されない
order = await create_order_in_db(order_data)
return order

問題点:

  • 実行時間の制限: Lambdaは最大15分、Vercelは最大300秒
  • コールドスタート: Pythonの起動に時間がかかる(500ms-2s)
  • メモリ制限: メモリ使用量に制限がある(Lambda: 128MB〜10GB)
  • 接続バースト: スケールアウト時に接続プールが急増し、DBがパンクする可能性

解決策:

# ✅ 良い例: Serverless環境に適したコード
from fastapi import FastAPI
from celery import Celery
app = FastAPI()
celery_app = Celery('tasks', broker='redis://localhost:6379')
@app.post("/orders")
async def create_order(order_data: OrderData):
# 1. バリデーション(短時間)
validate_order_data(order_data)
# 2. 注文を作成(短時間)
order = await create_order_in_db(order_data)
# 3. 非同期処理をキューに投入
process_order.delay(order.id)
# 4. 即座にレスポンスを返す
return {"orderId": order.id, "status": "PROCESSING"}

Uvicornアプリケーションサーバー

Section titled “Uvicornアプリケーションサーバー”

特徴:

# 常駐プロセス環境での実行
import uvicorn
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

メリット:

  • 長時間実行可能: 実行時間の制限がない
  • 接続プール: データベース接続プールを保持
  • キャッシュ: メモリキャッシュを保持
  • バックグラウンド処理: Celeryによる処理

実装例:

# ✅ 良い例: 常駐プロセス環境に適したコード
from fastapi import FastAPI
from celery import Celery
app = FastAPI()
celery_app = Celery('tasks', broker='redis://localhost:6379')
@app.post("/orders")
async def create_order(order_data: OrderData):
# トランザクション内で処理
order = await create_order_in_db(order_data)
# バックグラウンド処理をCeleryで実行
process_order.delay(order.id)
return order

FastAPIの実行モデルと前提のポイント:

  • リソースの物理的制約: CPU・メモリよりも先に枯渇するのは、DB接続数・イベントループのブロック・ファイル記述子・メモリリーク
  • Python: 動的型付け、ガベージコレクション、GIL、非同期処理
  • トランザクション境界: db.commit()で明示、手動ロールバック
  • 非同期処理: async/awaitによる制御、エラーハンドリングが必要(同期I/O操作は避ける)
  • Serverless環境: 実行時間制限、コールドスタート、メモリ制限、接続バースト
  • 常駐プロセス環境: 長時間実行可能、接続プール、キャッシュ、Celery(メモリリーク・接続リークに注意)

重要な原則: 性能ではなく制約を前提に設計する。イベントループのブロックや接続リークは数時間後にシステム全体を停止させる。