MongoDB时间序列:如何保留每日最后一条记录并清理其余数据?
MongoDB时间序列股票数据留存的优化方案
针对你遇到的问题,这里提供几个比“双集合分开查询”更高效的解决方案:
方案1:定时归档+TTL+聚合合并查询
这是最直接的优化方案,核心思路是自动归档每日最后一条数据到历史集合,同时用TTL清理主集合的过期数据,最后通过聚合操作一次获取全量数据:
- 设置主集合TTL:给时间序列集合的时间字段(比如
timestamp)配置expireAfterSeconds,值设为2个月对应的秒数(例如5184000,按30天/月计算),确保2个月后自动清理非归档数据。 - 定时归档每日最后一条记录:每天执行一次聚合任务,提取前一天的最后一条股票数据,插入到历史集合(比如
stock_daily_last)。示例聚合脚本:
可以用MongoDB Atlas触发器、crontab(结合// 提取前一天的最后一条记录并插入历史集合 const yesterdayEnd = new Date(); yesterdayEnd.setDate(yesterdayEnd.getDate() - 1); yesterdayEnd.setHours(23, 59, 59, 999); const yesterdayStart = new Date(yesterdayEnd); yesterdayStart.setHours(0, 0, 0, 0); const lastDailyRecord = db.stock_prices.aggregate([ { $match: { timestamp: { $gte: yesterdayStart, $lte: yesterdayEnd } } }, { $sort: { timestamp: -1 } }, { $limit: 1 } ]).toArray()[0]; if (lastDailyRecord) { db.stock_daily_last.insertOne(lastDailyRecord); }mongosh脚本)或其他定时任务工具执行这个脚本。 - 一次查询获取全量数据:用
$unionWith聚合操作符合并主集合(近2个月数据)和历史集合(更早的每日数据),无需两次查询:// 查询指定时间范围的所有数据(合并主集合和历史集合) const targetStartDate = ISODate("2024-01-01T00:00:00Z"); db.stock_prices.aggregate([ // 取主集合中符合时间范围的数据 { $match: { timestamp: { $gte: targetStartDate } } }, // 合并历史集合中更早的数据 { $unionWith: { coll: "stock_daily_last", pipeline: [ { $match: { timestamp: { $lt: targetStartDate } } } ] } }, // 按时间排序 { $sort: { timestamp: 1 } } ])
方案2:实时归档(基于Change Streams)
如果需要实时维护每日最后一条记录的准确性(避免定时任务的延迟),可以用MongoDB的Change Streams监听主集合的插入事件,实时更新历史集合的当日记录:
// 监听主集合的插入操作 const changeStream = db.stock_prices.watch([ { $match: { operationType: "insert" } } ]); changeStream.on("change", (change) => { const newRecord = change.fullDocument; // 提取日期键(如"2024-05-20") const dateKey = newRecord.timestamp.toISOString().split("T")[0]; // 更新历史集合:如果当日已有记录则替换为最新的,否则插入 db.stock_daily_last.updateOne( { date: dateKey }, { $set: { timestamp: newRecord.timestamp, price: newRecord.price, symbol: newRecord.symbol // 其他需要保留的字段 } }, { upsert: true } ); });
这种方式下,历史集合始终保存当日最新的最后一条记录,主集合的TTL策略保持不变,查询时同样用$unionWith合并数据。
方案3:桶级保留(进阶)
MongoDB时间序列集合以桶为单位存储数据,每个桶对应一个时间范围。对于超过2个月的桶,我们可以:
- 用聚合提取桶内的最后一条记录
- 将这条记录插入历史集合
- 删除整个过期桶
这种方式避免了主集合中留存大量过期的细粒度数据,但需要额外处理桶的识别和删除逻辑,适合对存储占用要求极高的场景。
内容的提问来源于stack exchange,提问作者Mehdi Eskandari
相关产品推荐
相关产品推荐

