Skip to content

VMax 服务端逻辑数据模型

1. 设计原则与状态

  • 来源 MUST:PostgreSQL 是权威存储;服务端只保存路由、公开材料、密文信封和必要运维状态。
  • 来源 MUST:私钥、消息正文、会话密钥、消息预览、主密码、假密码和备份密码不得入库。
  • 来源 MUST:队列按 recipient_device_id 隔离配额;ACK 与删除状态事务化;写接口幂等结果保留 24 小时。
  • 已选架构:Redis 承载后台任务队列,但每个需要可靠执行的业务意图先写入 PostgreSQL outbox;Redis 记录是可重建投影,不属于权威业务数据。
  • 范围边界:S3 兼容对象模型属于 P1 附件能力;MVP 不创建上传会话、不接受图片/视频/文件对象。
  • 建议方案:使用 UUID/ULID 类公开标识、数据库内部 surrogate key 与行级约束;实际标识编码待 ADR-003/ADR-004 决策。
  • 已验证事实:当前没有迁移文件或数据库实现,以下为逻辑模型,不代表已部署 schema。

VMax 服务端核心逻辑实体以及 PostgreSQL outbox 到 Redis 任务的关系

图中实体关系是审阅摘要,字段、约束和待决物理结构仍以下文为准。查看图示说明

2. 核心实体

2.1 instances

字段语义约束
instance_id实例公开稳定标识唯一;格式待总控决策
signing_public_key实例长期签名公钥只存公钥;版本化
policy_version注册、配额、保留策略版本单调递增
created_at创建时间数据库时间

实例签名私钥必须由独立 secret 管理,不得作为普通表字段、镜像层或数据库备份内容保存。

2.2 identities 与 devices

关键字段约束与隐私
identitiesidentity_id, public_address, identity_public_bundle, status不保存姓名、邮箱、手机号、私钥;地址格式待 ADR-003
devicesdevice_id, identity_id, device_public_key, status, revoked_atMVP 每身份一个活动设备;模型允许未来扩展但不启用多设备能力
prekeysdevice_id, key_id, public_key, state, reserved_at, consumed_at只存公钥;一次性消费必须原子化;编码待 ADR-004

预密钥状态建议为 available -> reserved -> consumed,并为超时 reservation 提供回收或明确消耗策略。最终协议语义属于密码协议线路,待总控决策

2.3 auth_challenges 与 token 状态

auth_challenges 至少保存 challenge 的哈希、设备、实例/域上下文、用途、过期时间和消费时间。challenge 默认 60 秒且只能成功消费一次。长期 bearer token 不允许;刷新模型与撤销粒度待认证设计冻结。

2.4 envelopes

建议字段:

text
envelopes
  envelope_pk            database internal key
  message_id             sender generated public id
  recipient_device_id    routing target
  protocol_version       opaque envelope version gate
  envelope_bytes         bytea
  envelope_size          validated byte count
  padding_class          bounded enum or null
  accepted_at            database receive time
  expires_at             min(instance policy, sender requested TTL)
  acked_at               nullable
  cleanup_after          nullable

关键约束:

  • 唯一键至少覆盖接收设备和 message_id,避免不同设备或未来多设备语义被提前错误绑定。
  • envelope_size 由服务端实际字节数计算,不信任客户端声明。
  • expires_at > accepted_at,且不超过实例策略上限;默认 7 天,可配置范围 1–30 天,发送端可请求更短 TTL。
  • ACK 不立即依赖物理删除成功;事务先记录 acked_at/cleanup_after,清理任务最长 1 小时内删除。
  • 服务端不得解析 envelope_bytes 内的正文或生成预览;只验证外层版本、长度和路由字段。

2.5 idempotency_records

text
idempotency_records
  actor_scope_hash
  operation
  idempotency_key_hash
  request_fingerprint
  response_status
  response_body_safe
  resource_reference
  created_at
  expires_at
  • 唯一键为 (actor_scope_hash, operation, idempotency_key_hash)
  • 相同键与相同请求指纹返回首次结果;相同键配不同指纹返回稳定冲突错误。
  • response_body_safe 只能保存可安全重放的最小响应,不复制密文或令牌。
  • 记录保留至少 24 小时;过期后的行为必须在接口契约中声明,不能默认为永久去重。

2.6 message_acks 与游标

MVP 可将 delivered ACK 状态放在 envelopes;若需要事件审计或批量性能,可拆成 message_acks。无论物理形态如何,重复 ACK 必须成功且不得延长数据保留。

拉取游标应是服务端签名或不可篡改的 opaque token,编码稳定排序键,例如 (accepted_at, envelope_pk)。客户端不得依赖或构造数据库主键。

2.7 push_routes 与 outbox_events

保存内容禁止内容
push_routes随机 wake_route_id、设备关联、模式、Gateway 引用、状态消息正文、联系人名、会话 ID
outbox_eventsevent_id、聚合类型/引用、任务类型、版本、状态、尝试次数、下次重试时间、创建时间消息正文、私钥、完整密文副本、发送方可读身份

官方 Gateway 是否直接保存 APNs token、以及 token 的加密/分离存储方式,属于 ADR-006 的待总控决策

outbox_events 建议状态为 pending -> published -> completed,另有 retry_wait/dead_letter。业务事务只创建 pending;dispatcher 发布带相同 event_id 的 Redis job;Worker handler 以 event_id + task_type + task_version 幂等执行。Redis job payload 只带定位所需的内部引用和版本,不复制消息密文、token 或用户可读字段。

2.8 object_uploads 与 encrypted_objects(P1)

只有启用附件范围时才启用对象存储相关迁移和端点:

  • object_uploads:短期上传会话、所有者设备、预期密文字节数、对象 key、过期时间和状态。
  • encrypted_objects:对象引用、密文字节数、客户端提供的密文哈希、TTL、完成/删除状态。

不得保存文件名明文、MIME 推断结果、缩略图明文或文件密钥。对象 key 使用服务端随机值,不嵌入地址、设备或原文件名。S3 生命周期与数据库清理通过 outbox 对账,避免孤儿对象;MVP schema 可以保留独立的未来迁移文件,但默认部署不得执行或启用附件表。

2.9 audit_events

只保存稳定事件类型、请求 ID、短期 actor 哈希、结果、配置版本和时间。不得保存密文全值、完整地址/IP 长期历史、token、APNs token、备份路径或任何口令材料。审计事件不是聊天历史。

3. 索引与并发

  • 拉取:(recipient_device_id, accepted_at, envelope_pk) 的活动信封索引。
  • 过期清理:按 expires_at 的部分索引;分批 FOR UPDATE SKIP LOCKED 处理。
  • ACK 清理:按 cleanup_after 的部分索引。
  • outbox:按 (state, next_attempt_at, event_id) 建立领取索引;dispatcher 使用短事务与 FOR UPDATE SKIP LOCKED,不得在持锁期间调用 Redis。
  • 配额:每设备待收数量与字节必须在同一事务内检查和更新,避免并发超配。实现可采用配额计数行锁或可验证的聚合查询。
  • 一次性预密钥:通过条件更新/行锁确保只有一个请求取得可消费材料。

4. 生命周期与删除语义

数据默认生命周期删除语义
未送达密文信封7 天,策略可设 1–30 天到期后后台清理;发送端可请求更短 TTL
已 ACK 信封立即进入清理,最长 1 小时重复 ACK 仍成功
一次性预密钥成功消费后立即失效协议状态要求待总控决策
challenge60 秒或成功使用单次消费
幂等记录24 小时仅保留安全响应摘要
限流状态不超过 24 小时不得用于长期画像
脱敏服务日志默认 7 天由运维策略自动清理
Redis 任务短期直到完成/超时可由 PostgreSQL outbox 重建,不作为业务保留副本
S3 密文对象P1,跟随附件 TTL/引用MVP 不启用;具体保留待附件范围批准

逻辑删除、备份副本和底层介质可能延迟物理清除。对外只能承诺定义的逻辑删除与保留策略,不得宣称可验证物理擦除。

5. 数据库迁移约束

  • 每次迁移具有唯一版本、前置版本和数据兼容说明;CI 在真实 PostgreSQL 上验证升级和回退/安全失败。
  • 在线变更采用 expand-contract:先新增可空字段/新表,再双读或回填,最后在旧版本退场后收紧约束。
  • 不重解释既有 protocol_version、状态枚举或游标含义;需要改变语义时创建新字段或新 API 主版本。
  • 大表回填限速、可恢复,并监控锁等待、复制延迟和磁盘余量。
  • 备份恢复后必须验证迁移版本、信封数量/字节、过期队列和幂等约束。
  • outbox 与 Redis job schema 分别版本化;滚动升级期间新 Worker 必须能安全识别旧任务,未知任务版本进入隔离而不是丢弃。
  • P1 对象表和 S3 lifecycle 作为独立能力迁移,不能随 MVP 核心 schema 默认启用。

6. 待总控决策

  1. 身份与设备标识格式:UUID、ULID 或定长随机 bytes。建议以不可枚举随机 bytes 为模型、展示编码跟随 ADR-003;验证碰撞、排序泄漏和跨语言解析。
  2. 协议对象编码:数据库只保留版本与 opaque bytes;canonical encoding 跟随 ADR-004;验证跨语言固定向量与非规范输入拒绝。
  3. 备份格式:数据库备份与客户端 .vmaxbak 是不同边界;客户端格式跟随 ADR-007;验证不可互相冒充或误导恢复能力。
  4. 配额具体值:需要压测与成本模型后冻结。建议至少支持每设备信封数、总字节、单信封字节三维限制;验证并发超限不会影响其他设备。
  5. Redis 队列库与 job envelope:选择前审查许可证、维护状态、确认语义与失败恢复;job envelope 不复用 E2EE canonical encoding。
  6. 附件对象元数据与保留:在 P1 独立立项后决定;不得因已选 S3 架构把图片、视频或文件并入 MVP。

系统初步设计 · 待决事项不代表批准 · 安全方案尚未完成独立审计