通过 TTL 让 MongoDB 集合数据自动过期

MongoDB 的存活时间(TTL)集合功能,可以让 mongod 在指定秒数之后,或指定时刻自动删除数据。它适用于只需保留有限时间的机器事件、日志与会话等信息。

TTL 依靠特殊索引属性,以及 mongod 后台线程实现。后台线程读取索引中的日期值,并从集合删除过期文档。

使用 createIndex() 创建 TTL 索引,索引字段必须是日期类型,或包含日期值的数组,并通过 expireAfterSeconds 指定秒数。

注意:TTL 只支持单字段索引,复合索引不支持此属性。可通过 collMod 修改已有 TTL 索引的 expireAfterSeconds。

在 MongoDB Atlas 界面中设置过期

1. 打开项目的 Clusters 页面

必要时,先从导航栏 Organizations 菜单选择组织,再从 Projects 选择项目。在侧栏 Database 下点击 Clusters。

2. 进入集合

在目标集群点击 Browse Collections,再从左侧依次选择数据库和集合。

3. 打开创建索引对话框

点击 Indexes,再点击 Create Index。

4. 创建带 expireAfterSeconds 的索引

在 Fields 中填写索引键。本例索引 expiresAfter:

{ "expiresAfter": 1 }

在 Options 中指定 expireAfterSeconds。以下设置让数据在 expiresAfter 的值之后一秒过期:

{ expireAfterSeconds: 1 }

点击 Review,再点击 Confirm。

5. 添加包含 expiresAfter 的文档

从左侧选择集合,点击 Find,再点击 Insert Document。在 _id 下方字段框输入 expiresAfter,旁边填写:

2023-10-01T12:00:00.000+00:00

该值对应 2023 年 10 月 1 日 12:00。将数据类型下拉框改为 Date,再点击 Insert。

文档在此时间之后一秒过期。原文说明界面中的删除可能需要一到两秒,也可能需要刷新才能看到结果。

指定若干秒后过期

可以通过集合创建选项,或 TTL 索引设置。

创建集合时指定

创建集合时使用 expireAfterSeconds。此集合级选项仅适用于时间序列集合和聚簇集合。

使用 TTL 索引

在 BSON 日期字段或日期数组上创建 TTL 索引,设置非负的 expireAfterSeconds。当距离索引字段时间超过指定秒数,文档过期。

有效值为 0 到 2147483647,包含两端。

以下在 log_events.createdAt 上创建索引,设置十秒后过期:

db.log_events.createIndex( { "createdAt": 1 }, { expireAfterSeconds: 10 } )

插入时把 createdAt 设为当前时间:

db.log_events.insertOne( {
   "createdAt": new Date(),
   "logEvent": 2,
   "logMessage": "Success!"
} )

当 createdAt 距当前时间超过指定秒数,MongoDB 自动删除文档。如果字段是日期数组,只要其中至少一个日期超过过期时长,文档就会过期。

带筛选条件的过期

可以创建同时具有 partial 与 TTL 属性的索引,只让满足表达式的文档过期:

db.foo.createIndex(
   { F: 1 },
   {
      name: "Partial-TTL-Index",
      partialFilterExpression: { D : 1 },
      expireAfterSeconds: 10
   }
)

插入两个文档,其中一个匹配 partialFilterExpression 的 { D : 1 }:

db.foo.insertMany( [
   { "F" : ISODate("2019-03-07T20:59:18.428Z"), "D" : 3},
   { "F" : ISODate("2019-03-07T20:59:18.428Z"), "D" : 1}
] )

等待十秒后查询:

db.foo.find({}, {_id: 0, F: 1, D: 1})

匹配 { D : 1 } 的文档已经过期删除,只剩:

{ "F" : ISODate("2019-03-07T20:59:18.428Z"), "D" : 3}

在指定时刻过期

在日期字段或日期数组上创建 TTL 索引,将 expireAfterSeconds 设为 0。每个文档的索引日期就是它应过期的时刻。日期在过去时,MongoDB 将其视为已过期。

例如:

db.log_events.createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } )

为每个文档设置 expireAt。以下文档在 2013 年 7 月 22 日 14:00:00 过期:

db.log_events.insertOne( {
   "expireAt": new Date('July 22, 2013 14:00:00'),
   "logEvent": 2,
   "logMessage": "Success!"
} )

当 expireAt 比当前时间早于配置秒数时自动删除;此处秒数为 0。

expireAfterSeconds 被设为 NaN 的索引

警告:可能丢失数据。TTL 索引的 expireAfterSeconds 为 NaN 时,升级、降级和某些同步操作可能出现意外行为并丢失数据。不要这样配置。

  • MongoDB 5.0 之前:记录错误,不删除记录。
  • MongoDB 5.0.0 至 5.0.13,以及 6.0.0 至 6.0.1:NaN 被视为 0,全部 TTL 索引文档立即过期。
  • 从 5.0.14 和 6.0.2 起:服务器不使用 expireAfterSeconds 为 NaN 的 TTL 索引。

仍可能导致文档意外过期的情况包括:

  • 从 5.0.0–5.0.13 或 6.0.0–6.0.1 向更旧版本执行初始同步。
  • 从更旧版本升级到 5.0.0–5.0.13。
  • 把 5.0 之前的 mongodump 集合恢复到 5.0.0–5.0.13 或 6.0.0–6.0.1。

应删除或纠正配置错误的 TTL 索引。

1. 找出配置错误的索引

在 mongosh 中运行以下脚本。它不适用于旧 mongo shell:

function getNaNIndexes() {
   const nan_index = [];

   const dbs = db.adminCommand({ listDatabases: 1 }).databases;

   dbs.forEach((d) => {
      if (d.name != 'local') {
         const listCollCursor = db
            .getSiblingDB(d.name)
            .runCommand({ listCollections: 1 }).cursor;

         const collDetails = {
            db: listCollCursor.ns.split(".$cmd")[0],
            colls: listCollCursor.firstBatch.map((c) => c.name),
         };

         collDetails.colls.forEach((c) =>
            db
               .getSiblingDB(collDetails.db)
               .getCollection(c)
               .getIndexes()
               .forEach((entry) => {
                  if (Object.is(entry.expireAfterSeconds, NaN)) {
                     nan_index.push({ ns: `${collDetails.db}.${c}`, index: entry });
                  }
               })
         );
      }
   });

   return nan_index;
};

getNaNIndexes();

2. 修正索引

使用 collMod 更新脚本找到的错误值。也可以删除这些 TTL 索引,之后通过 createIndexes 重新创建。


原文:Expire Data from Collections by Setting TTL。作者/维护方:MongoDB 文档维护者。本文为中文翻译,代码及命令保留原文。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容