跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

怎么设计MongoDB的浏览足迹_有限容量数组与固定集合Capped Collection

应优先使用 $push 与 $slice 组合维护用户足迹数组,原子性保证最新 N 条;避免先 push 再 pop 或应用层截断;Capped Collection 不适用于用户级足迹,因其不支持按用户隔离且缺乏查询灵活性;高频写入需警惕文档迁移导致的碎片与写放大;时间范围查询应改用独立集合 + 复合索引 + TTL。 用
$push
+
$slice
维护用户足迹数组,别硬塞满再删 MongoDB 里存最近 N 条浏览记录,最常见做法是字段设为数组,每次用
$push
加新记录,同时用
$slice
限制长度。这不是“模拟栈”,而是原子级裁剪——写入和截断一步完成,不会出现中间态超长。 常见错误是先
$push
再
$pull
或
$pop
,结果并发写入时漏删、重复删、甚至把整个数组清空。也有人想用应用层判断长度再决定是否删老数据,这既慢又不一致。
db.users.updateOne({ _id: userId }, { $push: { history: { $each: [newRecord], $slice: -50 } } })
—— 保留最新 50 条,负数表示从尾部截取 注意
$slice
必须和
$each
同级,单独写
$push: { history: newRecord }
再加
$slice
无效 数组字段必须已存在(哪怕为空数组),否则首次插入会失败;建模时建议初始化
history: []
别用 Capped Collection 存用户级足迹,它不支持按用户隔离 Capped Collection 看似“自动淘汰旧数据”,但它是整个集合级别 FIFO,所有文档共用一个写入顺序。你没法让张三的足迹只影响张三的容量,李四的操作可能把张三刚写的记录顶掉——这不是“用户足迹”,这是“全站乱序日志池”。 除非你在做全站最近 10 万次访问快照(且不要求归属到具体用户),否则 Capped Collection 在足迹场景下是误用。它连
find()
都不能带复杂查询条件,
sort()
和
skip()
效率极差,还禁用
$set
更新已有文档。 Capped Collection 的
size
是字节上限,不是文档数上限;文档大小波动大会导致实际条数不稳定 无法创建二级索引(除
_id
外),查某用户最近 10 条得全表扫 一旦集合满,新插入直接覆盖最老文档,且无任何回调或通知机制 当足迹要查时间范围或带筛选条件时,数组方案立刻变重 如果只是展示“最近看过什么”,数组 +
$slice
足够。但一旦要查“昨天看过的商品”“3 天内未重复的类目”,数组就扛不住了:MongoDB 无法高效在数组子元素上建带时间范围的复合索引,
$elemMatch
只能加速存在性判断,对范围查询帮助有限。 这时该切回普通集合,用
userId
+
timestamp
建复合索引,并配合 TTL 索引自动过期(比 Capped 更可控)。
db.user_history.createIndex({ userId: 1, timestamp: -1 }, { name: "user_time" })
支持按用户+时间倒序分页
db.user_history.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 })
让 MongoDB 自动删过期文档 TTL 删除是后台线程异步执行,延迟通常在 60 秒内,比手动定时任务更轻量 数组方案的隐含成本:文档增长与 WiredTiger 页面分裂 每次
$push
+
$slice
都是原地更新(in-place update),但如果数组扩容后超出原存储页大小,WiredTiger 会触发文档迁移——复制整份数据到新位置,旧空间标记为可复用。高频足迹写入下,这会导致写放大和碎片。 实测中,单文档超过 4MB(MongoDB 文档上限)不是问题,但频繁迁移会让 oplog 增大、副本同步延迟上升,尤其在副本集 secondary 节点资源紧张时。 预估单条足迹平均 200 字节,50 条约 10KB,安全;但若混入截图 base64 或完整 referer,单条破 KB 就得警惕 避免在足迹数组里存冗余字段,比如重复存
userName
;只留必要 ID 和时间戳,详情走关联查询 如果发现
db.collection.stats().wiredTiger.block-manager.fileSize
明显大于
dataSize
,说明碎片已高 实际选型没那么多纠结:用户维度、需归属、要查时间——用独立集合 + TTL;纯前端快速展示最近几条——数组 +
$slice
;Capped 只留给审计日志、埋点缓冲这类不要求语义、只要顺序的场景。

相关文章