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 会使用相同的 TLS 设置连接到默认的 postgres 数据库,以执行 CREATE DATABASE。
生成 JSON 资源并运行迁移
为 todos 生成 context、schema、迁移和 JSON controller:lib/hello_web/router.ex 中,取消对 /api scope 的注释,并将该资源添加到其中:
lib/hello_web/router.ex
schema_migrations 表中记录已应用的迁移,之后再次调用时只会运行新的迁移。
运行并验证
检查连接是否已加密。以下查询会读取当前连接的pg_stat_ssl 信息:
3116 上启动服务器:
PATCH 请求会返回更新后的待办事项:
http://localhost:3116/api/todos,即可列出剩余的待办事项:
guide_phoenix 和 public,然后点击 todos 表。public schema 中还包含 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消息释放语句,从不使用 SQLDEALLOCATE。尽管如此,在默认的prepare: :named设置下,通过 PgBouncer 连接时仍会出现两个问题:- SQL 文本长度约为 4 KB 的查询可能会一直挂起直至超时,调用方会在大约一分钟后收到
DBConnection.ConnectionError。 - 迁移更改某列的类型后,读取该列的查询会持续报错
cached plan must not change result type,即使重启应用也无济于事,因为 PgBouncer 会在服务器上复用其预处理语句。
prepare: :unnamed后,Postgrex 每次运行查询时都会重新预处理,上述两个问题即可解决,代价是每个查询多一次网络往返。 - SQL 文本长度约为 4 KB 的查询可能会一直挂起直至超时,调用方会在大约一分钟后收到
-
迁移:使用 Ecto 默认的迁移锁时,
mix ecto.migrate可以通过 PgBouncer 正常运行,该锁会在事务内锁定schema_migrations表。不要为 PgBouncer 连接设置migration_lock: :pg_advisory_lock。会话级 advisory 锁可能会在另一个 Postgres 连接上被释放,导致报错failed to release advisory lock,且该锁会一直被持有,后续运行 (包括通过端口5432的运行) 都会因等待该锁而阻塞。若要通过直连方式运行迁移,请为mix ecto.migrate命令设置指向5432端口的DATABASE_URL。 -
会话状态:
SET语句 (例如在after_connect回调中执行的SET search_path) 仅对当前事务所用的 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
- 设置:Postgres 和 PgBouncer 参数,例如
max_connections - 只读副本:借助 Ecto 副本仓库将读请求路由到副本
- 同步到 ClickHouse:将 Phoenix 表复制到 ClickHouse 中进行分析