ADR-0007 一期部署档位:简化形态 + 保留升级路径¶
状态¶
已接受(2026-08-14)。对应 docs/PLAN.md §2.2、§7.12、§10.4.2、§17.1、§18.1、§18.3、§19.4、§27.2.4。
背景¶
PLAN.md §25 的三档容量表以目标档(DAU 5000 万、千万连接、10 万人群、R_avg = 122)
为设计基线。按该档一期交付,意味着第一天就要建成:ScyllaDB 集群、完整 lane 向量水位、
成员 Bitmap 分片与槽位映射、检查点体系、MailboxNode 主备。
但 §25.0.2 的构成显示成本高度集中:
R_avg = 122 = 1.48(单聊) + 19.2(中群80人) + 100(大群5000人) + 1.25(控制事件)
^^^^^^^^^^^^^^^^
2% 的消息贡献 82% 的邮箱写入
若一期产品上限为千人群,R_avg 降至约 22,邮箱层成本下降一个数量级,
MailboxStore 的选型空间随之打开(Redis 从"不成立"变为"成立")。
同时 §18.1.1 已论证:自研 MailboxNode 等于自研一套分布式有状态存储, 把它作为一期基线的唯一方案会让交付风险集中在最难的一块上。
决策¶
1. 一期按简化形态建设¶
| 维度 | 一期取值 | 目标档取值 |
|---|---|---|
| 产品群规模上限 | 1000 人 | 10 万人 |
(参数名 max_members_per_group,附录 B.6.1;超限整批拒绝,禁止静默截断) |
||
R_avg |
约 22 | 122 |
MailboxStore 实现 |
Redis 7.0.0+ standalone(AOF + noeviction,30 天日桶) |
ScyllaDB → 自研 LSM |
| MailboxNode 容错 | 无主备,租约漂移接管(§10.4.2 形态 B) | 同左(主备降级为可选 RTO 优化) |
| 聊天室回放 | RoomWriter 进程内环形缓冲(§7.12) | 同左 |
| 分发日志 | Redpanda(ADR-0006) |
同左 |
| 检查点 | 仅 W[lane] + W_floor[lane] + DispatchProgress 快照 |
完整邮箱索引检查点 |
2. 不可变项一律按目标档定死¶
这是本 ADR 的核心:简化的是实现,不是契约。以下项建集群/建表后不可变 (§27.2.4),一期必须按目标档取值,否则升级路径被焊死:
virtual_bucket_count = 65536 不可变
lane_count = 64 不可变 ← 即使千人群完全用不上
message_seq_bucket_width = 4096 不可变
mailbox_seq / room_seq 位布局与 epoch 上界 不可变
bucket_hash / lane_id 的哈希族(blake3) 不可变
event_id / dispatch_id 的哈希输入元组 不可变
event_ordinal 的 event_type 优先级映射 不可变
全部分区键设计 不可变
契约核心(§5.1 / §6 / §7 / 附录 A / 附录 B) 改动需 ADR
lane_count = 64 是本 ADR 最需要强调的一条:千人群的 dispatch 展开是毫秒级,
§9.2.1 描述的队头阻塞根本不成立,lane 机制在一期毫无用处。但它已签入游标令牌(§6.8)
且标记为建集群后不可变(附录 B.1)——设 1 则升级到大群档时必须重建集群并换发全部游标;
设 64 的成本是 64 个计数器,约等于零。
这是唯一一条今天不做、以后就再也做不了的大群准备。
3. 一期可不做¶
- 自研 LSM MailboxStore(
ADR-0001阶段二) - MailboxNode 主备
- 成员 Bitmap 分片与槽位映射(千人群用有序 ID 列表足够;但在线 Bitmap 求交必须保留, 它的规模跟在线用户数相关,与群规模无关)
mailbox_write_policy = mention_only(ADR-0003,千人群下准入判据不成立)- 完整邮箱索引检查点(Redis 实现下权威数据不在节点本地)
- 跨地域双活(§19.4 的高级租户档)
4. 一期不能砍的承重墙¶
以下机制与群规模无关,砍掉即丢消息或错未读,一期必须完整实现:
分发日志(提交与重放的载体,§6.9.5 永不丢弃已 COMMITTED 消息)
确定性物化(blake3 event_id、created_at 取自 dispatch 记录,§6.7)
lane 连续物化水位 + 追平才服务(§9.2、§10.4.2)
设备游标只由 MAILBOX_BATCH 连续推进(§6.8 不变量)
mailbox_trim_watermark + CURSOR_EXPIRED + REBUILD(§18.3、§9.6)
SESSION_DELTA / REACTION_UPDATE 的幂等绝对值语义(§12.6、§13.6.2)
未读权威定义式 + reconcile(§12.5)
fencing 单写 + 接管起始位点 = min_j(W[j])(§19.2、§10.4.2)
三层准入限流(§8.2)
5. 升级路径¶
当前:Redis 7.0.0+ standalone(AOF + noeviction)
优先:ScyllaDB
触发:产品放开群规模上限,或 standalone Redis 的资源/可靠性边界不再满足。
动作:优先更换到 ScyllaDB,实现走 §18.1 已定义的灰度流程
单 MailboxShard 粒度切换 → 影子读比对(1% 采样)
→ mailbox_store_shadow_mismatch 非零即阻断 → 可随时回切
Redis Cluster 不在这条升级路径中。它需要独立的提交状态机、数据迁移、Cluster 路由和
端到端故障验证;现阶段必须启动拒绝,不能用局部 hash tag 宣称完成。
不需要:改协议、改客户端、改游标、改分片映射、改哈希族
客户端完全无感——这正是第 2 条把不可变项按目标档定死换来的回报。
理由¶
- 成本:一期邮箱层从 PB 级降到 TB 级,
MailboxStore从"自研分布式存储" 降到"一套 standalone Redis",交付风险不再集中在最难的一块。 - 风险:§18.1.1 论证的自研存储风险被推迟到有真实负载数据之后,
而
ADR-0001的四条切换判据届时可用实测值判定,而不是拍脑袋。 - 不损失可选性:第 2 条保证升级只换实现不换契约。代价仅为
lane_count = 64这类"当下无用但必须预置"的少量结构。 - 与
ADR-0006协同:Rust 生态下 Redis 客户端(fred/redis-rs)成熟度高于rust-rocksdb的运维验证,一期用 Redis 也降低了语言选型的连带风险。
后果¶
正面
- 一期 Redis 仅支持 7.0.0+ standalone,启动须严格证明版本;必须配置 AOF +
noeviction;邮箱由 Redpanda 分发日志 驱动物化。DispatchProgress 默认不 GC,只有部署方显式提供并审计日志 replay-safety window 后才允许回收;Redis Cluster 配置必须拒绝启动。 - 客户端提交/
ClientDedup窗口固定为 2 小时;邮箱保留 30 天不等于历史保留期,MessageRecord历史只按retention_class生命周期处理。 TruncateBefore在 Redis 实现下是真删(ZREMRANGEBYSCORE或按天 keyEXPIRE), 比 ScyllaDB 阶段的纯逻辑裁剪(受 TWCS 墓碑约束,§18.1.2)更干净。- 无主备使 MailboxNode 成为分片化无状态计算节点,扩缩容与故障处理显著简化。
负面
- Redis 的 AOF everysec 有 1 秒持久性缺口。该缺口的可接受性完全依赖分发日志兜底: 崩溃后重放该窗口内的 dispatch 重新物化(确定性 event_id 保证同值覆盖)。 因此分发日志与 Redis 不能同时简化掉,§18.1 必须写明这一耦合。
- 漂移接管的 RTO 高于热备(冷启动需重建 Bitmap 与重算水位),
shard_epoch递增更频繁,mailbox_cursor_rebase_count的告警阈值需按漂移形态取值。 - 产品对外承诺的群规模上限从 10 万降为 1000,§2.2 需相应标注一期口径。
该上限已作为
max_members_per_group收进附录 B.6.1,由服务端强制执行。
替代方案及其否决理由¶
| 方案 | 否决理由 |
|---|---|
| 直接建目标档 | 交付周期长、初期资源利用率极低,且 ADR-0001 的切换判据在无真实负载时无法判定 |
简化形态但 lane_count = 1 |
省下的成本约等于零,却把升级路径焊死(需重建集群 + 换发全部游标) |
| 只定契约不锁档位、两种实现都不建 | 抽象层容易过度设计;且 §18.1 的四原语抽象已足够承载后续切换,不需要再加一层 |
一期直接上 mention_only 支持大群 |
千人群下 α 判据不成立(§10.2.3),读扩散只省少量写入却牺牲未读精确性 |
| Redis 承载正文与历史 | 正文按 365 天保留、体量远大于邮箱,且 §7.1 的 seq_bucket 分区设计针对宽列存储;正文仍用 ScyllaDB。2026-09-26 由 ADR-0018 修订:永久历史仍以 ScyllaDB 为准,但 history_hot_window(7 天)内的近期历史与提交状态留在 Redis,HistoryArchiver 异步归档——否决的是“Redis 永久承载历史”,不是“近期历史先在 Redis” |
复评条件¶
- 实测
R_avg超过 25,或 30 天邮箱日桶驻留超过 standalone Redis 的资源边界; - 产品决定放开群规模上限至 1000 人以上;
- standalone Redis 出现单 key 倾斜或大 key 问题,且按天分桶(§18.1)无法缓解;
- 分发日志保留期被下调到无法覆盖 Redis 的持久性缺口窗口——此时"负面"第 1 条的 兜底论证失效,必须重新评估;
- 漂移接管的实测 RTO 超过
mailbox_takeover_rto_target,需要重新引入热备。