拉取模式适合中小型应用,冷启动快、写入压力小但读放大明显;推送模式读快实时好但写放大严重、取关清理难;混合模式按关注数或请求量动态分流。

拉取模式:用户访问时才查关注列表和最新动态
适合中小型社交应用,冷启动快、写入压力小,但读放大明显,尤其热门用户被大量访问时容易拖慢响应。
典型做法是:follows 集合存 user_id → followed_id 映射,查首页时先用 find({user_id: "u123"}) 拿出所有关注者 ID,再用 $in 去 posts 集合捞最近 50 条,最后按时间排序。
- 必须给
posts.created_at加倒序索引,否则$in+sort会全表扫 -
$in数组长度别超 500,否则查询计划退化,建议分批查(比如每次 20 个 ID) - 关注数上万的用户,
$in查询可能触发内存限制,MongoDB 默认maxTimeMS不够用,得显式调大 - 无法天然支持“已读标记”或“未读计数”,需要额外字段或双写逻辑
推送模式:发帖时就写进每个粉丝的 feed 集合
读快、实时性好,但写放大严重,发一条帖可能要插入上千次,且关注关系变更(取关)需反向清理历史 feed,容易漏删或卡顿。
核心结构是 user_feeds 集合,每条记录含 user_id(接收者)、post_id、created_at,查首页直接 find({user_id: "u123"}).sort({created_at: -1})。
- 务必复合索引
{user_id: 1, created_at: -1},否则分页(skip/limit)会极慢 - 发帖时批量写入要控制并发,避免瞬间打满连接池;可用
bulkWrite+ 分片写入 - 取关操作不是删
follows就完事,还得异步删user_feeds中对应post_id的所有行——漏删会导致“已取关还看到对方帖子” - 新用户关注别人时,要补推最近 N 条(比如 30 天内),这个补推任务不能阻塞主流程,得走队列
混合模式:热用户走推送,冷用户走拉取
折中方案,但判断“热”“冷”的阈值和切换时机很关键。实际中常按关注数或日活 feed 请求量动态分流。
例如:关注数 posts;≥1000 的,才触发推送逻辑。但要注意,这个阈值不是静态配置,得监控 user_feeds 写入延迟和 posts 查询耗时来自动调整。
- 分流逻辑不能放在应用层硬编码,得用 MongoDB 的
$expr或单独的user_stats集合存实时指标 - 切换模式时存在窗口期:比如用户刚涨粉到 1001,旧帖还没推,新帖开始推——首页会出现“断层”,得加兜底拉取(查不到推送内容时 fallback 到
$in) - 聚合管道里混用推送和拉取数据,
$unionWith性能差,建议应用层合并再排序
最容易被忽略的点:时间线一致性与分页陷阱
MongoDB 没有全局事务保障多文档写入的原子性,推送模式下,如果用户 A 同时发帖、B 取关 A,feed 列表可能出现“看到帖子但没看到取关生效”,或者分页跳过内容。
- 不要依赖
skip/limit做分页,用created_at+post_id做游标分页({$lt: {created_at: ..., post_id: ...}}) - 推送失败的帖子,不能只靠重试,得有对账脚本定期比对
posts和user_feeds计数 - 拉取模式下,
$in查出的帖子如果被删除或设为私密,得在应用层过滤,不能只靠数据库权限 - 所有时间字段统一用 UTC,别混用本地时区,否则排序错乱










