外观
VMax 兼容性与迁移策略
1. 目标与边界
本文定义 API、协议、数据库、配置和部署升级的兼容规则。首版不支持跨实例联邦、多设备同时在线或服务端聊天历史恢复,迁移设计不得暗中引入这些能力。
- 来源 MUST:未知协议版本拒绝;协议升级通过能力协商建立新会话,不在原会话内降级或切换密码套件。
- 来源 MUST:切换服务器不能复用旧实例身份;各 Profile 的身份、数据库、Keychain 和通知路由隔离。
- 来源 MUST:数据库迁移可回滚或安全失败,并在 CI 与恢复演练中验证。
- 已验证事实:当前没有 API、schema 或发布构建,尚无历史版本需要真实迁移;以下规则应在首个实现前固化到 CI。
2. 版本轴
VMax 至少有五个独立版本轴,禁止用单一“App 版本”替代:
api_version:HTTP 资源与错误语义,例如/v1。protocol_version:E2EE 信封和会话能力。config_version:/.well-known/vmax/config签名配置结构。schema_version:PostgreSQL 迁移版本。backup_format_version:客户端备份包版本,跟随 ADR-007。job_version:PostgreSQL outbox 与 Redis 后台任务 envelope 版本。object_api_version:P1 S3 附件元数据与上传流程版本;MVP 不启用。
密码套件、canonical encoding、身份地址和备份格式的具体值均为待总控决策,本文只要求版本轴不可混用。
3. 兼容矩阵
实例发现响应公布支持的 API/协议范围和 feature flags。客户端连接前计算交集:
- 没有共同 API 主版本:阻断连接,提供升级/联系管理员提示。
- API 可用但协议无交集:允许查看实例信息,不允许创建身份或发消息。
- 未知可选能力:忽略并保持基础功能;未知必需能力:拒绝。
- 实例签名密钥或固定指纹变化:阻断,不自动迁移信任。
attachments=false:图片、视频、文件入口和上传调用全部禁用;这不是实例故障。
发布物必须维护 客户端版本 × API 版本 × 协议版本 × schema 版本 的测试矩阵,至少覆盖当前版与仍在支持窗口内的上一版。
4. OpenAPI 演进
4.1 /v1 内允许
- 新增可选响应字段。
- 新增客户端可忽略的枚举值,但只有 schema 明确允许 unknown 且客户端有安全默认行为时可用。
- 新增端点或可选能力。
- 放宽非安全边界的上限,同时保持客户端兼容。
4.2 /v1 内禁止
- 删除/重命名字段、改变字段类型或单位。
- 将可选字段改为必填、收紧已发布范围、改变状态码或错误码语义。
- 复用旧字段承载不同安全语义。
- 让旧客户端因未知值而静默降低认证、隐私或密码强度。
破坏性变化创建 /v2,并定义双版本期限、客户端最低版本和退役指标。未验证旧版流量归零前不得移除旧端点。
5. 协议升级
- 每个会话固定
protocol_version与密码套件;升级通过新会话建立并显示必要的安全状态变化。 - 服务端只能根据实例允许列表接受/拒绝 opaque 信封版本,不参与密码套件降级协商。
- 客户端对未知版本、篡改版本和不支持套件安全失败,不“尝试兼容”。
- 协议版本冻结前必须具备 canonical encoding、跨语言向量、降级攻击、乱序/重复/重放和状态迁移测试。
协议编码和套件仍属密码线路,待总控决策;本文不得被引用为生产安全证明。
6. 数据库迁移
6.1 expand-contract
- 备份并验证可恢复性。
- 部署兼容新旧 schema 的代码。
- 添加新表/字段/索引,避免长事务和阻塞式全表重写。
- 分批回填并记录检查点;失败可恢复。
- 切换读写路径并观察错误、锁、复制延迟和磁盘。
- 支持窗口结束后再移除旧结构。
6.2 回滚边界
- 代码回滚只有在旧二进制仍兼容新 schema 时允许。
- 数据迁移不可逆时,必须提供 forward-fix 或从已验证备份恢复的 Runbook,不能伪称“一键回滚”。
- 密文或协议状态不得在迁移中被重新编码,除非对应协议/编码 ADR 已冻结并有逐字节验证工具。
7. 部署滚动升级
- API/Worker 版本需要在混合运行窗口兼容相同 schema 和 outbox 语义。
- 先迁移数据库,再部署兼容 API,再部署 Worker;清理旧 schema 最后执行。
- Worker 任务必须版本化;旧任务由新 Worker 可安全识别、重试或送入隔离队列。
- 健康检查分别提供 liveness、readiness、dependency;迁移中不就绪实例应被摘流,但不能触发任务丢失。
7.1 PostgreSQL outbox 与 Redis 队列升级
- 先部署能读取旧、新
job_version的 Worker,再让 API/dispatcher 产生新版本任务,最后停止旧版本消费。 - PostgreSQL 保存任务业务状态;Redis 只保存可重建的投递。升级不得把 Redis backlog 当作唯一迁移来源。
- 未知 job 版本进入有界隔离队列并告警,不能确认成功、静默丢弃或无限热循环。
- Redis 清空/更换集群后,reconciler 从
outbox_events重建未完成任务;重建前后用稳定event_id去重。 - 队列库变更需要双读/双发布仅限短迁移窗口,并防止两个 Worker 对同一非幂等副作用重复执行。
7.2 S3 能力启用
S3 兼容存储已经选入总体架构,但媒体附件是 P1。MVP 发布始终声明 attachments=false,对象存储缺失不阻断核心 readiness。P1 启用顺序为:部署 bucket/lifecycle 和凭据、执行独立对象 schema 迁移、部署兼容服务端、打开实例能力、最后由兼容客户端展示入口。回退按相反顺序先关闭能力,不能先删除仍被引用的密文对象。
8. Profile 与实例迁移
切换服务器创建或导入目标实例专属身份,旧实例保留为独立 Profile 或经用户明确确认删除。禁止:
- 静默复制身份私钥到新实例。
- 复用旧实例 token、数据库、推送路由或固定指纹。
- 把相同显示 ID 当成跨实例同一身份。
- 将服务端备份描述为可恢复客户端明文历史。
身份地址映射和备份导入冲突规则分别跟随 ADR-003 与 ADR-007,待总控决策。
9. 数据保留策略迁移
- 缩短 TTL:只影响新信封,还是同时提前删除旧信封,需在管理员操作前明确显示影响。
- 延长 TTL:默认只影响新信封,不能复活已过期或已 ACK 数据。
- 降低配额:现有超限队列不得随意丢弃;建议阻止新入队并让其自然拉取/过期。
- 幂等窗口不能在仍支持的客户端重试期内缩短到 24 小时以下。
以上默认行为属于 ADR-009 的待总控决策,应通过迁移测试后接受。
10. 验证与发布门槛
- OpenAPI breaking-change 检查与生成代码 diff。
- N-1 客户端连接新服务端、新客户端连接 N-1 服务端的契约测试。
- 数据库从空库、上一版数据量级、失败中断和备份恢复四条路径演练。
- 游标、幂等记录、ACK、outbox 在混合版本和重启后不丢不重。
- 升级前后统计信封行数/字节、待清理数、幂等冲突、过期积压和 Push 失败,且指标不含敏感标识。
- Redis 队列升级验证任务不丢、允许重复但副作用幂等;S3 关闭的 MVP 环境必须完整跑通文本消息回归。
- 未完成跨版本、恢复和降级攻击测试前,不得宣布协议或发布兼容性已达标。
11. 待总控决策清单
| 决策 | 选项 | 建议 | 验证 |
|---|---|---|---|
| API 支持窗口 | 仅当前版 / 当前+上一主版本 | 首版至少当前+上一主版本的迁移测试,实际期限结合发布节奏 | 双版本流量回放与退役告警 |
| schema 工具 | Go migrate、Atlas、其他 | 选择可审计 SQL、支持锁超时与 CI 回放的方案 | 空库/升级/中断/重复执行 |
| 游标格式与轮换 | 签名 JSON / 加密二进制 | 版本化 opaque token,不暴露主键 | 篡改、过期、密钥轮换 |
| 协议状态迁移 | 原地升级 / 新建会话 | 新建会话,避免同会话换套件 | 跨版本向量和降级测试 |
| 备份版本兼容 | 仅当前 / 多版本读取 | 跟随 ADR-007,不在服务端线路冻结 | 恢复旧备份、篡改和冲突测试 |
| Redis 队列实现 | Go 库 / Redis Streams / 最小自建 | 优先成熟且许可可接受的实现,PG outbox 保底 | 清空、重启、重复、毒任务、滚动升级 |
| S3 提供方 | MinIO、云 S3、其他兼容实现 | 使用最小兼容子集,不写死供应商;MVP 关闭 | P1 再做兼容矩阵、生命周期与一致性测试 |