16.2 时序表与时间分区
分区不是“数据带时间戳以后自然要做的事”。它是一项物理设计决策:
第 4 章先建立类型、约束与分区 ADR。本节只把那套判断落实到事件时间场景, 不把“按天分区”包装成默认答案。
16.2.1 从 ch04 的分区 ADR 选择时间键
分区键首先决定数据归属
配送事件有两个候选:
按 received_at 分区的好处是写入几乎总落到最新分区,创建和冻结历史分区
容易;坏处是“某业务日发生的事件”会散布到以后到达的分区。
按 occurred_at 分区使业务日查询与保留自然对应,也能只扫描目标事件时间
范围;代价是迟到和重放会写旧分区,旧分区不能简单变成永久只读。
本章 ADR 选择:
理由不是“事件表都该这么做”,而是本 PoC 的主要查询与历史围栏连接都以事件 发生时间为准。若法规要求按接收时间保留原始消息,可让 raw ingest 与规范 事件采用不同分区键。
父表合同
核心定义摘自 setup.sql:
主键包含 occurred_at,不是为了业务身份。PostgreSQL 在分区父表上建立
UNIQUE/PRIMARY KEY 时,约束列必须包含所有分区键列;这样每个叶分区的
局部唯一索引才能共同证明父表范围内不重复。
业务要求的全局 event_id 唯一性由未分区的:
承担。这个模式把两个合同分开:
若只在每个叶分区上建 UNIQUE(event_id),同一个 ID 仍可出现在不同分区。
应用重试改变 occurred_at 时尤其危险。
半开日分区
边界含义是:
fixture 验证:
若没有可接收某值的分区,向父表写入会失败。生产应提前创建未来分区并监控 覆盖范围,不应依赖事故发生后手工补表。
UTC 边界与当地业务日
本章日分区是 UTC 日,不等于纽约当地日。纽约 2026-03-08 当地日可能跨越 两个 UTC 分区:
查询仍然写两个绝对边界:
规划器可能保留两个相关 UTC 分区,其余分区被裁剪。这比为每个用户时区建立 分区可控得多。
裁剪取决于谓词与分区边界
正例:
time-pruned-plan.sql 的固定计划只有:
这已经是成功的分区裁剪。叶表只有七行,顺序扫描是合理选择;“没有使用 B-tree”不影响裁剪已经生效。
反例:
逻辑答案一样,物理工作不同。不要用“给表达式建索引”代替分区裁剪;索引可 减少每张叶表内的扫描,却不一定让规划器排除叶表。
官方 声明式分区文档 区分规划期与执行期裁剪,也明确指出裁剪由分区边界驱动而非普通索引。
从目录验证,而不是从表名猜
partition-catalog.sql 同时采集边界、行数、
最早/最晚事件、owner 与 marker。正式验收不应只检查三张名字像日期的表。
16.2.2 写入模式、冷热生命周期与保留
写入链先处理身份,再路由事实
本章确定性流程:
顺序很重要。如果先向分区事件表写入,再尝试全局注册,两个并发事务可能把
同一业务 ID 写进不同叶表。生产可使用单事务、注册表 INSERT ... ON CONFLICT、显式状态机或消息 inbox/outbox 协调,但必须让全局身份争用发生在
可证明唯一的位置。
为迟到写入保留窗口
按事件时间分区时,“旧”不等于“不再写”。生产 ADR 至少定义:
数字取决于业务,重要的是分开:
- 正常迟到:自动接受并更新聚合;
- 允许迟到:接受但触发更正或告警;
- 超窗回补:需要显式作业、审计和容量窗口;
- 冻结:应用角色不再写,但受控管理员可能回补;
- 保留到期:可删除或归档。
若把“昨天的分区”每天 00:00 立刻设只读,e001 这类迟到事件会在正常链路
失败。
不要让 DEFAULT 分区变成垃圾桶
DEFAULT 分区可以避免缺分区导致写入失败,但会引入新的责任:
- 为什么正常日期落入 default?
- 补建正式分区时怎样迁移而不长时间阻塞?
- default 上的约束是否允许 attach 新分区?
- 查询是否意外长期扫描 default?
- 异常未来时间和损坏年份是否被悄悄接受?
一种稳健策略:
另一种策略可以保留受控 DEFAULT,但必须把行数、年龄和迁移作业作为一等 监控。不存在普适答案。
分区粒度由约束共同决定
按小时、日、周或月选择时,至少估算:
例如一年按日 365 张、每张 4 个索引,已经有约 1,460 个叶索引;若按小时, 一年约 8,760 张表和数万个索引。小分区不自动更快,空或微小分区也有目录、 锁、统计与规划成本。
本章用三张日分区只为让边界和计划可见,不构成生产粒度建议。
索引是叶分区成本
本章每张事件分区维护:
三个叶表一共 12 个索引对象。分区越多,DDL、REINDEX、统计、磁盘 inode、 缓存和故障面越大。
父分区索引能管理对应的叶索引集合,但 PostGIS、不同历史策略或在线构建流程
仍可能需要逐叶控制。无论自动还是手工创建,都要从 pg_index 验证
indisvalid/indisready/indislive,不能只看 DDL 命令返回成功。
热、温、冷不是表空间颜色
可以按生命周期决定:
| 状态 | 可能动作 |
|---|---|
| 热 | 正常写、完整索引、频繁 analyze |
| 温 | 低频回补、限制写角色、保留关键索引 |
| 冷 | detach/归档、压缩、外部存储或只读集群 |
| 到期 | 经审批删除并保留删除证据 |
但每个动作都要回答恢复路径。DROP TABLE old_partition 很快,却会同时删除
数据和局部索引;只有备份、归档与法规合同允许时才是正确保留策略。
PostgreSQL 支持 DETACH PARTITION,可让数据先脱离父表再归档或处理。在线
动作的锁、并发、约束验证和版本差异应在接近生产的环境验证,本章 PoC 不演示
线上表迁移。
删除与回补会影响 vacuum
时间序列通常“追加为主”,不等于没有 MVCC 成本:
- 重复处理可能执行冲突更新;
- 迟到修正会更新旧聚合;
- 围栏重算可能写结果表;
- 保留若使用大批
DELETE会产生死元组和 WAL; - 索引页仍会分裂、膨胀或缓存失衡。
分区级删除可以避免海量行删除,但活跃分区仍需 vacuum/analyze。后续 第 28 章 专门处理 vacuum、冻结与膨胀;本章只要求 把这些成本列入 ADR。
16.2.3 原生分区、聚合与可选时序扩展
先列需求,再选能力
“这是时序数据,所以安装 TimescaleDB”不是决策。先问:
| 需求 | PostgreSQL 原生基线 | 何时考虑专用扩展 |
|---|---|---|
| 时间范围裁剪 | RANGE 分区 |
分片/自动 chunk 管理明显减负 |
| 普通时间聚合 | date_bin、GROUP BY |
continuous aggregate 有量化收益 |
| 预计算 | 物化视图、增量任务 | 自动刷新与失效模型更合适 |
| 保留 | detach/drop 分区 | policy 自动化降低运维风险 |
| 压缩/列式收益 | 外部归档、其他扩展/方案 | 压缩率与查询代价已实测 |
| 高写入 | 批量、COPY、schema/索引优化 | chunk 并行与架构收益已验证 |
原生方案的优势:
- 能力随 PostgreSQL 一起交付;
- 依赖与升级边界较小;
- SQL、备份和故障模型更接近核心数据库;
- 可以先建立可信基线。
扩展的优势可能包括自动 chunk、保留策略、压缩、时间函数和连续聚合,但也 增加:
“少写运维脚本”有价值,但必须与新依赖成本一起衡量。
本章为何推迟 TimescaleDB
fixture 只有 12 行、三天数据。它能证明:
- 事件时间分区合同;
- 半开边界;
- 裁剪正反例;
- 聚合语义;
- 迟到和历史围栏连接。
它不能证明:
- 压缩比;
- continuous aggregate 刷新成本;
- 大规模 chunk 规划;
- 写吞吐或副本延迟;
- 自动保留比受控分区作业更可靠。
所以 spatiotemporal-adr.md 把
TimescaleDB 标为 deferred,而不是反对。重开条件是压缩、保留、连续聚合或
运维收益出现量化证据。
可选扩展仍要完整走交付链
在 Pigsty 中,时序扩展不是一句 CREATE EXTENSION:
Pigsty 当前 TimescaleDB 扩展页 应作为目标 release 的供应入口;实际版本和 PG major/OS 可用性要在 inventory 中核对,不能从本章快照推断未来版本。
聚合真值仍来自时间合同
无论使用:
都必须固定:
- event time 还是 processing time;
- bucket origin 和 timezone;
[)边界;- 迟到水位线;
- 更正是否回写旧桶;
- 去重在哪一层发生;
- 结果版本与重建方式。
扩展能自动化计算,不会替业务决定这些语义。
用 A/B 迁移而不是信仰迁移
若要引入时序扩展,建议保留原生基线:
然后分别比较:
只有语义结果一致后,性能数字才可比较。
16.2.4 不在本章重复在线分区化和维护细节
本章边界
本章从空 schema 创建三张固定分区,目的是教学验证,不处理已有大表的在线 分区化。以下主题需要单独的迁移设计:
- 在写入不中断时建立新分区父表;
- 双写、触发器或逻辑复制;
- 历史数据分批回填;
ATTACH PARTITION前的约束证明;- 索引并发构建与父索引 attach;
- 外键、序列、权限、RLS 和依赖对象迁移;
- 切流、回退、校验和与旧表退役。
它们属于迁移、锁和运维章节,而不是时空语义入门。这里不提供一条貌似通用的 “在线改分区”命令,以免读者在生产大表上照抄。
分区维护也不应塞进应用请求
不要让第一条新日期写入在业务事务里执行 CREATE TABLE。DDL 会涉及锁、
catalog、权限、审计和副本传播。更合适的是受控作业:
删除旧分区也需要独立审批、备份/归档确认和 active worker 防护。
本章必须保留的判断力
即使篇幅有限,也不能删掉:
- event/ingest/valid time 分离;
- 选择分区键的 ADR;
[)边界;- 全局
event_id与分区主键分离; - 直接谓词与包裹谓词的计划对照;
- 迟到写旧分区的成本;
- 原生能力与扩展收益的证据门槛;
- 分区数量乘以索引数量的运维成本。
可以删的是某一扩展的参数百科或某一版本的命令清单。基础判断一旦省掉,读者 会把工具选择误当成时间模型。
本节验收
应看到:
如果逻辑行数正确但计划扫描全部分区,先修谓词和边界;如果行路由错误,先修 分区定义或事件时间。不要用更多索引掩盖语义错误。
上一节:时间语义先于时序扩展 · 返回本章目录 · 下一节:空间类型与坐标参考 · 查看全书目录 · 查看索引中心