You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB通知Schema无界数组扩展性问题如何优化重构

问题根因

你当前的Schema属于典型的MongoDB无界数组反模式:单条通知文档的receivers和readBy数组会随着收件人规模线性膨胀,等你发全量通知时单文档数组就要存35万个ID,不仅很快会触碰到MongoDB单文档16MB的硬大小限制,数组更新、查询的索引开销也会随着数组长度指数级上升,这就是Atlas性能顾问直接报警的核心原因。

分场景建模方案

不要用单一模型覆盖所有通知场景,按通知覆盖的用户规模拆分方案,能在实现成本和性能之间拿到最好的平衡。

小范围定向通知(单聊、小群@、管理员点对点通知,收件人<1000)

这类场景不需要完全推翻原有设计,只要把无限增长的已读数组合拆出去即可:

  • 保留Notification主文档的receivers字段,但是在业务代码层加硬校验,数组长度超过1000就自动走广播通知逻辑,禁止写入超长数组
  • 彻底删除Notification文档内嵌的readBy数组,单独新建NotificationReadStatus集合存储已读状态,Schema如下:
{
  notificationId: { type: Schema.Types.ObjectId, ref: 'Notification', required: true },
  userId: { type: Schema.Types.ObjectId, ref: 'User', required: true },
  readAt: { type: Date, default: Date.now }
}
// 必须建复合唯一索引,避免重复写入已读状态,同时加速查询
db.notification_read_status.createIndex({ notificationId: 1, userId: 1 }, { unique: true })
// 按用户查已读记录的索引
db.notification_read_status.createIndex({ userId: 1, notificationId: 1 })
  • 查用户未读通知时,只需要找收件人包含当前用户、且在notification_read_status中没有对应记录的通知即可,不需要扫描大数组。

大范围/全量广播通知(全站公告、全体已验证用户推送,收件人>1000)

这类场景绝对不能给每个收件人生成关联记录——35万用户发一条全量通知就要写35万条关联数据,写流量会直接打满数据库,用「范围标记+拉取时匹配」的模式实现:

  • 扩展Notification主文档结构,去掉无长度限制的receivers数组,新增通知类型和受众筛选字段:
{
  message: { type: String, required: true },
  sender: { type: Schema.Types.ObjectId, ref: 'User' },
  // 通知类型枚举
  type: { type: String, enum: ['targeted', 'broadcast'], required: true, index: true },
  // 仅定向通知存收件人ID,加长度校验
  receivers: {
    type: [{ type: Schema.Types.ObjectId, ref: 'User' }],
    validate: v => !v || v.length <= 1000
  },
  // 仅广播通知存受众匹配规则,比如给所有已验证用户发通知就存 { verified: true }
  audienceRule: { type: Object, default: null },
  createdAt: { type: Date, default: Date.now, index: true }
}
  • 已读状态完全复用上面的notification_read_status集合,不需要额外改结构
  • 拉取用户通知列表时,合并两类结果即可:
    • 定向通知:type: 'targeted' 且 receivers 包含当前用户ID
    • 广播通知:type: 'broadcast' 且 audienceRule 匹配当前用户属性(比如用户已验证就匹配audienceRule.verified = true的通知)
    • 合并结果后关联已读状态表,标记哪些通知已读,按createdAt倒序做游标分页返回。
关键性能注意事项
  • 不要给广播通知预生成用户收件记录,这是订阅类通知模型最常见的性能坑:不仅写放大极其严重,还会浪费数倍的存储空间。
  • 通知列表必须用createdAt+_id做游标分页,不要用skip+limit,数据量超过10万之后翻页性能差距可达两个数量级。
  • 做冷数据归档:默认只给用户返回近3个月的通知,更早的历史通知迁移到归档集合,降低热集合的数据量。
  • 在User文档上冗余unreadCount字段,发新通知、用户标记已读时用原子操作增减计数,不要每次实时count未读记录,接口响应速度能提升10倍以上。

内容的提问来源于stack exchange,提问作者Eazy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 15:18:19