可行但仅适用于中小规模数据,因$lookup内存限制和笛卡尔积膨胀;应转向轻量级Item-Item共现统计,全程内存计算且无需外部查询。

直接用聚合管道做协同过滤推荐是可行的,但必须接受它只适合中小规模行为数据——单次聚合超过 50 万文档就容易触发 $lookup 内存限制或超时,别硬扛。
为什么不能直接用 $group + $lookup 做全量用户相似度?
聚合管道不是 MapReduce,它没有分布式 shuffle 阶段,所有中间结果都在单节点内存里流转。你看到的“商品推荐”示例里那个 $lookup 关联 purchases 集合的操作,实际依赖的是 localField 是数组、foreignField 是标量的“反向关联”技巧,本质是笛卡尔积展开,极易爆炸:
- 1 万个活跃用户,每人平均买过 20 个商品 →
$push出来的purchases数组平均长度 20 - 后续
$unwind会把这 1 万条文档撑成 20 万条 - 再
$lookup匹配全量purchase集合(假设 500 万条),即使加了{"product_id": 1}索引,也得对每条展开记录做一次索引查找 —— 这不是慢,是不可控
真正能落地的聚合推荐模式:基于共现的轻量级 Item-Item
放弃“算用户相似度”,转而统计“哪些商品总被同一用户购买”,这是聚合最擅长的场景,且天然规避 $lookup 膨胀:
- 先用
$group按user_id聚合出每个用户的购买商品集合:{$group: {_id: "$user_id", items: {$addToSet: "$product_id"}}} - 再用
$unwind+$unwind把items数组两两配对(注意加$expr: {$lt: ["$$item1", "$$item2"]}去重) - 最后
$group统计每对商品的共现次数:_id: {a: "$pair.a", b: "$pair.b"}
这个流程全程不查外部集合,纯内存计算,100 万行为数据通常在 2 秒内完成。输出可直接存入 item_cooccurrence 集合,供线上服务查表推荐。
聚合阶段顺序和索引必须严格匹配
电商行为集合(如 purchase)若没建对索引,聚合根本跑不起来:
-
$match必须放在管道最前面,且字段要命中索引 —— 比如查“最近 7 天购买”,purchase_date字段必须有{purchase_date: 1}索引 -
$group的_id字段如果含嵌套路径(如"$shipping_address.city"),对应字段在索引中必须显式展开,不能只靠{"shipping_address": 1} - 涉及
$unwind的数组字段,建议在写入时就控制长度(如单次订单最多 50 件商品),否则展开后文档数不可预估
聚合管道做推荐,核心不是堆操作符,而是把问题约束到它能高效处理的维度上:共现、频次、时间窗口。一旦需要向量运算、余弦相似度或实时更新用户画像,就得切到应用层或专用引擎——MongoDB 的聚合,强在快,不在深。

















