Show HN: PgDog – 在不更改应用的情况下扩展 Postgres
Source: Hacker News
介绍
嗨,HN!我们是 Lev 和 Justin,PgDog(https://pgdog.dev/)的作者,它是一个用于 PostgreSQL 的连接池、负载均衡器和数据库分片工具。如果你构建流量很大的应用,你会知道最先崩溃的往往是数据库。我们通过一个网络代理来解决这个问题,且无需修改应用代码或进行数据库迁移。
我们去年发布的帖子:https://news.ycombinator.com/item?id=44099187
Production Update
最重要的更新:我们已经进入生产环境。分片被大量使用,直接对分片的查询(每个查询对应一个分片)几乎一直在工作。跨分片(或多数据库)查询仍在进行中,但我们正在取得进展。
Features
聚合函数
聚合函数如 COUNT()、MIN()、MAX()、AVG()、STDDEV() 和 VARIANCE() 可以在不重构应用的情况下直接使用。PgDog 在传输过程中计算聚合,并透明地重写查询以获取任何缺失的信息。例如,多数据库的平均值计算需要总行数来计算原始的求和。PgDog 会在查询中添加 COUNT()(如果尚未存在),并从发送给应用的行中移除它。
排序和分组
只要引用的列出现在结果集中,排序和分组(包括 DISTINCT)都能正常工作。支持超过 10 种数据类型,例如 timestamp with time zone、所有整数类型、varchar 等。
跨分片写入
跨分片写入(包括模式更改 CREATE/DROP/ALTER)现在是原子操作,并通过两阶段提交在所有分片之间同步。PgDog 在内部跟踪事务状态,如果第一阶段失败则回滚事务。无需对 ORM 进行猴子补丁:PgDog 拦截 COMMIT 语句并执行 PREPARE TRANSACTION,随后执行 COMMIT PREPARED。
全局分片表
全局分片表(在所有分片上复制或镜像)支持原子读写。这一点很重要,因为大多数数据库无法完全分片,需要在所有实例之间保持一些公共数据同步。
多元组插入
多元组插入,例如:
INSERT INTO table_x VALUES ($1, $2), ($3, $4);
会被 PgDog 的查询重写器拆分,并自动分发到相应的分片。此功能兼容 Prisma、Sequelize 等 ORM,允许它们在无需代码更改的情况下工作。
分片键变更
当分片键被更新时,PgDog 会将 UPDATE 重写为三个查询(SELECT、INSERT 和 DELETE),以在分片之间移动该行。如果你使用 Citus(PostgreSQL 的分片扩展),这种做法可能会感兴趣。
跨分片唯一序列
如果你更倾向于使用整数而非 UUID 作为主键,PgDog 在代理内部提供跨分片唯一序列。它使用系统时钟(以及其他少量输入),可以像普通 PostgreSQL 函数一样调用:
INSERT INTO my_table (id, created_at) VALUES (pgdog.unique_id(), now());
该序列单调递增,最高每秒可生成 400 万个数字,范围覆盖 69.73 年,免除了立即迁移到 UUIDv7 的需求。
负载均衡
没有良好的负载均衡器,分片几乎无法工作。PgDog 可以监控副本,并在故障转移期间将写流量切换到提升为主的实例。这在托管的 PostgreSQL 服务(RDS/Aurora、Azure PostgreSQL、GCP Cloud SQL)上同样适用,因为它仅通过 SELECT pg_is_in_recovery() 轮询每个实例。当前尚不支持主选举,因此在使用 Patroni 的自托管环境中应继续使用现有的选举机制,但不再需要在数据库前面放置 HAProxy。
负载均衡器还处理诸如 SELECT FOR UPDATE 以及带有 INSERT/UPDATE 的 CTE 等边缘情况。如果你更喜欢在代码中管理读写分离,仍然可以通过运行时提供 PgDog 提示的方式实现:使用连接参数(-c pgdog.role=primary)、SET 语句或查询注释。
连接池与事务管理
PgDog 能自动回滚未完成的事务,并对部分已发送的查询进行排空/重新同步,帮助保持与数据库的连接。如果在应用崩溃后出现连接风暴导致某个 Postgres 实例 CPU 占用率飙升至 100%,PgDog 可以缓解此问题。排空通过接收并丢弃被放弃查询的行,然后通过 PostgreSQL 协议发送 Sync 消息来实现,该消息会清除查询上下文并将连接恢复到正常状态。
开源与文档
PgDog 是开源的,欢迎任何形式的贡献和反馈。所有功能均可配置,可随时开启或关闭,让您可以按自己的节奏采用它。
感谢阅读,祝您玩得开心!