Skip to content

分层与存储角色

数据放在哪一层、依据是什么。这是数据层最上游的决策,表定义与写入路径都是它的推论。


五层数据模型

从最权威、最持久,到最易失、最可重建。层次决定备份策略、丢失后果与恢复方式——这三者构成分层的实际意义。

内容存储生命周期丢失后果恢复方式
L1 配置事实源节点身份、Peering、DNS、密钥、端口池、管理员账号PostgreSQL常驻全站不可用,且不可重建仅能从备份恢复
L2 运行观测各节点最新健康、路由全表、差分基准PostgreSQL常驻(覆盖式 upsert[1]视图空窗agent 下一轮上报即重建
L3 时序存档流量、指标、路由事件、flap 速率的降采样序列PostgreSQL保留 60 天历史趋势断档不可重建(既往采样无法追溯)
L4 持久瞬态flap 热态、agent 心跳、登录限速、WS 订阅、会话与凭据ValkeyTTL 秒至天一次重登录或一格时序断档功能自愈,无需干预
L5 派生缓存desired-state 物化结果、健康与路由聚合、ASN 名、token 解析RedisTTL 10 秒至 1 小时仅延迟上升,不产生错误下次读取时自动回填

三条推论,落到日常操作:

  • 备份范围为 L1–L3 加 Cap 所属的那部分 L4(见备份边界)。备份 L5 意味着分层划分有误。
  • L4 与 L5 的分界不是持久与否,而是是否为唯一副本。 L5 的每一条都能从 L1–L3 重算;L4 的 flap 热态无法重算——它是对一份已经过去的上报所做的差分基准。
  • L3 是唯一「不可重建但可以丢失」的一层:丢失不影响功能,仅历史曲线缺失一段。

层是属性,不是目录

真实子系统通常跨层。流量同时占据 L2(差分基准)、L3(5 min 存档)与 L5(Redis 热窗口);flap 占据 L4(打分热态)、L3(速率存档)与事实层(告警与转移)。若按层切分文档,任一子系统都会被拆散,因此 tables/ 按子系统分册,层标注在每张表上。


存储实例与角色

实例配置角色判据
postgresPG 16,三个 databaseL1–L3,事实源需要事务、JOIN 或审计追溯,或丢失后不可重建
valkeynoeviction[2],RDB[3] save 300 100maxmemory 256mbL4,持久小状态 + Cap 的事实源键值可寻址、必须带 TTL、需跨进程共享
redisallkeys-lru[4]maxmemory 256mb,不落盘L5,可丢弃缓存删除整个实例后功能仅变慢、不出错

注意:两者的差别在角色而非协议。 Redis 会淘汰键且不落盘,写入唯一副本等同于静默丢数据;Valkey 落盘且不淘汰,写入可丢弃缓存则长期占用 noeviction 额度,额度耗尽会连带导致 Cap 的验证码签发报 OOM,表现为登录中断。

db 编号分配

Redis 与 Valkey 各有 16 个逻辑 db。按应用分配,且同一应用在两个实例上使用同一编号——由编号即可判定归属,FLUSHDB 也能精确清除单个应用而不影响其他。

db应用valkey(L4)redis(L5)
0Cap事实源:签名密钥对、sitekey、配置不使用
1control-serverflap 热态、心跳、限速、WS 订阅、拨测中转、门户登录事务desired-state、健康、routing、流量窗口、ASN 名、token
2auth-serverOAuth 授权码、WebAuthn ceremony预留
3registry-server预留只读查询缓存

注意:db 仅隔离键名空间。 maxmemory、淘汰策略、持久化与 CPU(单线程)均为实例级配置,分库不等于资源隔离。

编号由 apps/control-server/app/tests/test_storage_discipline.py 静态检查约束:将某应用配置到其他应用的 db 上,测试会明确指出失败位置。


新数据的准入决策

按顺序判断,首个满足条件的分支即为结论:

三条硬性纪律,均由 test_storage_discipline.py 静态约束:

  • *_strict 系列仅可用于 Valkey。 「失败即抛出」意味着调用方将其视为主存储,而 Redis 会淘汰键且不落盘。
  • 写入 Valkey 的每个键都必须带 TTL。 实例为 noeviction,无 TTL 的键将永久占用额度。
  • PostgreSQL 中不存放寿命短于一天且无审计价值的行。 此类数据只会制造死元组[5]与 vacuum[6] 抖动。

四个已落地的判例

判据抽象,判例具体。以下四次迁移可直接对照:

数据迁移方向依据
flap 打分热态(两级)PostgreSQL → L4短命、可重建,且为唯一副本,不能置于会淘汰的 L5
门户登录事务PostgreSQL → L430 分钟内消费一次即作废,无审计价值,不参与关联查询
agent 心跳、登录限速进程内存 → L4多 worker 下必须跨进程共享
拨测与日志流中转进程内存 → L4同上,且单次作业天然横跨三个请求

反向判例同样重要:时序存档不迁移(不可重建,置于可丢失介质违反分层原则);告警的 started_atpeak_score 不迁移(需审计追溯)。取舍详见性能与容量


备份边界

对象是否备份理由
dn42_control / dn42_auth / dn42_registry必备L1–L3,事实源
valkeydump.rdb必备db 0 为 Cap 的事实源(14 个无 TTL 的键,含签名密钥对);db 1/2 一并覆盖
redis不备份L5 的定义即「删除后仅变慢不出错」,备份它意味着分层划分有误

脚本见 deploy/tools/backup_control_plane.sh;上线与恢复步骤见 deploy/docker/STORAGE-ROLLOUT.md

注意: 备份集中敏感度最高的两张表是 node_wireguard_keys(全 fleet 隧道私钥,base64 明文)与 portal_sessions(上游 OIDC 令牌明文),访问控制应按此定级。


  1. upsert:插入或更新的合并操作。行不存在则插入,存在则按主键更新,用于「每节点最新态」这类只关心当前值的写入。 ↩︎

  2. noeviction:Redis / Valkey 的淘汰策略之一。内存达到 maxmemory 后拒绝所有写入并返回 OOM 错误,而非淘汰既有键。适用于存放唯一副本的实例。 ↩︎

  3. RDB:Redis / Valkey 的快照式持久化。按 save <秒> <变更数> 触发全量落盘,与增量追加的 AOF 相对。 ↩︎

  4. allkeys-lru:内存达到上限时按最近最少使用(Least Recently Used)顺序淘汰任意键。适用于纯缓存实例,代价是任何键都可能在任何时刻消失。 ↩︎

  5. 死元组(dead tuple):PostgreSQL 的 MVCC 机制下,UPDATE 与 DELETE 不就地修改数据,而是保留旧版本行。旧版本在无事务引用后成为死元组,占用空间直至被 vacuum 回收。 ↩︎

  6. vacuum / autovacuum:PostgreSQL 回收死元组并更新统计信息的维护过程。autovacuum 为后台自动触发版本,其回收速度跟不上产生速度时,表体与索引会持续膨胀。 ↩︎