Skip to content

时序建模通则

控制库有 6 张降采样存档表3 张差分基准表,分属四个子系统,但形态完全一致。本页描述这套共同形态,具体列定义见各子系统册。

存档表桶长
node_iface_traffic_rollup5 min流量
node_traffic_rollup5 min流量
node_metrics_rollup5 min健康与自观测
autopeer_route_rollup5 min自动对等
prefix_flap_rate_rollup60 sFlap
flap_activity_rollup60 sFlap

统一形态

主键 = (维度…, bucket_start)        bucket_start = epoch 秒,对齐桶长
值列 = <指标>_sum + sample_count    读取时以 sum / count 得均值

从一次上报到一条曲线,数据流经四个环节:

四条共同纪律:

  • 桶起点用 BigInteger 存 epoch 秒,不使用时间戳类型:分区序号可由整除直接算出,且与时区无关。
  • sample_count 并非冗余。 它区分「该桶值为 0」与「该桶无上报」,读侧据此绘制断档而非零值。
  • 累计量必须先差分再入桶。 agent 上报的是自进程启动以来的累计值,落桶前需与持久基准求差。
  • 主键已包含 bucket_start,满足 PostgreSQL「分区键必须包含在唯一约束内」的硬性要求,分区化无需改动主键。

唯一的例外:gauge 语义

autopeer_route_rollup 存储的是路由条数,属于 gauge[1]:同一桶内多次观测取最后一次,既不累加也不求平均。将 gauge 按 counter 累加是时序建模中最常见的错误,该表以「不设 _sum 列」从结构上排除了这种误用。

三张差分基准表

node_traffic_last_samplenode_iface_traffic_lastnode_metrics_last 在形态上属于 L2 当下值(覆盖式 upsert,丢失后下一轮重建),在用途上服务于 L3。

其存在理由是持久性:差分所需的上一份样本原本可仅存于缓存,但 Redis 可丢失,一旦丢失即出现「重启后首个桶无速率」的空洞。存入 PostgreSQL 后,Redis 不可用期间 5 分钟粒度的存档仍可正常累加。


保留与裁剪

保留裁剪方式分区目标
node_traffic_rollup17 280 桶 ≈ 60 天按节点保留最近 N 桶(notin_ 子查询)
node_iface_traffic_rollup60 天按时间 cutoff DELETE
prefix_flap_rate_rollup86 400 桶 = 60 天按时间 cutoff DELETE
node_metrics_rollup17 280 桶 ≈ 60 天按时间 cutoff DELETE
autopeer_route_rollup17 280 桶 ≈ 60 天按时间 cutoff DELETE
flap_activity_rollup86 400 桶 = 60 天按时间 cutoff DELETE

60 天有明确依据:界面最长的 range 为 30 天,而「与上期对比」需同时读取紧邻的上一个 30 天窗口,保留期必须覆盖两个最长窗口。

注意: 三张分区目标表的按行 DELETE[database] partitioned_timeseries 开关门控。置真后裁剪全部交由分区维护循环 DROP 整个分区完成,因此必须在转换实际完成之后才置真——未分区却置真等同于旧行永不回收。转换步骤见迁移与演进

按行 DELETE 的代价

随机 DELETE 持续产生死元组与索引膨胀,依赖 autovacuum 回收。实测中仅 node_traffic_rollup 的死元组稳定偏高(约 7 900),因为它同时满足两个条件:高频 upsert(一个 5 min 桶被 30 s 采样刷新约 10 次)与按行裁剪。

分区化的收益存在边界:它只消除裁剪产生的死元组,不消除 upsert 产生的。该表两个来源皆有,分区化仅解决其中一半。量化见性能与容量


  1. gauge 与 counter:时序指标的两种语义。counter 单调递增,关心的是增量,可跨桶累加(如累计字节数);gauge 表示某一时刻的瞬时值,只能取样或求平均,累加无意义(如路由条数、内存占用)。 ↩︎