Skip to main content
Django は、ORM とマイグレーション機能を標準で備えた Python の Web フレームワークで、psycopg 3 ドライバーを介して PostgreSQL と通信します。このガイドでは、新規の Django プロジェクトを PgBouncer 経由で ClickHouse Managed Postgres に接続し、マイグレーションで todos テーブルを作成したうえで、小規模な JSON ビューで作成・読み取り・更新・削除のリクエストを処理します。すべての接続で、証明書の完全な検証を伴う TLS を使用します。

前提条件

  • Python 3.12 以降 (Django 6 の要件) 。本ガイドは Python 3.13、Django 6.1.1、psycopg 3.3.6 で動作確認しています。
  • ClickHouse Cloud アカウント
  • データベース作成用の psql。CREATE DATABASE ステートメントは SQL コンソールで実行することもできます。

ClickHouse Managed Postgres サービスを作成する

ClickHouse Cloud コンソールで New service をクリックし、Postgres を選択します。インスタンスは数分で利用可能になります。詳しい手順については、クイックスタートを参照してください。

接続情報を取得する

サービスの左サイドバーで Connect をクリックします。表示されるモーダルにはユーザー名、パスワード、サーバー、ポートが表示され、Directly / via PgBouncer を切り替えるトグルもあります。 このガイドでは、同梱の PgBouncer を経由し、ポート 6432 で Django を接続します。本番環境の Django アプリケーションは通常、複数のマシン上で複数の worker プロセスを実行し、各プロセスがそれぞれ接続を開きます。PgBouncer はトランザクションプーリングモードで動作し、これらすべてのプロセスが少数の Postgres 接続を共有できるようにします。トランザクションプーリングを利用するには Django 側でいくつかの設定が必要です。これらの設定はこのガイドに含まれており、詳細は Django 向けの PgBouncer 設定で説明します。 via PgBouncer を選択すると、プーリング用の接続情報が表示されます。ポートが 6432 に変わり、接続文字列に sslmode=verify-full が含まれるようになります。 モーダルで Download CA certificate をクリックします。Settings → CA Certificate からもダウンロードできます。この証明書はインスタンスごとに固有のものであるため、ドライバーはこれを使って接続先が自分のサーバーであることを検証できます。

プロジェクトをセットアップする

仮想環境付きのプロジェクトディレクトリを作成し、Django と psycopg 3 をインストールしてから、todos アプリを含む Django プロジェクトを作成します。
psycopg[binary] には最新の libpq が同梱されているため、ローカルに PostgreSQL をインストールする必要はありません。
既存のアプリに追加する場合上記のコマンドは省略してください。プロジェクトの仮想環境で pip install "psycopg[binary]" を実行してから、接続を設定するに進みます。この手順で、SQLite 用の DATABASES 設定を置き換えます。SQLite のデータを移行するには、次のように操作します。
  • DATABASES を変更する前に、python manage.py dumpdata --natural-foreign --natural-primary -e contenttypes -e auth.permission -o data.json を実行します。
  • python manage.py migrate を実行した後、python manage.py loaddata data.json を実行します。

接続を設定する

ダウンロードした CA 証明書をプロジェクトディレクトリ内の manage.py と同じ階層に移動し、ファイル名を ca-certificate.pem にリネームします。 アプリ用のデータベースを作成します。<PASSWORD> とホストは、Connect モーダルに表示される値に置き換えてください。
ホストとパスワードをコードに直接記述しないよう、環境変数として設定します:
mysite/settings.py を開き、from pathlib import Path の下に import os を追加したうえで、DATABASES 設定を次の内容に置き換えます。
mysite/settings.py
Django は OPTIONS 内のオプションを psycopg に渡し、psycopg はそれらを libpq に引き渡します。sslmode を verify-full に設定すると、libpq はサーバー証明書がサービスの CA によって署名されているか、またホスト名と一致するかを検証します。BASE_DIR を使うと証明書のパスが絶対パスになるため、Django をどのディレクトリから起動してもこの設定は機能します。
sslrootcert が誤った CA を指している場合、接続は SSL error: certificate verify failed で失敗します。sslrootcert を指定しない場合は、libpq がデフォルトの CA の場所にフォールバックするため、root certificate file ".../.postgresql/root.crt" does not exist で失敗します。ホスト名ではなく IP アドレスで接続した場合は、server certificate for "..." does not match host name で失敗します。
アプリケーションが dj-database-url を使って DATABASE_URL を読み込む場合、URL のクエリ文字列に含まれる sslmode と sslrootcert は OPTIONS に渡されます。同じ設定にするには、dj_database_url.config(conn_max_age=600, conn_health_checks=True, disable_server_side_cursors=True) を呼び出してください。URL 内で sslrootcert を相対パスで指定した場合、そのパスは Django を起動したディレクトリを基準に解決されます。

モデルを作成して移行を実行する

mysite/settings.py の INSTALLED_APPS に todos アプリを追加します。
mysite/settings.py
todos/models.py に Todo モデルを定義します。
todos/models.py
モデルの移行を作成し、すべての移行を適用します。
migrate は todos_todo に加えて、auth_user や django_session など Django の組み込みアプリ用のテーブルも作成し、適用済みの移行を django_migrations に記録します。既存のアプリの場合は todos モデルの作成を省略し、python manage.py migrate だけを実行してください。これで、アプリのすべてのテーブルが新しいデータベースに作成されます。 migrate は session レベルのアドバイザリロックを取得しないため、PgBouncer 経由でも動作します。ただし、同時実行を防ぐ仕組みもないため、移行はデプロイ時の 1 ステップなど、1 か所からのみ実行するようにしてください。

JSON エンドポイントを追加する

todos/views.py の内容を、Django ORM で todo の作成・一覧取得・更新・削除を行う 2 つのビューに置き換えます。
todos/views.py
mysite/urls.py を次のように置き換えて、/todos と /todos/<id> をそれぞれのビューにルーティングします。
mysite/urls.py

実行と確認

開発サーバーを起動します。
別の端末で todo を 2 つ作成し、1 つ目を完了済みにして、2 つ目を削除します:
PATCH リクエストを実行すると、次のように更新された行が返されます。
ブラウザで http://localhost:3111/todos を開くと、残りの todo を一覧表示できます。レスポンスは次のようになります:
コンソールでデータを確認するには、サービスの左サイドバーで SQL コンソール を開き、guide_django、public の順に展開して、todos_todo テーブルをクリックします。テーブルには、残った行が表示されます。

Django 向けの PgBouncer 設定

PgBouncer はトランザクションプーリングモードで動作します。つまり、各トランザクション (またはトランザクション外の各ステートメント) は、それぞれ異なる Postgres 接続上で実行される可能性があります。これが上記の設定やその他の Django の機能にどう影響するかを以下に説明します。
  • DISABLE_SERVER_SIDE_CURSORS: QuerySet.iterator は、サーバー側カーソルを介して行を chunk 単位で読み込みます。トランザクション外では、Django はカーソルを WITH HOLD として宣言し、各 chunk を個別にフェッチします。そのため PgBouncer 経由では、フェッチがそのカーソルを持たない Postgres 接続に割り当てられ、cursor "_django_curs_..." does not exist というエラーで失敗することがあります。この問題は他のクライアントが同時に pool を使用している場合にのみ発生するため、開発環境では表面化しないことがよくあります。この設定を有効にすると、.iterator() は通常のカーソルを使用し、psycopg は結果全体をメモリに読み込みます。非常に大きなテーブルをストリーミングする場合は、この設定を無効にしたうえで、ポート 5432 の直接接続を使ってそのコードを実行してください。
  • CONN_MAX_AGE: デフォルト (0) では、Django はリクエストごとに新しい接続を開きます。TLS 接続の確立には、PgBouncer 経由でも直接接続でも複数回のネットワークラウンドトリップが必要なため、接続を再利用するとリクエストが大幅に高速化されます。ノート PC から 4 つの Gunicorn worker を使用して行ったテストでは、CONN_MAX_AGE を 600 に設定すると、リクエスト時間の中央値が約 670 ms から 95 ms に短縮されました。PgBouncer 経由の場合、開いたままの Django の接続はトランザクション間で Postgres 接続を保持しないため、永続接続によって max_connections を使い切ることはありません。CONN_HEALTH_CHECKS を有効にすると、Django は各リクエストの開始時に再利用する接続をチェックし、切断されていれば再接続します。
  • 接続プール: Django の組み込みプール (OPTIONS 内の "pool": True。pip install "psycopg[binary,pool]" が必要) でも同程度の高速化が得られ、PgBouncer 経由でも動作します。ただし CONN_MAX_AGE を 0 に設定する必要があり、プロセスごとに最低 4 つの接続を開いたままにします。PgBouncer を使用する場合、ほとんどのアプリケーションでは CONN_MAX_AGE で十分です。スレッド型サーバーや ASGI サーバーではプールの利用を検討してください。
  • プリペアドステートメント: 設定は不要です。Django はデフォルトで psycopg の prepare_threshold を None に設定し、クエリパラメータをクライアント側でバインドするため、サーバー側のプリペアドステートメントは作成されません。OPTIONS で "server_side_binding": True を有効にして "prepare_threshold" を設定した場合でも、プリペアドステートメントは PgBouncer 経由で動作します。これは、psycopg[binary] に libpq 18 が同梱されており、プロトコルレベルの Close メッセージでステートメントを解放するためです。libpq 16 以前に対してビルドされた psycopg では、トランザクション内でステートメントを解放すると prepared statement "_pg3_0" does not exist というエラーで失敗します。バージョンを確認するには、python -c "import psycopg; print(psycopg.pq.version())" を実行してください。170000 以降が必要です。
  • セッション状態: SET search_path などの SET ステートメントは、それを実行した Postgres 接続にのみ適用されます。そのため、後続のクエリには反映されない可能性がある一方で、他のクライアントに影響する可能性があります。PgBouncer は Django の "options": "-c search_path=..." 設定を unsupported startup parameter in options: search_path というエラーで拒否します。代わりに、ALTER DATABASE guide_django SET search_path = myschema, public; のように、データベース側でデフォルトを設定してください。SET ROLE を実行する assume_role オプションは使用しないでください。Django 自体の TIME_ZONE の処理と isolation_level オプションは、PgBouncer 経由でも問題なく動作します。
PgBouncer を使わずに直接接続する場合は、PORT を 5432 に設定し、DISABLE_SERVER_SIDE_CURSORS を削除してください。この場合、各 Django worker がそれぞれ独自の Postgres 接続を保持するため、worker の総数が max_connections を超えないようにしてください。

次のステップ

最終更新日 2026年10月14日