DB接続プール vs リクエストごと生成・破棄の使い分け
接続プールとは
DB接続をあらかじめ一定数作成しておき、リクエストごとに「借りて返す」仕組み。 Railsがデフォルトで採用している方式。
# Rails database.yml production: pool: 5 checkout_timeout: 5 比較
| 接続プール(Rails等) | リクエストごと生成・破棄 | |
|---|---|---|
| 接続コスト | 初回のみ。以降は借りる/返すだけ | 毎回 connect + close |
| 同時接続数 | 制御できる(pool_size=5 等) | リクエスト数に比例して増える |
| メモリ使用 | 一定(プールサイズ分) | リクエストに比例 |
| 実装の複雑さ | プール管理が必要 | シンプル |
| 障害時の影響 | プール内の不良接続が再利用されるリスク | 毎回新規なので不良接続が残らない |
| 向いている DB | PostgreSQL, MySQL(接続が重い) | SQLite(接続が軽い) |
接続プールのメリット
接続確立が重い DB で効く。PostgreSQL や MySQL は接続時に TCP ハンドシェイク、認証、セッション初期化が走るので、毎回やると数十ms かかる。
接続プールのデメリット
- プール枯渇: 全接続が貸出中だとリクエストがブロックされる
- コネクションリーク: 返却し忘れるとプールが徐々に枯渇する
- 不良接続: DB 再起動後に古い接続が残って壊れた接続を掴むことがある
- 設定のチューニング: pool_size を適切に設定しないとパフォーマンスが出ない
DB別の接続コスト
PostgreSQL: ~20-50ms → プール必須 MySQL: ~10-30ms → プール推奨 SQLite: ~0.01ms → プール不要 SQLiteはファイルをopen()するだけで、TCPもネゴシエーションもないので、プールの管理コストの方が高くつく。
FastAPI でのリクエストごと生成・破棄パターン
def get_db() -> Generator[sqlite3.Connection, None, None]: conn = get_connection() # リクエストごとに新しい接続を作成 try: yield conn # ルートハンドラに接続を渡す finally: conn.close() # レスポンス返却後に必ず接続を閉じる FastAPIのyield依存関係がライフサイクルを保証する。yield前がセットアップ、yieldの値が注入対象、finallyがクリーンアップ。
PostgreSQLに変える場合(SQLAlchemy + プール)
from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker engine = create_engine("sqlite:///myweb.db", pool_size=5) SessionLocal = sessionmaker(bind=engine) def get_db(): db = SessionLocal() # プールから接続を借りる try: yield db finally: db.close() # プールに返す(破棄ではない) まとめ
- 接続が重い DB(PostgreSQL, MySQL)→ 接続プール
- 接続が軽い DB(SQLite)→ リクエストごと生成・破棄
- Railsがプールをデフォルトにしているのは PostgreSQL/MySQL 前提だから
This post is licensed under CC BY 4.0 by the author.