Web Push Subscription对象数据库存储数量最优方案咨询(MongoDB Atlas有限存储场景)
处理Web推送订阅存储:平衡空间限制与用户体验
嘿,作为刚踏入Web推送领域的开发者,你的这个问题真的很贴合实际——尤其是在MongoDB Atlas免费版只有512MB存储空间的情况下。咱们来拆解一下你的两个方案,再给出更优的平衡思路:
先聊聊两种场景的优缺点
场景1:保留所有设备的订阅记录
- ✅ 优点:用户在任何已授权的设备/浏览器上都能收到通知,体验完整,不会出现“换了旧设备收不到消息”的困惑;
- ❌ 缺点:无效订阅(用户弃用设备、更换浏览器、清除缓存导致的订阅失效)会持续占用空间,而且推送时给失效的endpoint发请求也是浪费资源。
场景2:只保留最新的设备/浏览器订阅
- ✅ 优点:存储空间占用极小,不会积累无效记录;
- ❌ 缺点:用户切换回旧设备时,完全收不到通知,体验割裂——比如用户在家用PC订阅,出门用手机,后来又换回PC,这时候PC的订阅已经被删了,就收不到推送了。
最优方案:保留所有订阅,但主动清理无效/长期未使用的记录
显然,两种场景都有明显的短板,最合理的做法是结合两者的优势:既保留用户的多设备订阅以保证体验,又通过主动清理机制控制存储空间。具体可以这么做:
1. 实时清理推送失败的无效订阅
当你向某个订阅的endpoint发送推送时,如果收到410 Gone(订阅已失效)或者持续的4xx/5xx错误,立刻从数据库中删除这个订阅对象。这是最直接有效的清理方式,能及时移除已确认失效的记录。
2. 定期清理长期未使用的订阅
给你的Schema新增一个lastUsed字段,记录该订阅最后一次成功接收推送的时间:
const pushNotificationsSchema = mongoose.Schema({ _id: { type: mongoose.Types.ObjectId, required: true, ref: 'User' }, subscriptions: [{ endpoint: { type: String, required: true }, expirationTime: { type: Date, default: null }, keys: { p256dh: { type: String, required: true }, auth: { type: String, required: true } }, lastUsed: { type: Date, default: Date.now } // 记录最后使用时间 }], updatedAt: { type: Date, default: Date.now } // 替换原有的time字段,自动维护更新时间 });
然后每周/每月跑一个定时脚本,删除lastUsed超过3个月(或根据你的业务调整时长)的订阅——这类订阅大概率是用户已经弃用的设备,清理后不会影响大多数用户的体验。
3. 优化Schema结构减少冗余
你原来的Schema里subobjects用了[Object]类型,改成明确的嵌套结构(如上面的示例),不仅能让数据更规范,还能避免MongoDB存储不必要的元数据,间接节省空间。另外,把原有的time字段换成自动维护的updatedAt,也能减少手动维护的麻烦。
额外的小建议
- 如果你的用户量很大,还可以考虑把活跃订阅(最近30天内使用过的)缓存到内存中,推送时优先从缓存读取,减少MongoDB的查询压力,同时也能降低无效查询的概率;
- 避免存储不必要的字段:比如如果
expirationTime大部分时候是null,可以考虑不存这个字段(MongoDB会自动忽略不存在的字段,节省空间)。
这样一来,你既能保证用户在多设备上的推送体验,又能有效控制MongoDB的存储空间,完美适配免费版的限制~
内容的提问来源于stack exchange,提问作者Pratik Agarwal
相关产品推荐
相关产品推荐

