bigywater.tech
MongoDB · 20

capped 集合:写满后覆盖的顺序和限制

capped 集合写满后覆盖的顺序是什么?还有哪些限制?这篇用 max=5 和 size=4096 的小集合逐步观察,再验证删除、改大文档、tailable 游标、转换和在线改大小。

21 个
size=4096、文档 194 字节的 capped 集合,稳定保留的文档数
能删能改大
9.0.2 上 capped 集合的 deleteOne 与变长更新都成功,和旧资料里的限制不同
count 264 → 50
collMod 把 cappedMax 改成 50 后,下一次插入时集合立刻裁剪到 50

max 与 size

实测 · 本机执行输出
  test> db.cap5.find({}, {v: 1})
  [
    { _id: 4, v: 'doc4' },
    { _id: 5, v: 'doc5' },
    { _id: 6, v: 'doc6' },
    { _id: 7, v: 'doc7' },
    { _id: 8, v: 'doc8' }
  ]
  test> var s = db.cap5.stats(); ({capped: s.capped, max: s.max, maxSize: s.maxSize, count: s.count, size: s.size})
  { capped: true, max: 5, maxSize: 4096, count: 5, size: 130 }

capped 集合有 size(字节)与可选的 max(文档数)两个上限,先到先生效,写满后最老的文档被覆盖。

字节上限

实测 · 本机执行输出
  每个文档 194 字节;插入过程中集合文档数依次(每 10 个取一次): [1, 11, 21, 21, 21, 21, 21, 21, 21, 21]
  test> var s = db.capsz.stats(); ({count: s.count, size: s.size, maxSize: s.maxSize, storageSize: s.storageSize})
  { count: 21, size: 4074, maxSize: 4096, storageSize: 4096 }
  test> db.capsz.find({}, {_id: 1}).toArray().map(d => d._id)
  [
    79, 80, 81, 82, 83, 84, 85,
    86, 87, 88, 89, 90, 91, 92,
    93, 94, 95, 96, 97, 98, 99
  ]

size=4096、每个文档 194 字节时,集合稳定保留 21 个最新的文档。

限制要以实测为准

实测 · 本机执行输出
  test> db.capsz.deleteOne({_id: 91})
  { acknowledged: true, deletedCount: 1 }
  test> db.capsz.updateOne({_id: 92}, {$set: {pad: "y".repeat(400)}})
  {
    acknowledged: true,
    insertedId: null,
    matchedCount: 1,
    modifiedCount: 1,
    upsertedCount: 0
  }
  test> db.capsz.updateOne({_id: 93}, {$set: {pad: "z".repeat(170)}})
  {
    acknowledged: true,
    insertedId: null,
    matchedCount: 1,
    modifiedCount: 1,
    upsertedCount: 0
  }
  test> db.capsz.updateOne({_id: 94}, {$set: {pad: "w"}})
  {
    acknowledged: true,
    insertedId: null,
    matchedCount: 1,
    modifiedCount: 1,
    upsertedCount: 0
  }
  test> db.capsz.replaceOne({_id: 95}, {pad: "y".repeat(400)})
  {
    acknowledged: true,
    insertedId: null,
    matchedCount: 1,
    modifiedCount: 1,
    upsertedCount: 0
  }
  test> db.capsz.find({_id: {$in: [91, 92, 93, 94, 95]}}).toArray().map(d => [d._id, d.pad.length])
  [ [ 92, 400 ], [ 93, 170 ], [ 94, 1 ], [ 95, 400 ] ]

在这台 9.0.2 上,删除单个文档、把文档改大、replaceOne 变长都成功了。常见资料里“不能删除、不能增长”的说法至少对这个版本不成立(没有逐个版本核对)。

自然顺序

实测 · 本机执行输出
  test> db.capsz.find({}, {_id: 1}).sort({$natural: -1}).limit(3).toArray().map(d => d._id)
  [ 99, 98, 97 ]
  test> db.capsz.getIndexes().map(i => i.name)
  [ '_id_' ]

capped 集合的自然顺序就是插入顺序,$natural: -1 可以倒着读最新的;它也带 _id_ 索引。

tailable 游标

实测 · 本机执行输出
  tailable 游标收到(距启动的秒数, _id): [(0.0, 0), (1.0, 1), (1.7, 2), (2.4, 3)]
  对普通(非 capped)集合 tailable 会怎样: error processing query: ns=test.plainTree: $and Sort: {} Proj: {} tailable cursor requested on non capped collection, full error: {'ok': 0.0

tailable 游标可以像 tail -f 一样等待新文档,只能用在 capped 集合上,普通集合会报错。

转换与在线修改

实测 · 本机执行输出
  转换前 reg: 1000 个文档,capped=False
  convertToCapped(size=32768): {'ok': 1.0}
  转换后: capped=True,count=264,maxSize=32768,size=32736(最老的文档被丢弃,留下最新的)
  最早的 _id = 736,最晚的 _id = 999
  collMod cappedSize=16384, cappedMax=50: {'ok': 1.0}
  之后: count=264, maxSize=16384, max=50
  再插入 1 个文档之后: count=50(新的上限立刻生效),最早的 _id = 951

convertToCapped 把普通集合转成 capped,保留最新的文档;collMod 可以在线改 cappedSize、cappedMax,下一次插入时才裁剪到新上限。

分步演示

capped 集合
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MongoDB 9.0.2(Homebrew 的 mongod、mongos)/ mongosh 2.12.0 / pymongo 4.18.3;每个实验在临时目录里起全新的 mongod(独立 dbpath,端口 9900 以上,只监听 127.0.0.1,WiredTiger 缓存 0.25 GB),需要副本集或分片集群时在本机起多个进程;命令输出来自真实的 mongosh,批量写入、计时与多线程用 pymongo;计时用 time.perf_counter,数字每次运行会有小幅抖动,ObjectId、时间戳等每次运行都不同。每一步都用真实的 mongosh 或 pymongo 命令;tailable 游标用 pymongo 的 TAILABLE_AWAIT
    • 没有验证:capped 集合上 delete 和变长更新从哪个版本开始允许;副本集里 capped 集合的删除如何复制到 oplog;分片集合不能是 capped;TTL 索引不能建在 capped 集合上;capped 集合在 oplog 内部的用法;更大 size 时覆盖的粒度
    京ICP备2026067502号-1京公网安备11010502063487号