分片Postgres查询的生命周期

分片Postgres查询的生命周期

💡 原文英文,约5500词,阅读约需20分钟。
📝

内容提要

文章以一条跨四个分片的Postgres查询为例,讲解分片数据库如何伪装成单一Postgres:路由器用Go实现认证、协议解析和查询规划,通过哈希分片键定位数据,再以散播-收集方式从各分片取回数据,并在路由器内完成哈希连接;同时说明连接池、执行引擎及按customer_id分片可使连接本地化,避免跨分片连接。

🔎

延伸解读

分片键选择决定连接代价

文章示例中,customers 按 id 分片,orders 也按 id 分片,导致同一客户的订单散落在多个分片,连接必须在路由器内完成。若将 orders 改为按 customer_id 分片,并与 customers 使用相同的哈希映射,则每个客户的订单与其客户行位于同一分片,Postgres 可本地执行连接。这样路由器只需向四个分片发送完整连接查询,无需构建客户哈希表,分片请求从八次降为四次。

路由器内哈希连接与内存溢出处理

当连接无法下推时,路由器选择哈希连接:先散播查询获取 customers 行,在内存中构建以 id 为键的哈希表,再散播查询获取符合条件的 orders 行,并用 customer_id 探测哈希表。若哈希表超出内存预算,路由器会按连接键将两侧数据分区并溢写到磁盘,然后逐分区加载客户数据并与对应订单分区连接,避免一次性占用过多内存。

连接池与 sidecar 的会话管理

为应对 Postgres 每连接一进程的架构,路由器不直接连接 Postgres,而是通过 sidecar 共享连接池。sidecar 复用连接前会先协调会话设置,再验证角色身份,防止角色污染。路由器与 sidecar 之间使用长生命周期的双向 gRPC 流,将 Parse、Bind、Execute、Sync 等扩展协议消息封装在 protobuf 请求中发送,从而在多个路由器间共享后端连接,减少空闲进程开销。

评估引擎处理跨分片聚合

对于 AVG 这类聚合,单个分片无法计算全局平均值,直接平均各分片结果也会出错。路由器的评估引擎将 AVG 重写为 SUM 和 COUNT,下发到各分片局部计算,再合并各分片的和与计数,最后在路由器内相除得到正确平均值。对于输出列已由分片以正确格式返回的查询,路由器可直接复制编码字节,避免解码再编码的开销。

Q&A

分片Postgres如何让应用感觉像在连接单个Postgres?

路由器在Go中实现了完整的Postgres认证交换(包括SCRAM-SHA-256)、线协议解析和分片感知的查询规划,对应用伪装成单个Postgres,管理认证、连接和查询分发。

在分片Postgres中,一条跨分片查询的规划过程是怎样的?

路由器先解析SQL生成AST,然后结合从权威分片获取的数据库模式和从etcd获取的数据拓扑,构建查询计划树。计划中为每个表创建Route节点,决定散播-收集查询,并选择连接算法(如哈希连接)在路由器内完成跨分片连接。

为什么按主键分片会导致跨分片连接,如何避免?

按orders.id分片会使同一客户的订单分散在不同分片,而客户行只在一个分片,导致连接必须在路由器内进行。改为按customer_id分片orders,并使用与customers.id相同的哈希和分片映射,可使每个客户及其订单共置,Postgres能在本地执行连接,减少分片请求。

路由器如何执行跨分片哈希连接?

路由器先向所有分片散播查询获取customers行,在内存中构建以customer ID为键的哈希表;然后向所有分片散播查询获取符合条件的orders行,用每条order的customer_id探测哈希表,匹配后构造结果行。若哈希表超出内存预算,会溢写到磁盘,按连接键分区后逐分区连接。

路由器如何管理到分片的连接?

路由器通过gRPC流与每个分片的sidecar通信,sidecar维护一个共享的Postgres连接池。路由器发送包含SQL、会话设置和身份的ExecuteRequest,sidecar从池中借用连接,先协调设置再验证角色,然后转发SQL到Postgres。这避免了每个路由器维护自己的连接池,减少了Postgres后端进程数量。

路由器如何处理聚合查询如AVG?

路由器将AVG(orders.total)重写为SUM(total)和COUNT(total),发送到各分片计算局部和与计数,然后在路由器内合并每个客户的sum和count,由评估引擎计算最终平均值。不能简单平均各分片的平均值。

🏷️

标签

➡️

继续阅读