前提条件
- uv と Python 3.11 以降。本ガイドは Python 3.13、FastAPI 0.142、SQLAlchemy 2.1、asyncpg 0.31、Alembic 1.20、Uvicorn 0.54 で動作確認済みです。
- ClickHouse Cloud アカウント
- データベース作成用の
psql。CREATE DATABASEステートメントは SQL コンソール で実行することもできます。
ClickHouse Managed Postgres サービスを作成する
ClickHouse Cloud コンソールで New service をクリックし、Postgres を選択します。インスタンスは数分で利用可能になります。詳しい手順については、クイックスタートを参照してください。接続情報を取得する
サービスの左サイドバーで Connect をクリックします。表示されるモーダルにはユーザー名、パスワード、サーバー、ポートが記載されており、Directly / via PgBouncer を切り替えるトグルもあります。 このガイドでは両方の接続を使用します。- PgBouncer (ポート
6432) :アプリで使用します。SQLAlchemy は Uvicorn の各 worker で接続の pool を保持しますが、同梱の PgBouncer を使えば、多数の worker やレプリカで少数の Postgres 接続を共有できます。PgBouncer はトランザクションプーリングモードで動作します。 - Direct (ポート
5432) :Alembic で使用します。移行は DDL を実行し、長時間に及ぶことがあり、セッション状態に依存する場合もあるため、Postgres に直接接続する必要があります。
sslmode=verify-full&sslrootcert=... になります。asyncpg はこれらのパラメータを読み取らないため、モーダルからはパスワードとホストのみをコピーしてください。CA 証明書は、アプリがコード内で渡します (接続を設定するを参照) 。
プロジェクトをセットアップする
プロジェクトを作成し、FastAPI、Uvicorn、SQLAlchemy (asyncio extra 付き) 、asyncpg、Alembic をインストールします。
接続を設定する
ダウンロードした CA 証明書をプロジェクトのディレクトリに移動し、ファイル名をca-certificate.pem にリネームします。
アプリ用のデータベースを作成します。<PASSWORD> とホストは、Connect モーダルに表示される値に置き換えてください。
.env ファイルを作成するか、既存の .env ファイルに以下の変数を追加してください。2 つの URL はポートのみが異なり、どちらも postgresql+asyncpg スキームを使用し、SSL パラメータは含みません。
.env
DATABASE_CA_CERT は、コマンドを実行するディレクトリからの相対パスとして解決されます。パスワードに @、/、# などの特殊文字が含まれる場合は、URL エンコードしてください。.env はバージョン管理に含めないでください。
app/db.py を作成します。このファイルでは、TLS 設定、engine、およびアプリ全体で共有する session ファクトリを構築します:
app/db.py
PROTOCOL_TLS_CLIENT を指定して作成した ssl.SSLContext は、有効な証明書を必須とし、ホスト名も検証します。これは sslmode=verify-full と同等です。SQLAlchemy は、このコンテキストを connect_args 経由で asyncpg に渡します。
TLS の設定を URL に含めたい場合や、アプリケーションで同期型の SQLAlchemy session を使用している場合は、asyncpg の代わりに psycopg ドライバーを使用してください。psycopg は libpq をベースにしており、
create_engine と create_async_engine のどちらでも Connect モーダルのパラメータ (例: postgresql+psycopg://...:5432/guide_fastapi?sslmode=verify-full&sslrootcert=ca-certificate.pem) をそのまま使用できます。モデルを定義する
型付きの SQLAlchemy モデルを定義したapp/models.py を作成します。
app/models.py
移行を実行する
async テンプレートから Alembic 環境を作成します:migrations/env.py の内容を置き換えます。新しいバージョンでは、自動生成用にモデルを読み込み、アプリと同じ TLS 設定で Postgres に直接接続します。
migrations/env.py
env.py 側でエンジンを構築するため、Alembic は alembic.ini 内のプレースホルダー sqlalchemy.url を無視します。
モデルからマイグレーションを生成して適用します:
migrations/versions に生成されたファイルを確認し、コードと一緒にコミットしてください。Alembic は適用済みのリビジョンを alembic_version テーブルに記録します。モデルを変更した場合は、その都度 revision --autogenerate と upgrade head を再実行してください。
API エンドポイントを追加する
app/main.py を作成します。各リクエストは FastAPI の依存関係を通じて専用の AsyncSession を取得し、アプリケーションのシャットダウン時にはエンジンの pool がクローズされます。
app/main.py
session.commit の時点で終了します。セッションがクローズされると、コミットされていない処理はすべてロールバックされます。INSERT 時には、SQLAlchemy が生成された id と created_at を RETURNING で取得するため、create_todo で追加のクエリを発行する必要はありません。
実行と動作確認
アプリを起動します。DELETE リクエストは 204 No Content を返します。存在しない todo をリクエストすると、404 が返されます。
pg_stat_ssl にクエリを実行します。
guide_fastapi、public の順に展開して、todos テーブルをクリックします。隣にある alembic_version テーブルには、マイグレーション履歴が保存されています。todos テーブルには、削除されずに残った行が格納されています。
Ctrl+C でアプリを停止します。
PgBouncer の使用
このアプリは、asyncpg と SQLAlchemy のデフォルト設定のままで PgBouncer 経由で動作します。- asyncpg はすべてのクエリを名前付きのプロトコルレベルのプリペアドステートメントとして実行し、SQLAlchemy は接続ごとにそれらを最大 100 個までキャッシュします。同梱の PgBouncer はトランザクションプーリングモードでこれらのステートメントをサポートしており、asyncpg は SQL の
DEALLOCATEではなくプロトコルレベルのCloseメッセージでそれらを解放します。statement_cache_size=0やカスタムのprepared_statement_name_funcは不要です。それでも asyncpg のステートメントキャッシュを無効にする場合は、URL にprepared_statement_cache_size=0も追加して SQLAlchemy のキャッシュも無効にしてください。 search_pathなど、SETで行ったセッション設定は次のトランザクションに引き継がれず、他のクライアントに漏れる可能性があります。代わりに、トランザクション内でSET LOCALを使用してください。pg_stat_sslが報告するのは PgBouncer と Postgres 間の接続であるため、前述の TLS チェックはポート6432経由では(False, None)を返します。それでも、アプリから PgBouncer への接続は検証されています。誤った CA 証明書を指定した場合は、同様に失敗します。- Alembic はアドバイザリロックを取得せず、各
upgradeを 1 つのトランザクション内で実行するため、PgBouncer 経由でも動作します。ただし、時間のかかる移行には直接接続を使用するほうが適しています。