bigywater.tech
MongoDB · 13

16MB 与嵌套深度上限:在哪一步报错

16MB 的上限到底卡在哪一步?嵌套深度的上限是多少?这篇构造刚好 16 MiB 的文档,逐字节增加,看 insert、update 和聚合在哪一步报什么错,并用二分查找确认嵌套深度的上限。

16,777,216
maxBsonObjectSize:刚好这么大的文档能插入,多 1 字节就报 10334
180 层
9.0.2 上能插入的最大嵌套深度是 179 层内嵌,180 层失败(maxBSONDepth 参数是 200)
20,971,714
聚合返回的文档可以大于 16 MiB,写进集合时才报 Size … exceeds maximum 16793600

上限是多少

实测 · 本机执行输出
  test> h = db.hello(); ({maxBsonObjectSize: h.maxBsonObjectSize, maxMessageSizeBytes: h.maxMessageSizeBytes, maxWriteBatchSize: h.maxWriteBatchSize})
  {
    maxBsonObjectSize: 16777216,
    maxMessageSizeBytes: 48000000,
    maxWriteBatchSize: 100000
  }
  maxBsonObjectSize = 16,777,216 = 16 × 1024 × 1024

hello 返回 maxBsonObjectSize 16,777,216 字节,整个 BSON 文档(含所有键名和结尾)不能超过它。

刚好与多 1 字节

实测 · 本机执行输出
  文档编码后 16,777,216 字节(= 1.0000 × 16 MiB)
  insert_one 成功,耗时 31 ms;读回 len(s) = 16,777,194
  多 1 字节(16,777,217): WriteError: object to insert too large. size in bytes: 16777217, max size: 16777216, full error: {'index': 0, 'code': 10334, 'errmsg': 'object to insert too large. size in 

刚好 16 MiB 的文档能插入;多 1 字节服务端返回错误码 10334 object to insert too large。

原始命令

实测 · 本机执行输出
  文档 16,777,217 字节(超 1 字节): n=0, writeErrors=[{'index': 0, 'code': 10334, 'errmsg': 'object to insert too large. size in bytes: 16777217, max size: 16777216'}]
  文档 16,778,240 字节(超 1024 字节): n=0, writeErrors=[{'index': 0, 'code': 10334, 'errmsg': 'object to insert too large. size in bytes: 16778240, max size: 16777216'}]
  超 16 KiB: DocumentTooLarge: BSON document too large (16793685 bytes) - the connected server supports BSON document sizes up to 16793598 bytes.
  集合里最后的文档数: 1(只有最开始刚好 16 MiB 的那一个)

直接用 insert 命令时,超限文档在回复的 writeErrors 里,命令本身不抛异常;超出命令体上限(16793598)时驱动在客户端就拒绝。

update 撑大

实测 · 本机执行输出
  起点文档约 14.0 MiB
  test> try { db.grow.updateOne({_id: 1}, [{$set: {s: {$concat: ["$s", "$s"]}}}]) } catch (e) { e.message }
  Plan executor error during update :: caused by :: RCString too large. Requires size=29360128 < limit=16777216
  test> bsonsize(db.grow.findOne({_id: 1})) / 1024 / 1024
  14.000020980834961
  test> var r = null; for (var i = 0; i < 40; i++) { try { db.arr.updateOne({_id: 1}, {$push: {a: "x".repeat(1024 * 1024)}}); } catch (e) { r = "第 " + (i + 1) + " 次 $push 1MiB 报错: " + e.message; break } } r
  第 16 次 $push 1MiB 报错: Plan executor error during update :: caused by :: Resulting document after update is larger than 16777216
  test> db.arr.aggregate([{$project: {n: {$size: "$a"}}}])
  [ { _id: 1, n: 15 } ]

$push 把文档撑过上限时报 Resulting document after update is larger than 16777216,文档保持原样;聚合表达式里拼出过大的字符串则报 RCString too large。

嵌套深度

实测 · 本机执行输出
  内嵌 100 层: 成功;二分查找得到:内嵌 179 层成功,180 层失败(外层文档本身也算 1 层,即总深度 180 成功)
  失败时的错误: cannot insert document :: caused by :: object exceeds 180 levels of nesting
  内嵌 250 层(超过 BSON 解析上限):BSONObj exceeds maximum nested object depth in element with …
  test> db.adminCommand({getParameter: 1, maxBSONDepth: 1})
  { maxBSONDepth: 200, ok: 1 }

在这台 9.0.2 上,二分查找得到内嵌 179 层可以插入、180 层失败(object exceeds 180 levels of nesting);更深的文档连 BSON 本身都拒绝(参数 maxBSONDepth 是 200)。常说的 100 层与此不同,以实测为准。

聚合产生大文档

实测 · 本机执行输出
  test> r = db.parts.aggregate([{$group: {_id: "$g", all: {$push: "$s"}}}]).toArray()[0]; [r.all.length, bsonsize(r), bsonsize(r) > 16 * 1024 * 1024]
  [ 20, 20971714, true ]
  test> try { db.parts.aggregate([{$group: {_id: "$g", all: {$push: "$s"}}}, {$out: "outc"}]).toArray(); "ok" } catch (e) { e.message.slice(0, 200) }
  Executor error during aggregate command on namespace: test.parts :: caused by :: Serializing Document failed :: caused by :: Size 20971714 exceeds maximum 16793600
  test> db.parts.aggregate([{$group: {_id: "$g", n: {$sum: 1}, bytes: {$sum: {$strLenBytes: "$s"}}}}])
  [ { _id: 1, n: 20, bytes: 20971520 } ]

聚合的结果文档可以超过 16 MiB 返回给客户端(实测 20,971,714 字节),但写进集合($out)时超过 16793600 字节就失败。需要汇总时尽量用累加器而不是 $push 全部元素。

分步演示

16MB 与嵌套深度
本步的对象

    本文用到的工具与边界

    • 本机 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、时间戳等每次运行都不同。文档大小用 bson.encode 精确构造;嵌套深度用二分查找
    • 没有验证:GridFS 绕过 16MB 的做法;分片集群上的限制;索引键超长的限制(见索引一篇);$push 在 100MB 内存限制处的行为;mongosh 取回超大文档的内存开销;其它版本的嵌套深度(手册写的 100 层与这台实测的 180 层不同,没有核对原因)
    京ICP备2026067502号-1京公网安备11010502063487号