不能直接用insertone,因其每秒2–5次滑动将导致3000+qps写入,迅速打满主节点写锁,引发writeconflict及延迟超80ms;应改用bulkwrite批量聚合写入,并优化索引与session设计。

为什么不能直接用 insertOne 记录每次滑动
短视频推荐场景下,单个用户每秒可能产生 2–5 次滑动(swipe_up、swipe_down、pause、skip),高频写入会迅速打满副本集主节点的写锁,尤其当集合未建好索引或使用默认 _id ObjectId 时,insertOne 的写放大和磁盘 IO 压力会明显拖慢实时特征计算链路。真实压测中,未优化的插入在 3000 QPS 以上就出现 WriteConflict 错误或平均延迟跳升至 80ms+。
实操建议:
- 改用
bulkWrite批量聚合写入,客户端本地缓冲 100–500ms 内的滑动事件(按user_id+session_id分组),再统一提交 - 禁用
_id自动生成,显式传入{ _id: `${user_id}_${timestamp_ms}` },避免 ObjectId 生成开销和随机写放大 - 写入前对
video_id、timestamp、action_type做轻量校验(如长度、枚举值),过滤掉明显脏数据,减少无效写入
如何让日志既支持实时查询又不拖慢写入
核心矛盾在于:实时推荐服务需要秒级查出「用户最近 30 秒内跳过多少次」,但全字段索引会让写入变慢;而无索引又导致 find({ user_id: "u123", action_type: "skip", timestamp: { $gt: ... } }) 全表扫描。
实操建议:
- 只建两个必要索引:
db.interaction_log.createIndex({ user_id: 1, timestamp: -1 })和db.interaction_log.createIndex({ video_id: 1, action_type: 1, timestamp: -1 })—— 前者支撑用户行为流拉取,后者支撑内容维度归因,其余组合查询走内存过滤 - 用 TTL 索引自动清理:
db.interaction_log.createIndex({ timestamp: 1 }, { expireAfterSeconds: 86400 }),确保日志只保留 24 小时,避免集合无限膨胀 - 禁止在日志集合上执行
$text或正则查询;模糊匹配类需求(如“类似视频”)应由离线 ETL 同步到专用分析库处理
如何设计 session_id 避免跨端/断网丢失
用户在 WiFi 切 4G、App 杀后台重进、多设备登录等场景下,传统基于前端时间戳或 UUID 生成的 session_id 容易断裂或重复,导致「一次完整浏览流」被切碎成多个 session,影响完播率、跳出点等关键特征计算。
实操建议:
- 后端生成
session_id:客户端上报首条滑动时附带设备指纹(device_id+app_version+os),服务端用HMAC-SHA256(device_id + app_version + os + Date.now().toString().slice(0,8))生成确定性 session ID,并缓存 30 分钟供续接 - 客户端本地持久化
last_session_id和last_active_ts,若 60 秒内无新事件且时间差 > 10 分钟,则主动触发新 session - 日志中必须保留
client_timestamp(前端采集毫秒时间)和server_timestamp(服务端写入时间),二者差值超过 5 秒即标记为「时钟异常」,后续用于校准用户停留时长
为什么推荐用 changeStream 而不是轮询拉取日志
推荐系统特征服务若用定时任务每 2 秒跑一次 find({ timestamp: { $gt: last_ts } }) 拉取新日志,会出现重复消费(last_ts 更新失败)、漏消费(查询间隙)、高延迟(平均 1.2 秒滞后)等问题;而 changeStream 可以精准捕获增量变更,延迟稳定在 100–300ms。
实操建议:
- 启动 changeStream 时务必加
{ fullDocument: "updateLookup" },否则replace或update操作无法拿到完整文档,影响特征拼接 - 监听范围限定到具体分片键(如
{ "fullDocument.user_id": { $regex: "^u" } }),避免监听整个集合引发内存溢出 - 每个消费进程维护自己的
resumeToken并落盘(如写入 Redis),重启后从断点继续,不要依赖startAtOperationTime—— 它在分片集群中不可靠
真正难的不是存下这些滑动,而是让每一条都对得上「用户此刻在想什么」;时钟偏差、session 断裂、changeStream 中断重连——这些细节不盯住,实时特征就只是看起来很快。











