文档菜单
文档首页
/
MongoDB 手册
/

时间序列集合的最佳实践

本页内容

  • 压缩最佳实践
  • 从文档中省略包含空对象和数组的字段
  • 将数值数据四舍五入到小数点后几位
  • 插入最佳实践
  • 批量文档写入
  • 在文档中使用一致的字段顺序
  • 增加客户端数量
  • 分片最佳实践
  • 使用metaField作为您的分片键
  • 查询最佳实践
  • 创建集合时设置战略性的metaField
  • 设置适当的桶粒度
  • 创建二级索引
  • 额外索引最佳实践
  • 在子字段上查询metaField
  • 使用$group而不是Distinct()

本页描述了提高时间序列集合性能和数据使用的最佳实践。

为了优化时间序列集合的数据压缩,执行以下操作

如果您的数据包含空对象、数组或字符串,为了优化压缩,请从您的文档中省略空字段。

例如,考虑以下文档

{
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字段具有填充值和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]
}

将数值数据四舍五入到应用程序所需的精度。将数值数据四舍五入到小数点后位数较少可以提高压缩率。

为了优化时序集合的插入性能,执行以下操作

在插入多个文档时

  • 为了减少网络往返,使用单个insertMany()语句,而不是多个insertOne()语句。

  • 如果可能的话,将包含相同metaField值的请求数据批量插入。

  • ordered参数设置为false

例如,如果你有两个传感器对应两个metaField值,传感器A传感器B,一个包含多个单个传感器测量值的批量只产生一次插入成本,而不是每次测量产生一次插入成本。

以下操作插入六个文档,但只产生两次插入成本(每个metaField值一次),因为文档按传感器排序。将ordered参数设置为false以提高性能

db.temperatures.insertMany(
[
{
metaField: {
sensor: "sensorA"
},
timestamp: ISODate("2021-05-18T00:00:00.000Z"),
temperature: 10
},
{
metaField: {
sensor: "sensorA"
},
timestamp: ISODate("2021-05-19T00:00:00.000Z"),
temperature: 12
},
{
metaField: {
sensor: "sensorA"
},
timestamp: ISODate("2021-05-20T00:00:00.000Z"),
temperature: 13
},
{
metaField: {
sensor: "sensorB"
},
timestamp: ISODate("2021-05-18T00:00:00.000Z"),
temperature: 20
},
{
metaField: {
sensor: "sensorB"
},
timestamp: ISODate("2021-05-19T00:00:00.000Z"),
temperature: 25
},
{
metadField: {
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
}

增加向您的集合写入数据的客户端数量可以提高性能。

为了优化您的时序集合上的分片,请执行以下操作

使用 metaField 对您的集合进行分片,为时间序列集合提供了一个足够的基数作为分片键。

注意

从 MongoDB 8.0 开始,在时间序列集合中使用 timeField 作为分片键已被弃用。

为了优化您时间序列集合的查询,执行以下操作

您对 metaField 的选择对优化应用程序中的查询影响最大。

  • 将很少或永不更改的字段作为 metaField 的一部分。

  • 如果可能的话,选择在过滤表达式中常见的标识符或其他稳定值作为 metaField 的一部分。

  • 避免将不用于过滤的字段作为 metaField 的一部分。相反,将这些字段用作度量。

有关更多信息,请参阅 metaField 考虑事项。

当您创建时间序列集合时,MongoDB将传入的时间序列数据分组到桶中。通过准确设置粒度,您可以根据数据摄取速率控制数据分桶的频率。

从MongoDB 6.3版本开始,您可以使用自定义桶参数bucketMaxSpanSecondsbucketRoundingSeconds来指定桶边界,更精确地控制时间序列数据的桶划分。

您可以通过将granularity或自定义桶参数设置为与来自同一数据源传入测量的时间跨度最佳匹配的值来提高性能。例如,如果您正在记录来自成千上万个传感器的天气数据,但每个传感器每5分钟只记录一次数据,您可以将granularity设置为"minutes"或将自定义桶参数设置为300(秒)。

在这种情况下,将granularity设置为hours将一个月的数据摄取事件合并到一个桶中,导致遍历时间更长,查询更慢。将其设置为seconds会导致每个轮询间隔多个桶,其中许多可能只包含单个文档。

以下表格显示了使用给定granularity值时,一个数据桶中包含的最大时间间隔

粒度
粒度桶限制
1小时
分钟
24小时
小时
30天

提示

另请参阅

为了提高查询性能,请在您的 timeFieldmetaField 上创建一个或多个二级索引,以支持常见的查询模式。在6.3及以上版本中,MongoDB会自动在 timeFieldmetaField 上创建二级索引。

  • 使用 metaField 索引进行过滤和相等性比较。

  • 使用 timeField 和其他索引字段进行范围查询。

  • 通用的索引策略也适用于时间序列集合。更多信息,请参阅 索引策略。

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
}
] )

以下查询在 sensorIdtype 标量子字段上返回与查询条件匹配的第一个文档

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
}

由于时间序列集合的独特数据结构,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"}}])

这会按照以下方式工作

  1. meta.projectmeta.type 上创建一个 复合索引,支持聚合。

  2. $match 阶段筛选出 meta.project = 10 的文档。

  3. $group 阶段使用 meta.type 作为分组键,为每个唯一值输出一个文档。

返回

添加次要索引