pgxpool はその接続プールです。このガイドでは、検証済みの TLS で Go サービスを ClickHouse Managed Postgres に接続し、golang-migrate の移行で todos テーブルを作成したうえで、net/http サーバーで作成・読み取り・更新・削除のリクエストを処理します。
前提条件
- Go 1.25 以降。本ガイドは Go 1.27.1、pgx 5.11.0、golang-migrate 4.20.1 で動作を確認しています。
- ClickHouse Cloud アカウント
- データベース作成用の
psql。CREATE DATABASEステートメントは SQL コンソールで実行することもできます。
ClickHouse Managed Postgres サービスを作成する
ClickHouse Cloud コンソールで New service をクリックし、Postgres を選択します。インスタンスは数分で利用可能になります。詳しい手順については、クイックスタートを参照してください。接続情報を取得する
サービスの左サイドバーで Connect をクリックします。Directly を選択したまま、Use SSL をオンにします。これで、url タブの接続 URL はポート5432 を使用し、末尾が sslmode=verify-full&sslrootcert=<service-name>-ca-certificate.pem になります。Download CA certificate をクリックします。後から Settings → CA Certificate でダウンロードすることもできます。この証明書はインスタンスごとに固有のものであるため、pgx はこれを使って、接続先が自分のサーバーであることを検証できます。
このガイドでは、移行とアプリのいずれも、ポート 5432 で Postgres に直接接続します。Go サービスは長時間稼働するプロセスであり、プロセス内で pgxpool がすでに接続の pool を保持しているため、別のプーラーを追加する必要はありません。サービスのインスタンスを多数実行していて、Postgres の上限を超えるクライアント接続が必要になる場合は、アプリ用に同梱の PgBouncer (via PgBouncer、ポート 6432) を使用してください。その場合は接続設定を 1 つ追加する必要があります。詳しくは PgBouncer を使用する を参照してください。
プロジェクトのセットアップ
Go モジュールを作成して pgx を追加し、pgx ドライバーを有効にしたmigrate CLI をインストールします。
-tags 'pgx5' フラグを指定すると、migrate CLI が pgx v5 ドライバーを組み込んだ状態でコンパイルされます。このドライバーは pgx5:// URL スキームを使用します。go install はバイナリを $GOBIN に配置します (GOBIN が設定されていない場合は $(go env GOPATH)/bin に配置されます) 。そのため、このディレクトリが PATH に含まれていることを確認してください。
接続を設定する
ダウンロードした CA 証明書をプロジェクトディレクトリに移動し、ファイル名をca-certificate.pem にリネームします。
アプリケーション用のデータベースを作成します。<PASSWORD> とホストは、Connect モーダルに表示される値に置き換えてください。
guide_go データベースを指しており、違いは URL スキームだけです。アプリケーションは DATABASE_URL を読み込み、migrate CLI は MIGRATE_URL を使用します。
libpq と同様に URL をパースします。sslmode=verify-full を指定すると、sslrootcert から CA ファイルを読み込み、証明書チェーンとホスト名の両方を検証するため、TLS 関連のコードを記述する必要はありません。sslrootcert のパスは、コマンドを実行するディレクトリを基準とした相対パスとして解決されます。パスワードに @、/、# などの特殊文字が含まれる場合は、URL エンコードしてください。
移行でスキーマを作成する
移行ファイルを作成します:up 移行に次の内容を記述します:
migrations/000001_create_todos.up.sql
down 移行は次のとおりです。
migrations/000001_create_todos.down.sql
migrate は現在のバージョンを schema_migrations テーブルに記録し、以降の実行ではそれより新しい移行のみを適用します。migrations ディレクトリはコードと一緒にコミットしてください。
migrate は必ず直接接続 (ポート 5432) 経由で実行してください。migrate は実行中に session レベルのアドバイザリーロックを保持し、2 つのデプロイメントが同時に移行を実行できないようにしています。PgBouncer を経由すると、ロックとロック解除が別々の Postgres 接続上で実行されることがあります。その場合はロックが保持されたままとなり、以降の migrate の実行がハングするか、timeout: can't acquire database lock というエラーで失敗します。データベースにクエリを実行する
db.go を作成します。このファイルは DATABASE_URL から pool を作成し、起動時に Postgres へ session で使用されている TLS バージョンを問い合わせることで、接続を確認します。
db.go
todos.go を作成します。各ハンドラーは pool 上でパラメータ化されたクエリを 1 つ実行し、pgx.CollectRows と pgx.CollectExactlyOneRow がカラム名をもとに行を Todo 構造体へスキャンします。
todos.go
$1) として渡すことで SQL インジェクションを防げます。pgx は引数をクエリテキストとは別に送信します。pool.Query から返されるエラーを個別に確認する必要はありません。pgx.CollectRows と pgx.CollectExactlyOneRow がそのエラーを返すためです。
main.go を作成します。このファイルでは、プロセス全体で 1 つの pool を作成し、ポート 3114 でルートを提供し、Ctrl+C が押されたときに pool をクローズします。既存のアプリケーションに組み込む場合は、代わりに起動時に newPool を 1 回呼び出し、既存の http.ServeMux に対して registerTodoRoutes を呼び出してください:
main.go
実行と検証
pgxpool の残りの依存関係を go.mod と go.sum に追加してから、サーバーを起動します。
TLSv1.3 はサーバー側の pg_stat_ssl から取得した値なので、セッションが暗号化されていることを確認できます。sslrootcert に別の証明書を指定すると、サーバーは起動せずに x509: certificate signed by unknown authority というログを出力します。このことから、証明書が実際に検証されていることがわかります。
別の端末で todo を 2 件作成し、1 件目を完了にして、2 件目を削除します。
PATCH リクエストを実行すると、更新された行が返されます。
http://localhost:3114/todos を開くと、未完了の todo が一覧表示されます。pgx は timestamptz の値をローカルのタイムゾーンで返します。レスポンスは次のようになります。
Shutting down と出力され、pool がクローズされます。
コンソールでデータを確認するには、サービスの左サイドバーで SQL コンソール を開き、guide_go、public の順に展開して、todos テーブルをクリックします。隣にある schema_migrations テーブルには、移行のバージョンが格納されています。todos テーブルには、残った行が入っています。
PgBouncer を使用する
アプリのクエリを同梱の PgBouncer 経由で送信するには、Connect モーダルで via PgBouncer を選択し、DATABASE_URL のポートを 6432 に変更したうえで、default_query_exec_mode=cache_describe を追加します。MIGRATE_URL はポート 5432 のままにしてください。
default_query_exec_mode を読み取り、サーバーには送信しません。コードの変更は不要で、同じ CA 証明書をそのまま使用できます。起動ログには TLS: none と表示されるようになりますが、これは pg_stat_ssl が PgBouncer 自身から Postgres への接続について報告しているためです。アプリから PgBouncer への接続は引き続き検証されており、誤った CA 証明書を使用した場合は同様に失敗します。
PgBouncer はトランザクションプーリングモードで動作するため、トランザクションごとに異なる Postgres 接続上で実行される可能性があります。
pool.Beginまたはpgx.BeginFuncを使用したトランザクションは通常どおり動作します。SETで行ったセッション設定は接続に保持されず、他のクライアントに漏れる可能性があります。代わりに、トランザクション内でSET LOCALを使用してください。また、PgBouncer は接続 URL 内のsearch_pathをunsupported startup parameter: search_pathというエラーで拒否します。代わりに、テーブル名をスキーマ名で修飾してください。- SQL レベルの
PREPAREとEXECUTE、LISTEN、およびセッションアドバイザリーロックは動作しません。