关于时间序列数据
在内部,MongoDB 通过根据常见的 metaField
值对时间序列集合中的文档进行分组,来优化时间序列数据。选择一个有用的选项,可以显著优化存储密度和查询性能。有关更多信息,请参阅 metaFields。
时间序列数据的属性
时间序列数据具有几个有别于其他数据格式的属性:
文档按顺序到达,需要频繁的插入操作来追加它们。
更新操作很少见,因为每个文档都代表一个时间点。
如果您的应用程序受益于丰富的历史记录,则删除操作就很少发生。
数据按时间和标识符(如股票代码)编制索引,以标识其所属的唯一时间序列。
数据量庞大,因为每个单独的时间序列都需要大量且不断增长的文档。
考虑到这些因素,MongoDB采用了专门的列式存储,将每个时间序列的文档归拢在一起。此功能具有以下优点:
减少存储和索引的大小
提高查询效率
减少读取操作的 I/O 量
增加了 WiredTiger 内存缓存的使用量,进一步提高了查询速度
降低处理时间序列数据的复杂性
将时间序列集合与常规集合进行比较
在常规集合中,数据作为区块按顺序存储在磁盘上,从而优化了写入速度。不过,这种方法得为每个数据点创建索引,而这些索引很快就会变得极其庞大。此外,这种方法还需要建立一个包含时间序列标识符和时间戳的第二个索引,以便用户可以查询单个序列。要读取这些数据,MongoDB 必须处理包含这些数据的所有数据库和磁盘区块,即使一个区块只包含一个相关文档。
该模型针对 CRUD 操作和频繁更新进行了优化。银行账户余额只需要反映当前状态,因此每个账户持有人的文档都会随着信息的变化而更新。
将其与时间序列集合进行比较。时间序列集合按顺序写入数据,这意味着最近的事务可以保留在内存中,以便更快地检索。由于数据是按顺序写入的,因此文档存储在一起,因此在文档不再存在于内存中后,无需读取每个磁盘区块。数据按 metaField
进行索引,从而使索引小得多。
分桶如何运作
当您创建时间序列集合时,MongoDB 会自动创建system.buckets
系统集合。MongoDB 对同时具备以下条件的文档进行分组:
相同的
metaField
值,应唯一标识时间序列。如果metaField
是对象或数组,则仅当所有对象字段或数组元素匹配时,MongoDB 才会进行分组。timeField
值相近。时间序列集合的granularity
、bucketMaxSpanSeconds
和bucketRoundingSeconds
参数控制每个存储桶涵盖的时间跨度。有关更多信息,请参阅设置时间序列数据的粒度。
例如,粒度为 seconds
时,MongoDB 会在同一小时内对文档进行存储。如果存储桶包含 metaField
值为 sensorA
且 timeField
值为 2024-08-01T18:23:21Z
的文档,则 metaField
为 sensorB
的传入文档将进入单独的存储桶,而不管时间如何。仅当其 timeField
介于 2024-08-01T18:00:00Z
和 2024-08-01T18:59:59Z
之间时,来自 sensorA
的传入文档才会进入同一存储桶。
如果时间为 2023-03-27T16:24:35Z
的文档不适合现有存储桶,MongoDB 将创建一个新存储桶,其最小时间为 2023-03-27T16:00:00Z
,最大时间为 2023-03-27T19:59:59Z
。
注意
您可以修改时间序列集合的粒度,但只能从更精细的测量更改为更粗略的测量,例如将存储桶覆盖范围从分钟扩展到小时。这会更新集合的视图定义,但不会更改数据在现有存储桶中的存储方式。
metaField 如何影响分桶
由于 metaField
值必须与分组文档完全匹配,因此时间序列集合中的存储桶数取决于唯一 metaField
值的数量。具有精细或不断变化的 metaField
值的集合会生成许多稀疏打包的短期存储桶。这会导致存储和查询效率降低。
例如,在以下文档中,metadata
是 metaField
的不错选择,因为它可以轻松查询来自给定天气传感器的数据。利用这些字段,MongoDB 会将来自单个传感器的读数汇总在一起。
{ timestamp: ISODate("2021-05-18T00:00:00.000Z"), metadata: { sensorId: 5578, type: 'temperature' }, temp: 12, _id: ObjectId("62f11bbf1e52f124b84479ad") }
分桶目录
存储桶目录是 WiredTiger 中专门的内存缓存。它能跟踪存储桶,以尽量减少延迟并协调并发写入。
对于每个打开的存储桶,目录会维护 metaField
、活动编写器、覆盖的时间跨度、文档数、大小和最近的操作等信息。由于 MongoDB 会为具有不同 metaField
的文档创建单独的存储桶,因此通常会同时打开多个存储桶。
为了避免由竞争条件引起的不一致,当执行冲突操作时,可能会关闭存储桶并将其从存储桶目录中删除。重新启动 mongod
会关闭所有存储桶并重置存储桶目录。
创建
如果传入的文档没有合适的存储桶,MongoDB 会创建新的存储桶。当满足以下任一条件时,就会执行此操作:
文件
metaField
不匹配任何活动存储桶。文档时间戳超出所有活动存储桶的范围。
文档超过所有活动存储桶的剩余大小或文档限制。
新存储桶的起始时间戳根据集合的粒度向下舍入。这样就可以处理具有无序时间戳的文档连续到达的情况。
关闭
MongoDB 在以下任何情况下都会关闭存储桶:
时间向前或向后移动,超过了所覆盖的时间跨度,如超出存储桶边界的传入文档时间戳所示。这些边界由集合的粒度设置确定。
存储桶已达到文档限制(默认为 1000)。
存储桶已超出存储大小限制。出现这种情况的原因是:
大小超过允许的最大值(默认 125 KiB)。
文档数量低于最小数量(默认为 10),且大小小于 12 MiB。
这是一个设定的内部限制,当数据由较少但较大的文档组成时,可以优化性能。
活动存储桶集不适合允许的存储引擎缓存大小。您可以使用
collStats
数据库命令查看此信息。
存储桶目录超出了其允许的总内存分配(默认情况下,为可用系统内存的 2.5%)
冲突操作(例如数据块迁移或更新)会改变磁盘上的存储桶状态。
mongod
重新启动。这将关闭所有存储桶。
删除
MongoDB 会在以下情况删除存储桶:
其允许的最大时间戳小于当前时间减去集合的
expireAfterSeconds
参数。这相当于 TTL 集合的生存时间。delete
或db.collection.deleteMany()
命令会删除存储桶中的最后一个文档。