SWE / Infrastructure · System Design

Slack式聊天:群成员、消息顺序、媒体与删除

将持久消息和实时通知分开,删除也需要传播到多设备。

题目重点:将持久消息和实时通知分开,删除也需要传播到多设备。

题意与假设

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

  • 支持私聊/群聊、建群/加成员、富媒体、群或个人通知、消息删除。训练按conversation维持服务器sequence顺序,无全局消息总序。
  • 发送需要当前成员权限,加入者能否读旧消息另定;删除采用tombstone并停止新读,已被用户下载内容无法仅靠服务端撤回。规模与保留期先确认,以下数字是容量练习。

解题路线

  1. 数据为Conversation(id,tenant,type)、Membership(conversation,user,join_seq/leave_seq)、Message(conversation,seq,message_id,author,body/media_ref,deleted,version)。POST message带client_request_id,数据库唯一约束(conversation,sender,request_id)防超时重发重复。
  2. 消息分片按conversation,分配seq并写消息+outbox原子提交,成功ACK表达持久化。实时gateway从outbox事件向在线设备推送,断线用last_seq拉取补齐;WebSocket发送成功不能替代历史存储。
  3. 小群推成员inbox,大群频道只推活跃订阅设备并由客户端pull历史。大群1条消息×百万成员的写放大不能藏在消息QPS里;用户通知也可合并提醒,不逐条扇出。
  4. 媒体直传对象存储,获得有权限的media_ref后绑定消息;扫描状态/上传完成状态影响是否可展示。删除授权后写version更高的tombstone及事件,缓存/搜索索引异步更新,读时再核对删除和权限避免旧缓存显示。
  5. 多设备设备级cursor独立,read receipt与deliver receipt分开。成员变更会影响历史访问,缓存key需包含权限版本;跨region单conversation主写降低排序冲突,代价是异地写延迟。

手动走一遍

发送m成功但ACK丢失,重试同request_id返回同message_id/seq,不写第二条。设备离线期间m被删除,恢复从seq补齐时会收到删除事件;历史API不能仅返回旧body。容量练习每天1亿条、正文1KiB约100GB/day原始正文,未含副本、索引、媒体;3副本约300GB/day,媒体通常主导。

规模与取舍

发送基础写O(1),实时fanout O(在线订阅设备数);历史页O(page_size),大群inbox写放大须单列。按对话分片会有热点,单超大对话顺序分配器吞吐是瓶颈。

易错点与追问

容易失分的边界

  • 消息持久化与推送双写无outbox会丢通知。
  • 把读游标当已送达游标会跨设备覆盖状态。
  • 声明删除能保证所有已下载拷贝消失。

继续深挖

  • 超大单频道如何保持局部顺序并避免单分片热点?
  • 删除搜索结果、通知预览与媒体引用时,如何表达传播延迟及验收?
✓已复制微信号!