Skip to main content
pgx は Go で最も広く使われている PostgreSQL ドライバーであり、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 に含まれていることを確認してください。
既存のアプリに追加する場合mkdir と go mod init は不要です。対象のモジュール内で go get github.com/jackc/pgx/v5 と上記の go install コマンドを実行し、接続を設定するに進んでください。コードで database/sql を使用している場合は、stdlib.OpenDBFromPool を使うと同じ pool を *sql.DB としてラップできるため、既存のクエリを段階的に移行できます。その際は、? プレースホルダーを $1、$2 のように書き換えてください。

接続を設定する

ダウンロードした CA 証明書をプロジェクトディレクトリに移動し、ファイル名を ca-certificate.pem にリネームします。 アプリケーション用のデータベースを作成します。<PASSWORD> とホストは、Connect モーダルに表示される値に置き換えてください。
シェルで 2 つの環境変数を設定します。どちらも同じ guide_go データベースを指しており、違いは URL スキームだけです。アプリケーションは DATABASE_URL を読み込み、migrate CLI は MIGRATE_URL を使用します。
pgx は libpq と同様に URL をパースします。sslmode=verify-full を指定すると、sslrootcert から CA ファイルを読み込み、証明書チェーンとホスト名の両方を検証するため、TLS 関連のコードを記述する必要はありません。sslrootcert のパスは、コマンドを実行するディレクトリを基準とした相対パスとして解決されます。パスワードに @、/、# などの特殊文字が含まれる場合は、URL エンコードしてください。
必ず sslmode=verify-full を設定してくださいsslmode を指定しない場合、pgx は prefer を使用します。この場合、sslrootcert を設定していてもサーバー証明書は検証されません。sslmode=verify-full を指定していても CA ファイルが誤っていると、pgx は x509: certificate signed by unknown authority エラーで失敗します。また、ファイルが存在しない場合は unable to read CA file エラーで失敗します。ホスト名ではなく IP アドレスで接続した場合は、x509: cannot validate certificate for <IP> because it doesn't contain any IP SANs エラーで失敗します。

移行でスキーマを作成する

移行ファイルを作成します:
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
HTTP ハンドラーを記述した todos.go を作成します。各ハンドラーは pool 上でパラメータ化されたクエリを 1 つ実行し、pgx.CollectRows と pgx.CollectExactlyOneRow がカラム名をもとに行を Todo 構造体へスキャンします。
todos.go
値を SQL 文字列に埋め込んでフォーマットするのではなく、引数 ($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 の値をローカルのタイムゾーンで返します。レスポンスは次のようになります。
Ctrl+C でサーバーを停止します。ログに 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 のままにしてください。
pgx は URL から default_query_exec_mode を読み取り、サーバーには送信しません。コードの変更は不要で、同じ CA 証明書をそのまま使用できます。起動ログには TLS: none と表示されるようになりますが、これは pg_stat_ssl が PgBouncer 自身から Postgres への接続について報告しているためです。アプリから PgBouncer への接続は引き続き検証されており、誤った CA 証明書を使用した場合は同様に失敗します。
PgBouncer 経由では pgx のデフォルトのクエリモードを使用しないでくださいデフォルトでは、pgx は各クエリを接続ごとに一度だけ名前付きステートメントとして準備し、その際に返されたカラム型を再利用します。一方 PgBouncer は、クエリを実行した Postgres 接続上でこれらのステートメントを準備し、クライアント間で共有します。そのため、移行によってカラムの型が変更されたり、SELECT * でクエリしているテーブルにカラムが追加されたりすると、PgBouncer 経由のクエリがアプリを再起動しても cached plan must not change result type で失敗し続けることがあります。さらに pgx 5.9 以降では、エラーを出さずに誤った値を返すことがあります。たとえば、bigint カラムを double precision に変更すると、42.5 が 4631178160564600832 のように float64 値の生のビット列として返されます。cache_describe はパラメータとカラム型のみをキャッシュし、すべてのクエリを名前なしステートメントとして 1 回のラウンドトリップで実行し、各結果とともにサーバーが返すカラム型を使って行をデコードするため、この問題の影響を受けません。exec も使用できます。一方、describe_exec は PgBouncer 経由で使用しないでください。各クエリが 2 回のラウンドトリップに分割され、PgBouncer がそれぞれを別々の Postgres 接続に送る可能性があります。なお、statement_cache_capacity=0 を指定する必要はありません。
PgBouncer はトランザクションプーリングモードで動作するため、トランザクションごとに異なる Postgres 接続上で実行される可能性があります。
  • pool.Begin または pgx.BeginFunc を使用したトランザクションは通常どおり動作します。
  • SET で行ったセッション設定は接続に保持されず、他のクライアントに漏れる可能性があります。代わりに、トランザクション内で SET LOCAL を使用してください。また、PgBouncer は接続 URL 内の search_path を unsupported startup parameter: search_path というエラーで拒否します。代わりに、テーブル名をスキーマ名で修飾してください。
  • SQL レベルの PREPARE と EXECUTE、LISTEN、およびセッションアドバイザリーロックは動作しません。

次のステップ

  • 接続: 接続文字列、PgBouncer、TLS
  • 設定: max_connections などの Postgres および PgBouncer のパラメータ
  • 読み取りレプリカ: 2 つ目の pgxpool.Pool を使用して読み取り専用クエリをレプリカに送信する方法
  • セキュリティ: IP アクセスリストとプライベートネットワーキング