一条全量馈送的真实代价
我没有去估算,而是把一张全表真的灌进生产库表结构里量了一遍,而这次测量干掉了我自己的一个默认值
Michel Wijnberg
“它扛得住一张全表吗?“是每个 BGP 产品都会被问到的问题,而几乎没有哪个产品拿数字来回答。诚实的 原因是:给出这个答案的过程很难堪,你得把一张真表灌进自己真正的库表结构,然后去看。
所以本文就是这件事。一张完整的 IPv4 表,来自 78,538 个起源 AS 的 1,071,538 条最优路径, 灌进生产库表结构,跑在与实验网络相同的 PostgreSQL 主版本上。下面每一个数字都是对它的测量, 而不是对它的建模。
它还包含了测量所能带来的最有用的东西:反驳你自己。下面的一个数字,早已作为默认值随 Osprey 出货, 而测量显示它落在日常流量之下。此后它已被上调了四倍。
计量单位是会话,不是路由器
这是最常见的容量估算错误,值得先搬开。
一台接了四条全表上游的路由器,会通过一条 BMP 连接汇报四个 peer。它的开销是单上游路由器的 四倍。按设备台数做容量估算,恰好会少算你的多宿倍数,也就是少算了你当初建这张网的目的本身。
一张全表按单位计的开销
这些尺寸是 PostgreSQL 自己报告的关系大小,尽管标注为 MB 和 GB,它们其实是 1024 进制;每行数值 由此推导而来。所以 412 “MB” 就是 412 MiB:把 1,071,538 行乘以 403 字节得到 411.8 MiB,而不是 按十进制读出来的 432 MB。
| 单位 | 开销 | 作用范围 |
|---|---|---|
bgp_rib_entry(含全部 9 个索引) | 412 MB(403 B/行) | 每个受监控的 peer 会话 |
bgp_best_path(含 4 个索引) | 327 MB(319 B/行) | 每个 AS 与路由域 |
bgp_rib_entry_history(含 3 个索引) | 每个区间 376 B | 每条被记录的状态转移 |
| BMP 的每 peer 前缀集合,Go 堆 | 53 MiB(52 B/前缀) | 每个受监控的 peer 会话 |
| 引擎 RIB 去抖缓冲(默认上限) | 267 MiB(560 B/条目) | 每个引擎,与馈送数量无关 |
这张表里有两点值得单拎出来。
索引占了略多于一半的占地。 在 bgp_rib_entry 上,按每会话计:主键 82 MB、peer_prefix
42 MB、prefix_gist 32 MB、prefix 23 MB、origin_as 8.9 MB、staleness 7.5 MB、
next_hop 7.3 MB。这七个值得点名;剩下两个更小,不影响结论。仅这七个就在 412 MB 的总量里占了
203 MB,而主键一个就占了全部的五分之一。如果你曾经想过在这种规模的表上再加一个索引是不是免费
的,这里最便宜的那个也要每会话 7.3 MB。
这次测量还查出了一个没人注意到的缺陷。idx_bgp_rib_peer_lastseen 竟然与 staleness 索引
逐字节完全相同:花掉 7.5 MB 和每一条 RIB 行上一次额外的 btree 写入,什么也没买到。它已被
删除。这类事情,在你把各项尺寸并排摆出来之前是看不见的。
每行 403 字节是含 community 的值。 其中包含了一个现实的六 community 字符串;没有 community 的路由要便宜约 55 字节。引用较低的那个数字而不说明是哪一个,你的容量规划指南在任何人部署任何 东西之前就已经乐观了 14%。
算过的几种部署
| 部署 | 在线数据库 | BMP 堆 |
|---|---|---|
| 1 台边界路由器,2 条上游 | 1.15 GB | 107 MiB |
| 2 台边界路由器,各 2 条上游 | 1.98 GB | 213 MiB |
| 4 台路由器 × 2 条上游 | 3.6 GB | 426 MiB |
| 路由反射器馈送,20 条全表会话 | 8.6 GB | 1.04 GiB |
这些是在线状态的数字。历史是另一笔账,而有意思的正是历史。
变动率没法从一张快照里读出来
一张表告诉你持有它的代价。它对记录它的代价一无所言,因为那取决于表变动得多频繁,而快照 没有时间轴。
所以变动率改从公开的 MRT 归档中测量:RIPE RIS 采集器 rrc00,2026-08-10,全天分散的四个 一小时窗口,跨 98 条不同 peer 会话的 680 万条 UPDATE 消息。对该样本套用了 Osprey 的 30 秒 去抖。属性哈希抑制刻意未建模,因此这些数字是实际会被记录量的上界。
这里的区间指去抖之后被持久化的一次状态转移。它不是原始的 BGP UPDATE(一条可以携带很多 前缀),也不是前缀事件(去抖可能把它折叠进已有区间)。
| 按会话 | 每天记录的区间数 | 历史存储 |
|---|---|---|
| 中位数 | 413,000 | 155 MB/天 → 90 天 14 GB |
| p90 | 840,000 | 316 MB/天 → 90 天 28 GB |
| 观测到的最重 | 8,300,000 | 3.1 GB/天 → 90 天 281 GB |
去抖确实在干实事:它把每个 30 秒窗口折叠成每(会话, 前缀)一条记录,消除了45.6% 的原始前缀 事件。在全表目标上关掉它,历史账单大约乘以 1.8,换来的是对窗口内部所发生转移的更细粒度可见性。 在 DFZ 馈送上,那几乎全是你本来就希望不必逐行存储的那类细节。
如果我是读者,我会想要两条限定。rrc00 是 RIPE 的多跳采集器,其 peer 比典型的 IXP 本地中转 会话更吵,所以请把这些当作偏悲观的数字。另外,离散程度远比中位数重要:最重的会话是中位数的 20 倍。按 p90 做容量规划,按尾部设告警,别让任何人把中位数当容量方案卖给你。
那个错了的数字
Osprey 在历史记录上有一个断路器。如果某个目标产生的区间数超过 storm_ceiling_per_hour,则该
小时剩余时间内记录被抑制,以免一条抖动的会话在无人看管时把磁盘填满。
出货的默认值是每小时 250,000。
现在把它和测量放在一起。rrc00 样本中最重的那条真实会话,速率约为每小时 346,000 个区间, 也就是上表里的每天 830 万。这不是失控,不是抖动环路,而是一条真实采集器上的一个真实 peer,在 一个普通的星期一。
这个默认值是被正常运行自己触发的。 而抑制型断路器的失效方式属于安静的那种:什么都不崩溃, 什么都不高声告警,历史只是不再完整,而那道缺口只有你专门去找时才看得见。
现在的默认值是每小时 1,000,000,同样从这次测量推导而来,而不是凭直觉。p90 的变动率约为 每会话每小时 35,000 个区间,因此上面最大的那个算过的方案,也就是 20 条全表会话的路由反射器馈送,在 p90 下约为每小时 70 万。新的上限带着余量高于它,也高于最重的那条真实单会话,同时仍远低于一次 真正的失控所产生的量:一条在抖动环路里反复重放整表的会话,产出的是这个数的若干倍,所以断路器 照样会断。
同一次测量还讲清了一件事,我宁愿明说而不是留作暗示:一个聚合了许多吵闹会话的目标,是可以正当地 超过这个上限的。rrc00 的 98 条会话合计每天产生约 5,000 万个区间,每小时约 200 万,轻松越过 新的上限。如果你把 Osprey 指向这样的对象,断路器会触发,而且它触发是对的。
这一点值得直说,因为这两个数字看起来像在互相矛盾:这个上限是失控防护,不是容量方案。 它按 目标设置,高于我们所规划的最大方案在正常运行下的产量,低于一条循环重放整表的会话的产量。一个 正当超过它的聚合目标,是长大到超出了一个目标,而不是超出了这个产品。
正因如此,触发是被记录下来的,而不只是写进日志。当上限生效时,该小时剩余时间的历史是不完整 的,而这道缺口会被写入一本你可以查询的盲区账本。一个悄无声息地停止监控的监控系统,比一个高声 停止的更糟,而区分二者的唯一之处,就在于那道缺口是不是一条一等公民的记录。
这些数字没有告诉你什么
上面的一切都是字节:一张全表在 PostgreSQL 里占多少,BMP 服务器在 Go 堆里持有多少,历史积累得 多快。这是一根轴,而容量问题有好几根。
这里没有测量、因此也不作断言的:
- CPU,无论是摄入时还是查询时。
- 会话建立后到第一个有用答案的时间,那才是运营者真正感受到的数字。
- 消息路径:在一张表收敛的过程中,总线与在线 WebSocket 状态各自的开销。
- 查询时延:针对这种规模的表,这与它的占地是两回事。
这些是各自独立的测量,我还没有做。一份容量规划指南若给你一个笃定的存储数字,再让你顺理成章地 假定其余部分也按同样方式伸缩,从而悄悄暗示并非如此,那它正在做本文存在的意义所反对的那件事。
为什么要公布一个自己弄错了的默认值
因为另一种做法在某个具体意义上更糟。
由估算搭建起来的容量指南是不可证伪的。没人能核对它,于是没人会发现错误,于是错误照样出货。 这一个之所以被发现,是因为那些数字是靠灌入一张真表、重放真实更新得出来的,而真实的更新流并不 在意配置文件认为什么才算合理。
这次测量花了大约一天。它找出了一个重复索引、一个错了四倍的默认值,并且给出了这个产品对 “它扛得住一张全表吗”的第一个诚实答案。那就是:扛得住,在线状态每会话 412 MB 加 53 MiB 堆, 历史则在 90 天 14 GB 到 281 GB 之间的某处,完全取决于你的 peer 有多吵。最后这个区间才是真正的 答案,而不是它的中点。
要点
本文中的每一个数字都是对已出货库表结构的测量,而其中一个反驳了已出货的代码。
这就是”去测量自己的产品、而不是对它进行推理”的理由。不是因为推理草率,而是因为一个靠推理选定的 默认值,没有任何办法告诉你它错了。一张真表用一天就做到了。
这是本系列的最后一篇。下一篇从这一切的底下开始:一个记录器如何加入别人的协议,却不成为它的 一部分,一次一扇正门。