小文档多会放大IO压力,因WiredTiger为每条小文档单独分配页、生成独立key并维护细碎索引项,导致物理存储离散,一次查询触发数十次随机磁盘读,OS页缓存难以合并访问。

小文档密集写入时,WiredTiger 的页分配和索引碎片会直接抬高随机磁盘 IO —— 不是靠加内存或 SSD 能解决的,必须从文档结构本身压缩数据密度。
为什么小文档多会放大IO压力
WiredTiger 为每条小文档(比如 {_id: ..., ts: ..., v: 1.2})单独分配页空间、生成独立 key、维护细碎索引项。结果就是:物理存储高度离散,一次逻辑查询可能触发几十次随机磁盘读;即使数据在 OS 页缓存里,也无法有效合并这些分散访问。
- 典型症状:
page faults per second持续 > 50,totalDocsExamined远高于实际返回数 - 影响范围:不仅限于磁盘 IO,还会拖慢 WiredTiger 内部缓存的 LRU 效率,导致热数据频繁被淘汰
- 关键阈值:单集合中小文档数量超过 10 万条,且平均大小
用文档桶模式聚合时间序列类小文档
把高频、同源、天然有序的小文档按时间窗口“压”进一个大文档,用数组存原始数据,再加元信息辅助定位 —— 这是最直接降低 IO 密度的手段。
- 桶键推荐用 ISO8601 字符串(如
"2026-10-21T08:00:00Z"),排序稳定、无时区歧义、可建高效范围索引 -
data数组长度控制在 1000–5000 条之间:太小起不到聚合效果,太大导致单次读取/更新开销飙升 - 必须在
bucket_start和bucket_end上建复合索引,否则查某段时间仍要全桶扫描 - 避免在应用层做后过滤:别用
findOne()拉全桶再用 JavaScriptfind()筛,改用聚合管道下推,例如$filter+$arrayElemAt
嵌入式结构要平衡局部性与膨胀风险
嵌入能提升数据局部性,但无节制嵌入反而破坏它 —— WiredTiger 对超大文档会切分多页,这些页在磁盘上未必连续,还容易因局部更新触发整页重写。
- 单文档估算大小 > 2MB(按公式
∑(字段名长度 + 字段值大小 + 10) × 1.2)就该拆分 - 嵌入数组预期平均长度 > 50 条,或存在高频追加/删除操作,优先考虑独立集合 + 合适索引
- 投影(
projection)无法跳过嵌入数组中的非目标字段:只查title,整个comments数组仍要加载进缓存 - 更新嵌入数组末尾元素时,注意用
$push+$slice控制长度,避免文档无限增长
真正难的是判断“什么时候该停手”:桶不能太小,嵌入不能太深,数组不能太长 —— 这些边界不是理论值,得结合你的查询模式、更新频率、硬件缓存容量反复实测。比如同样 3000 条数据,按小时聚合和按 5 分钟聚合,对缓存命中率的影响可能差 3 倍。











