第 17 / 23 篇

第16章 容量与延迟:为恢复保留余量

先计算哪些数据会增长

源库表会增长,binlog 会增长,Kafka 数据与内部 topic 会增长,PostgreSQL 投影、去重历史和索引也会增长。把投影文档大小乘以行数,只覆盖其中一部分。特别是 delivery_history 按事件增长,一个频繁变化的小表也可能形成很大的历史表;删除商品不会自动删除它之前所有交付坐标。

因此容量模型至少区分当前对象数、每日变化数、消息平均大小、峰值、保留天数与副本数量。Kafka 压缩和数据库索引还会改变实际空间,不能仅用 JSON 字节数估算磁盘。实验保存有限合成数据,不提供通用压缩比或每核吞吐数字;生产应当用自己的字段分布和更新模式测量。

延迟由多段组成

源事务提交后,连接器需要读取并转换,broker 需要接受,消费者需要轮询和写数据库,查询系统还可能有缓存。平均端到端延迟很低,可能掩盖某一段偶尔停顿数十秒。监控应当既能看整体业务探针,又能按阶段拆分,避免一遇到延迟就盲目扩容 broker。

连接器批量参数、Kafka 批处理、消费者事务大小都会影响延迟与吞吐。更大批次可能减少往返,却增加等待和故障重放量;每条同步提交清晰但开销较高。任何调整都应当保留提交顺序和幂等不变量,再用真实负载比较,不能因为某个参数名字带 throughput 就直接调到最大。

历史表的索引成本

delivery_history 的复合主键保证事件身份唯一,但每条事件都要进行索引写入。投影 upsert 也需要访问商品主键和旧版本行。高更新率下,数据库清理、事务日志与磁盘延迟可能先于 CPU 成为瓶颈。只看消费者进程 CPU 很低,不能推断它没有性能限制。

可以考虑按时间或输入范围分区历史表,并在明确恢复边界之后回收旧分区。然而当前主键是事件坐标,不含处理时间,增加生命周期字段需要同时设计约束与清理验证。清理之后能否安全接受旧回放,要看投影是否仍保留足够水位以及业务操作是否纯覆盖。本书没有实现这项扩展。

分区扩展带来的协议成本

单分区限制并行度,多分区增加吞吐空间,但同对象顺序仍需要稳定键路由。数据倾斜时一个热门对象无法通过增加分区自动拆散,除非业务允许分解状态。分区变化还会影响本书 offset 条件,不能简单修改 topic 分区数后继续使用原投影。

一种可审计的升级方式是新建目标分区数的 topic,通过新连接器或明确迁移管线建立新世代,再对账和切换。这样把路由变化变成一次显式版本迁移,而不是悄悄改变现有位置的含义。代价是重建时间和双份资源,需要提前纳入容量预算。

快照与追赶争用资源

全量快照可能占用源库缓存与磁盘,在线 binlog 读取也需要及时前进。若快照速度很慢,日志留存不足,重建尚未完成就可能失去衔接需要的日志。只增加 Kafka 磁盘无法解决源库日志清理问题,因为变化还没有进入 Kafka。容量规划必须沿整条链路计算最薄弱的恢复窗口。

投影重建也可能与线上查询争用同一 PostgreSQL 实例。generation 隔离的是逻辑身份,不是 CPU、IO 或连接数。生产若对可用性要求更高,可以使用独立重建集群或资源限额,但需要验证切换方式。我们不把同实例不同世代描述成资源故障隔离。

如何建立有意义的压测

先保留本书的正确性检查,再增加数据量和写入模式。负载应当包含插入、更新、删除、热点对象、大文档与 schema 兼容路径;只连续插入短字符串会高估许多系统的表现。每次测试记录环境、参数、持续时间和是否达到稳态,并在结束后完整或分段对账。

测试中还应安排消费者停机,观察恢复净吞吐是否足以追平;安排投影慢写,观察连接器与 broker 的缓冲边界;检查异常退出后是否仍然收敛。性能提升若依赖提前提交位点或丢弃失败消息,应当判为正确性失败,而不是一种可接受优化。

资源隔离需要真实限制

逻辑上分开世代并不会限制重建连接数,也不会防止一个错误循环耗尽磁盘。生产运行器应有CPU、内存、连接、队列和磁盘预算,并在达到阈值时停在可恢复位置。单纯把超时设得很大,只会延后暴露资源耗尽。增加这些限制时也要测试失败路径,确认不会在投影尚未提交时推进位点;资源保护只有与正确性协议一起工作,才真正提高可恢复性。

本章练习

按每天一千万条变更、每条一千字节、保留三天估算原始日志量,再列出未包含的副本、索引、压缩和峰值因素。为当前单分区消费者设计一项批处理优化,写明必须保留的事务与位点顺序。