内容提要
PlanetScale 的分片 Postgres 系统 Neki 通过路由器集中执行序列操作,以保证跨分片唯一性。文章区分三类表达式:不可变、稳定(如 now(),在路由器求值一次)、易变(如 nextval(),须按行基数逐行求值)。Neki 先获取表表达式,再在路由器上叠加序列求值,将唯一值下发各分片完成更新。
延伸解读
序列的集中式治理
在 Neki 中,序列操作被归类为路由器专属,所有序列值必须由路由器集中生成。这是因为每个分片上的序列表初始为空,若允许分片本地求值,可能产生重复值,破坏序列的唯一性保证。路由器通过权威分片获取新值,再分发到各分片,从而实现跨分片同步。这种设计牺牲了部分并行性,但确保了与原生 Postgres 一致的语义。
易变表达式的基数挑战
易变表达式如 nextval() 必须按行基数逐行求值,但 Neki 在路由器上无法预知分片将返回多少行。因此,不能预先缓存序列值,而必须在动态流上实时插入求值。对于 UPDATE 语句,Neki 先获取表表达式(仅需主键和分片键),加锁后逐行生成序列值,再参数化下发到分片执行更新。这体现了分布式查询规划中处理易变对象的复杂性。
稳定表达式的分片依赖
稳定表达式如 now() 和 to_char() 在单条语句内输出不变,但可能依赖会话级配置(如 TimeZone 或 lc_time)。Neki 不能盲目下推稳定表达式到分片,必须确保各分片依赖一致。例如,若用户设置了 lc_time,则需将该设置推送到相关分片。now() 因时钟同步问题必须由路由器求值一次,而 to_char() 则可视情况下推。这要求查询规划器仔细分析表达式依赖。
Q&A
Neki 是什么?它和 PlanetScale 有什么关系?
Neki 是 PlanetScale 的分片 Postgres 系统。
Postgres 中序列(sequence)的核心保证是什么?
序列的核心保证是永远不会生成重复的值。但序列不保证输出数字连续,例如事务回滚时已消耗的序列号不会回收。
Neki 如何处理 now() 这类稳定表达式?
now() 是稳定表达式,在路由器上求值一次,然后将同一个值用于所有输出行。
为什么 Neki 中的序列操作必须在路由器上执行?
因为序列表存在于所有分片上,但都从空序列开始。为了避免跨分片生成重复值,需要集中治理,所以序列操作都是路由器专属的。权威分片是所有序列的物理宿主,路由器从它获取新序列值并分发。
Neki 如何确定易变表达式(如 nextval())的求值次数?
易变表达式必须按行基数逐行求值。Neki 先获取表表达式,确定其行数,然后在路由器上叠加序列求值,为每一行生成新值。
对于包含 nextval() 的 UPDATE 查询,Neki 是如何执行的?
Neki 先在路由器上建立表表达式(仅获取主键和分片索引列),并加上 FOR UPDATE 锁。然后对每一行求值 nextval('tag_seq'),将分片全局唯一的序列值下发到对应分片的行,通过 id 路由并用 WHERE 过滤到正确行。