Staff SWE / SWE · Phone / VO System Design

Databricks Chat App:删消息/会话与并发发送的线性化边界

把会话状态、发送、删除、缓存和实时投递串成一条可解释的事务链,处理 send/delete 竞态。

题目重点:把会话状态、发送、删除、缓存和实时投递串成一条可解释的事务链,处理 send/delete 竞态。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练版多成员会话,send 成功表示消息已持久化并获 per-thread 单调序号;delete message 是对所有成员不可见的 tombstone,delete thread 是禁止新 send 的关闭操作。题目未规定删除是本人视图隐藏、全局撤回还是合规物理删除,必须当轮澄清。
  • 客户端可断线后按序号补拉;低延迟实时推送允许重复消息,客户端按 message_id 去重。

解题路线

  1. schema 可用 thread(id,status,epoch,last_seq)、membership(thread_id,user_id,role)、message(thread_id,seq,id,sender,body,state,created_at),索引按 (thread_id,seq)。发送前核成员权限,并在一个事务里检查 thread.status=active、锁定/条件更新 epoch 与 last_seq、写消息和 outbox;send 的线性化点为事务提交。
  2. 删除会话在同一 thread 行上把 status 改为 deleted 并递增 epoch,提交为删除的线性化点。若 send 先提交,消息属于删除前历史且之后按删除口径不可见;若 delete 先提交,send 的状态检查失败。不能让两个操作分别只改各自表而发生‘已删除会话里新消息’。
  3. 删单条消息写 tombstone 并递增 message 版本;对正在读取/推送的客户端发送删除事件。若需要硬删除,另走合规清理任务并确保缓存、搜索索引和备份的保留/清理策略一致,不能简单从数据库删除就宣称全域消失。
  4. 缓存以 thread_id/seq 范围与权限范围为键;缓存 miss 从持久库读并以版本/epoch 防旧值回填。分区若按 thread_id 可保一会话局部顺序,但大群热点和跨区用户延迟另需评估。应用内存缓存易失且跨实例不共享,是否加共享 cache 取决于流量。
  5. outbox 异步推送消息/删除事件,客户端用 seq/epoch 检测缺口后补拉;事件重试带 idempotency key,推送到达顺序不等于事务提交顺序,客户端不得靠收到事件的时间断定最终状态。

手动走一遍

用户 A send M 与管理员 delete T 同时到达:两者竞争同一 thread 状态行。若 send 先提交,M 得 seq=42,delete 随后把 T 标删除;客户端最终展示删除状态。若 delete 先提交,send 读到非 active 并返回冲突,不能产生 seq=42 的新消息。缓存中旧 active 状态需用 epoch 阻止回填。

规模与取舍

单会话发送是少量索引/行事务,逻辑上 O(1) 加持久 IO;读取最近 K 条约 O(log N+K),N 为该会话消息数。按 thread_id 分区时全局吞吐随会话分布扩展,超热会话仍是单分区热点;题目未明确 QPS、延迟或保留期,不能杜撰容量结论。

易错点与追问

容易失分的边界

  • send 与 delete 分开更新,无共同事务/版本条件。
  • 删 message 只删主库,不清缓存/索引/推送状态。
  • cache miss 后旧值回填覆盖新删除。
  • 把 WebSocket 送达当持久提交。
  • 未区分全局删、用户隐藏、撤回和合规硬删除。

继续深挖

  • 删除与发送并发时应允许哪一个先线性化?
  • 成员权限撤销和 send 竞态怎么处理?
  • 缓存 miss 与 delete 同时发生如何防旧数据回填?
  • 超大群会话如何降低单 shard 热点?
  • 客户端离线后怎样补齐删除事件?
✓已复制微信号!