todos リソースを提供します。
前提条件
- Elixir 1.15 以降および Erlang/OTP。本ガイドは Elixir 1.20.4、Erlang/OTP 29、Phoenix 1.8.15、
ecto_sql3.14.0、Postgrex 0.22.4 の環境で動作を確認しています。 - ClickHouse Cloud アカウント
ClickHouse Managed Postgres サービスを作成する
ClickHouse Cloud コンソールで New service をクリックし、Postgres を選択します。数分でインスタンスが利用可能になります。詳しい手順については、クイックスタートを参照してください。接続情報を取得する
サービスを開き、左サイドバーの Connect をクリックします。表示されるモーダルにはユーザー名、パスワード、サーバー、ポートが表示されるほか、Directly / via PgBouncer を切り替えるトグルがあります。Directly を選択したまま、Use SSL をオンにします。パスワードとサーバーのホスト名をコピーしてから、Download CA certificate をクリックします。 CA 証明書はサービスごとに固有です。後から Settings → CA Certificate でダウンロードすることもできます。 直接接続と PgBouncer のどちらを使うべきか? Phoenix アプリは長時間稼働するプロセスであり、Ecto は独自の接続プール (pool_size、デフォルトは 10) を保持します。本ガイドでは、ポート 5432 で Postgres に直接接続します。多数のアプリインスタンスを合わせた接続数が Postgres の接続数上限に近づく場合は、PgBouncer の使用を参照してください。
Phoenix アプリを作成する
Phoenix プロジェクトジェネレーターをインストールし、アプリを作成します。デフォルトのデータベースは Postgres です。データベース接続を設定する
ダウンロードした CA 証明書をアプリのpriv ディレクトリに移動し、ca-certificate.pem にリネームします。ダウンロードしたファイルの名前は、your-service-ca-certificate.pem のようにサービス名に基づいています。
priv 内のファイルはリリースにも含まれます。
config/dev.exs で、Hello.Repo の設定にある username、password、hostname、database の各行を url と ssl に置き換えます。
config/dev.exs
ssl がキーワードリストの場合、Postgrex は証明書の検証 (verify: :verify_peer) とホスト名のチェックを有効にし、URL のホスト名を TLS のサーバー名として送信します。指定が必要なのは CA 証明書だけです。ssl: true では接続できません。この設定ではサーバー証明書がオペレーティングシステムの CA ストアと照合されますが、そこにはサービスの CA が含まれていないためです。
新しいデータベース名を使用して、DATABASE_URL にサービスの接続先を設定します。このガイドでは guide_phoenix を使用します。
mix ecto.create は、CREATE DATABASE を実行する際、同じ TLS 設定を使用してデフォルトの postgres データベースに接続します。
JSON リソースを生成して移行を実行する
todos 用のコンテキスト、スキーマ、移行、JSON コントローラーを生成します。lib/hello_web/router.ex で /api スコープのコメントアウトを解除し、そこにリソースを追加します。
lib/hello_web/router.ex
schema_migrations テーブルに記録するため、2 回目以降の呼び出しでは新しい移行のみが実行されます。
実行と検証
接続が暗号化されていることを確認します。次のクエリは、現在の接続に関する情報をpg_stat_ssl から読み取ります。
3116 でサーバーを起動します。
PATCH リクエストを実行すると、更新された todo が返されます。
http://localhost:3116/api/todos を開くと、残りの todo の一覧が表示されます。
guide_phoenix、public の順に展開して、todos テーブルをクリックします。public スキーマには schema_migrations も含まれています。
本番環境の設定
本番環境では、Phoenix はデータベース設定をconfig/runtime.exs から読み込みます。if config_env() == :prod do ブロック内にある Hello.Repo の設定で、# ssl: true, の行を CA 証明書のパスに置き換えます。
config/runtime.exs
Application.app_dir はビルドまたはリリース内のパスを解決するため、アプリがどのディレクトリから起動されても設定は正しく機能します。デプロイ先の環境で DATABASE_URL を設定してください。こちらも、クエリ文字列は付けないでください。
アプリを SQLite で開始していた場合は、config/runtime.exs は代わりに DATABASE_PATH を読み込みます。database_path 変数とそれに対応する config :hello, Hello.Repo の呼び出しを、次の内容に置き換えてください。
config/runtime.exs
--database sqlite3 で作成したアプリでは、lib/<your_app>/application.ex 内の Ecto.Migrator 子プロセスにより、リリースの起動時に未適用の移行も実行されます。この仕組みは Postgres でもそのまま機能します。
PgBouncer の使用
アプリのインスタンス全体で開かれる直接接続の数が多すぎる場合は、代わりに付属の PgBouncer を経由して接続してください。Connect モーダルで via PgBouncer を選択し、DATABASE_URL でポート 6432 を指定します。さらに、config/dev.exs と config/runtime.exs の両方で、repo の設定に prepare: :unnamed を追加します。
config/dev.exs
-
プリペアドステートメント: Ecto はデフォルトですべてのクエリを名前付きプリペアドステートメントとして実行し、同梱の PgBouncer は名前付きステートメントをサポートしています。Postgrex はステートメントをプロトコルレベルの
Closeメッセージで解放し、SQL のDEALLOCATEは使用しません。それでも、デフォルトのprepare: :namedのままでは、PgBouncer 経由で次の 2 つの問題が発生します。- SQL テキストが約 4 KB のクエリが、タイムアウトするまでハングすることがあります。この場合、呼び出し元には約 1 分後に
DBConnection.ConnectionErrorが返されます。 - 移行によってカラムの型が変更された後、そのカラムを読み取るクエリが
cached plan must not change result typeで失敗し続けます。PgBouncer がサーバー上のプリペアドステートメントを再利用するため、アプリを再起動しても解消されません。
prepare: :unnamedを使用すると、Postgrex は実行のたびに各クエリを準備し直すため、両方の問題が解消されます。ただし、クエリごとにネットワークの往復が 1 回増えます。 - SQL テキストが約 4 KB のクエリが、タイムアウトするまでハングすることがあります。この場合、呼び出し元には約 1 分後に
-
移行: Ecto のデフォルトの移行ロック (トランザクション内で
schema_migrationsテーブルをロックする方式) を使用すれば、mix ecto.migrateは PgBouncer 経由でも動作します。PgBouncer 接続に対してmigration_lock: :pg_advisory_lockは設定しないでください。セッションレベルのアドバイザリロックが別の Postgres 接続上で解放されると、failed to release advisory lockで失敗してロックが保持されたままになり、ポート5432での実行も含め、以降の実行がすべてそのロックを待ち続けることになります。直接接続で移行を実行するには、mix ecto.migrateコマンドの実行時に5432を指すDATABASE_URLを設定してください。 -
セッション状態:
after_connectコールバック内のSET search_pathなどのSET文は、現在のトランザクションで使用されている Postgres 接続にのみ適用されます。Postgrex のparametersオプションでsearch_pathを指定すると、unsupported startup parameter: search_pathで失敗します。Postgrex.Notificationsが使用するLISTENとNOTIFYには直接接続が必要です。
トラブルシューティング
CLIENT ALERT: Fatal - Unknown CA:cacertfileがサービスの CA 証明書を指していないか、ssl: trueが設定されています。Settings → CA Certificate から証明書をダウンロードし直してください。CA はサービスごとに異なります。CLIENT ALERT: Fatal - Bad Certificate(hostname_check_failedを伴う) :DATABASE_URLのホストがサーバー証明書と一致していません。IP アドレスを指定した場合などに発生します。Connect モーダルに表示されるホスト名を使用してください。Invalid CA certificate file ... no such file or directory:cacertfileのパスが正しくありません。priv/ca-certificate.pemが存在するか確認してください。
次のステップ
- 接続: 接続文字列、PgBouncer、TLS
- 設定:
max_connectionsなどの Postgres および PgBouncer のパラメータ - 読み取りレプリカ: Ecto のレプリカリポジトリを使用して読み取りクエリをレプリカに振り分ける
- ClickHouse への同期: Phoenix のテーブルを ClickHouse にレプリケートして分析に活用する