MongoDB近实时跟踪聚合:Accounts集合数据统计需求
解决千万级Accounts集合实时统计问题的替代方案
我之前处理过类似的千万级数据实时统计需求,直接跑聚合查询确实会因为数据量太大导致响应延迟,完全满足不了UI实时更新的要求。下面是几个实战中好用的替代方案:
1. 预计算统计结果到专用集合
这是最常用的方案,核心思路是把实时需要的统计结果提前计算好存在一个单独的集合里,UI直接查询这个小集合就能拿到数据,速度毫秒级。
- 首先创建一个统计集合,比如
account_type_stats,结构大概是:{ "_id": "TYPE1", // 用type作为_id,方便快速查询 "count": NumberLong(1000000), // 该类型账户数量 "total_amount": NumberDecimal("123456789.00") // 该类型账户amount总和 } - 更新统计的方式有两种:
- 定时批量更新:适合对实时性要求不是极致(比如允许1-5分钟延迟)的场景,用MongoDB的定时任务(Atlas可以用Scheduled Triggers,自建集群可以用crontab配合脚本跑聚合),定期跑一次聚合把结果同步到统计集合。
- 实时增量更新:如果要求极致实时,就用MongoDB的Change Streams监听Accounts集合的增、删、改事件,每次数据变化时直接更新统计集合的对应字段:
- 插入新账户:找到对应type的统计文档,
count += 1,total_amount += 新账户的amount - 删除账户:找到对应type的统计文档,
count -= 1,total_amount -= 被删账户的amount - 更新账户:如果type字段变更了,要分别调整旧type和新type的统计;如果amount字段变更了,计算差值后更新对应type的
total_amount
- 插入新账户:找到对应type的统计文档,
2. 用内存数据库缓存统计结果
如果UI对响应速度要求极高(比如毫秒级返回),可以把统计结果存在Redis这类内存数据库里,配合上面的预计算方案使用:
- 每次更新
account_type_stats集合的时候,同时更新Redis中的对应key(比如account:stats:TYPE1) - UI直接从Redis读取统计数据,完全不用碰MongoDB,速度快到飞起
- 为了防止Redis数据丢失,可以定期把Redis里的统计数据同步回MongoDB的统计集合,或者依赖Change Streams重新构建Redis数据
3. 利用MongoDB物化视图
MongoDB 4.2及以上版本支持物化视图,它可以定期刷新聚合结果,比直接跑全量聚合要高效很多,因为它是基于上次的视图结果增量更新(或者按配置的频率重新生成):
- 创建物化视图的示例命令:
db.createCollection("account_type_mv", { viewOn: "Accounts", pipeline: [ { $group: { _id: "$type", count: { $sum: 1 }, total_amount: { $sum: "$amount" } } } ], refreshInterval: 60 // 每60秒刷新一次,单位秒 }) - 这个方案适合对实时性要求中等(允许几十秒延迟)的场景,配置好刷新间隔后,UI直接查询物化视图就行,不用自己写定时任务
4. 分片集群优化聚合查询
如果你的MongoDB已经是分片集群,可以通过优化分片策略来提升聚合速度:
- 把
type字段设置为片键,这样聚合时MongoDB可以在每个分片上先做本地分组统计,再把各分片的结果合并,大幅减少数据传输量 - 配合
allowDiskUse: true参数,让聚合可以使用磁盘临时空间处理大数据量,但这个方案还是不如预计算快,适合无法修改架构的临时优化
总结
如果要满足UI实时更新的需求,预计算集合+Change Streams实时更新+Redis缓存是最优组合,既保证数据的实时性和准确性,又能让UI拿到极速的响应。如果实时性要求稍低,物化视图是最省心的方案。
内容的提问来源于stack exchange,提问作者Kevin Day
相关产品推荐
相关产品推荐

