时间序列集合最佳实践
在此页面上
本页介绍了改善时间序列集合性能和数据使用情况的最佳实践。
优化插件
要优化时间序列集合的插入性能,请执行以下操作。
批量文档写入
插入多个文档时:
为避免网络往返,请使用单个
insertMany()
语句,而不是多个insertOne()
语句。如果可能,构建批次以包含每个系列的多个测量值(由元数据定义)。
为了提升性能,请将
ordered
参数设置为false
。
例如,如果您具有两个传感器(sensor A
和 sensor B
),则包含来自单个传感器的多个测量值的批次产生一个插入的成本,而不是每个测量值一个插入。
以下操作会插入六个文档,但仅产生两次插入的成本(每批一次),因为文档是按传感器排序的。ordered
参数设置为 false
,以提升性能:
db.temperatures.insertMany( [ { "metadata": { "sensor": "sensorA" }, "timestamp": ISODate("2021-05-18T00:00:00.000Z"), "temperature": 10 }, { "metadata": { "sensor": "sensorA" }, "timestamp": ISODate("2021-05-19T00:00:00.000Z"), "temperature": 12 }, { "metadata": { "sensor": "sensorA" }, "timestamp": ISODate("2021-05-20T00:00:00.000Z"), "temperature": 13 }, { "metadata": { "sensor": "sensorB" }, "timestamp": ISODate("2021-05-18T00:00:00.000Z"), "temperature": 20 }, { "metadata": { "sensor": "sensorB" }, "timestamp": ISODate("2021-05-19T00:00:00.000Z"), "temperature": 25 }, { "metadata": { "sensor": "sensorB" }, "timestamp": ISODate("2021-05-20T00:00:00.000Z"), "temperature": 26 } ], { "ordered": false })
在文档中使用一致的字段顺序
在文档中使用一致的字段顺序,可以提高插入性能。
例如,插入这些文档可实现最佳插入性能:
{ "_id": ObjectId("6250a0ef02a1877734a9df57"), "timestamp": ISODate("2020-01-23T00:00:00.441Z"), "name": "sensor1", "range": 1 }, { "_id": ObjectId("6560a0ef02a1877734a9df66"), "timestamp": ISODate("2020-01-23T01:00:00.441Z"), "name": "sensor1", "range": 5 }
相比之下,这些文档无法实现最佳的插入性能,因为其字段顺序不同:
{ "range": 1, "_id": ObjectId("6250a0ef02a1877734a9df57"), "name": "sensor1", "timestamp": ISODate("2020-01-23T00:00:00.441Z") }, { "_id": ObjectId("6560a0ef02a1877734a9df66"), "name": "sensor1", "timestamp": ISODate("2020-01-23T01:00:00.441Z"), "range": 5 }
增加客端户数量
增加向集合写入数据的客户端数量可以提高性能。
优化压缩
要优化时间序列集合的数据压缩,请执行以下操作。
省略文档中包含空对象和数组的字段
为优化压缩,如果数据包含空对象或数组,请从省略文档中的空字段。
例如,考虑以下文档:
{ "timestamp": ISODate("2020-01-23T00:00:00.441Z"), "coordinates": [1.0, 2.0] }, { "timestamp": ISODate("2020-01-23T00:00:10.441Z"), "coordinates": [] }, { "timestamp": ISODate("2020-01-23T00:00:20.441Z"), "coordinates": [3.0, 5.0] }
具有填充值的coordinates
字段与空数组之间的交替会导致压缩器的模式发生更改。模式更改会导致序列中的第二个和第三个文档保持未压缩状态。
相比之下,以下省略空数组的文档可获得最佳压缩的好处:
{ "timestamp": ISODate("2020-01-23T00:00:00.441Z"), "coordinates": [1.0, 2.0] }, { "timestamp": ISODate("2020-01-23T00:00:10.441Z") }, { "timestamp": ISODate("2020-01-23T00:00:20.441Z"), "coordinates": [3.0, 5.0] }
将数值数据四舍五入到小数点后几位
将数值数据四舍五入到应用程序所需的精度。将数值数据四舍五入到更少的小数位可提高压缩率。
优化查询性能
设置相应的桶粒度
创建时间序列集合时,MongoDB 会将传入时间序列数据分组到存储桶中。通过精确设置粒度,您可以根据数据摄取率来控制数据的分组频率。
从 MongoDB 6.3 开始,可以使用自定义分桶参数 bucketMaxSpanSeconds
和 bucketRoundingSeconds
来指定存储桶边界,并更准确地控制时间序列数据的分桶方式。
您可以将 granularity
或自定义分组参数设置为与同一数据源的传入测量值之间时间段的最佳匹配,从而提高性能。例如,如果您要记录数千个传感器的天气数据,但每个传感器每 5 分钟只记录一次数据,您可以将 granularity
设置为 "minutes"
,或者将自定义分组参数设置为 300
(秒)。
在这种情况下,将 granularity
设置为 hours
会将最多一个月的数据导入事件分组到单个存储桶中,从而导致遍历时间更长且查询速度更慢。将其设置为 seconds
会导致每个轮询间隔有多个存储桶,其中许多可能只包含一个文档。
下表显示了使用给定 granularity
值时一个数据桶中包含的最长时间间隔:
granularity | granularity 存储桶限额 |
---|---|
seconds | 1 小时 |
minutes | 24 小时 |
hours | 30天 |
创建二级索引
为提升查询性能,请在 timeField
和 metaField
上创建一个或多个二级索引,以支持常见查询模式。在版本 6.3 及更高版本中,MongoDB 会自动在 timeField
和 metaField
上创建二级索引。
查询子字段上的 metaField
MongoDB 对时间序列集合的 metaField 重新排序,这可能会导致服务器以不同于应用程序的字段顺序存储数据。如果 metaField 是对象,对整个 metaField 的查询可能会产生不一致的结果,因为 metaField 顺序可能因服务器和应用程序而异。要优化对时间序列 metaField 的查询,请查询标量子字段上的时间序列 metaField 而不是整个 metaField。
以下示例创建一个时间序列集合:
db.weather.insertMany( [ { "metaField": { "sensorId": 5578, "type": "temperature" }, "timestamp": ISODate( "2021-05-18T00:00:00.000Z" ), "temp": 12 }, { "metaField": { "sensorId": 5578, "type": "temperature" }, "timestamp": ISODate( "2021-05-18T04:00:00.000Z" ), "temp": 11 } ] )
以下对 sensorId
和 type
标量子字段的查询返回第一个与查询条件匹配的文档:
db.weather.findOne( { "metaField.sensorId": 5578, "metaField.type": "temperature" } )
示例输出:
{ _id: ObjectId("6572371964eb5ad43054d572"), metaField: { sensorId: 5578, type: 'temperature' }, timestamp: ISODate( "2021-05-18T00:00:00.000Z" ), temp: 12 }
使用 $group 而不是 Distinct()
由于时间序列集合的独特数据结构, MongoDB 无法有效地为不同值建立索引。避免在时间序列集合中使用 distinct
命令或 db.collection.distinct()
辅助方法。取而代之的是,使用 $group
聚合按不同的值对文档进行分组。
例如,要在 meta.project = 10
的文档上查询不同的 meta.type
值,而不是:
db.foo.distinct("meta.type", {"meta.project": 10})
使用:
db.foo.createIndex({"meta.project":1, "meta.type":1}) db.foo.aggregate([{$match: {"meta.project": 10}}, {$group: {_id: "$meta.type"}}])
具体操作如下: