【问题标题】:MongoDB time series database design: hour/minute/second-of-minute vs hour/second-of-hour?MongoDB时间序列数据库设计:小时/分钟/秒与小时/秒?
【发布时间】:2018-06-22 13:22:05
【问题描述】:

我在academic research project 中,并使用 MongoDB 存储加速度计值的时间序列数据(物联网/遥测数据)。粒度是采样率可以在 1 到 100 Hz 之间的任何样本。目前我每个文档使用一个小时的数据,然后有一个 3 维数组,第一级是分钟,第二级是秒,第三级是样本(双数据类型)。这是受 MongoDB 用于时间序列数据演示的启发(Part 1Part 2)。

例如

{
  "_id": "2018011200:4", /* Jan 12, 2018 hour 00 UTC for sensor 4 */
  "z": [
    00: [ /* 00h00m */
      00: [ 0.1, 0.0, -0.1, ... ], /* 00h00m00s */
      01: [ 0.1, 0.0, -0.1, ... ], /* 00h00m01s */
      02: [ 0.1, 0.0, -0.1, ... ], /* 00h00m02s */
      ...
      59: [ 0.1, 0.0, -0.1, ... ]  /* 00h00m59s */
    ], ...
  ]
}

这样,使用$slice获取数据子集只能在分钟级别完成,例如如果我想获取从00:00:00到00:00:01的数据,我需要获取从 MongoDB 获取 00:00 的整分钟(包含 60 秒),然后获取我在应用程序中需要的秒数。此外,如果我想从 00:00:59 到 00:01:01 获取数据,那么我需要整整两分钟,然后在应用程序子集中将它们合并回来。这有一些 IO 浪费,应用程序也有一些复杂性。顺便说一句,我不需要检索单个样本,检索(和存储)的最小单位是一秒。

我正在考虑一种稍微不同的方法,将小时文档直接分为秒数组(因为一个小时有 3600 秒),然后是样本数组。这意味着要获得 5 秒的数据,我将准确检索 5 秒的数组(即使在两个不同的文档中,如果时间范围超过一小时)。仍然会有合并不同文档中两部分秒的应用逻辑,但比小时/分钟/秒层次结构更简单。

{
  "_id": "2018011200:4", /* Jan 12, 2018 hour 00 UTC for sensor 4 */
  "z": [
    0: [ 0.1, 0.0, -0.1, ... ],   /* 00h00m00s */
    1: [ 0.1, 0.0, -0.1, ... ],   /* 00h00m01s */
    2: [ 0.1, 0.0, -0.1, ... ],   /* 00h00m02s */
    ...
    3599: [ 0.1, 0.0, -0.1, ... ] /* 00h59m59s */
  ]
}

但是,我也担心替代方法存在我不知道的弱点。

你推荐哪个更好?我需要考虑哪些潜在的陷阱?或者我应该考虑另一种设计?

提前谢谢你。

【问题讨论】:

  • 您需要的最大精度是多少?秒?
  • @MarkusWMahlberg 每个样本的精度在 10 毫秒(100 赫兹)到 1000 毫秒之间,但是数据总是以每个一秒的块为单位检索。例如,如果采样率为 40 Hz(典型值),每小时将有 40x60x60=144,000 个样本(每个站每个通道),但这些必须在 40 个样本块中检索,即只能获得 40 或 80 或 120 或 160 或 144,000样本,但无法获得 35 个样本或 41 个样本。

标签: arrays database mongodb time-series iot


【解决方案1】:

我认为您的数据模型过于复杂。

更新文档比简单地插入文档要复杂得多。由于您的粒度似乎是秒,我们完全在BSON datatype UTC datetime 提供的粒度之内:它是毫秒级的。

因此,根据您的数据模型,假设您每次写入都获得一个值,只需使用类似的东西:

{
  _id: new ObjectId(),
  value: 0.1,
  sensor: 4,
  ts: new ISODate()
}

使用这种数据模型,我们可以确保在不牺牲信息的情况下尽可能降低写入成本。然后,您可以使用MongoDB's aggregations 查询您的数据以获取感兴趣的值。一个简单的例子是计算 2018-01-01T00:00:00.000Z 和 2018-01-02T23:59:59.999Z 之间传感器 4 的值的数量:

db.values.aggregate([
  {"$match":{"sensor":4,"ts":{"$gte":ISODate("2018-01-01"),"$lt":ISODate("2018-01-02")}}},
  {"$sort":{"ts":-1}},
  { "$group": {
      "_id": {
          "year": { "$year": "$ts" },
          "dayOfYear": { "$dayOfYear": "$ts" },
          "hourOfDay": {"$hour":"$ts"},
          "minuteOfHour": {"$minute":"$ts"},
          "secondOfMinute": {
            "$subtract": [ 
              { "$second": "$ts" },
              { "$mod": [{ "$second": "$ts"}, 1] }
            ]
          }
      },
      "count": { $sum: 1 }
    }},
  ],{"allowDiskUse":true})

更好的是,您可以使用$out stage 保存您的聚合以便更快地访问。

编辑:请注意,您必须正确使用索引才能使这种方法有效。就其本身而言,即使使用我相当有限的 50M 样本文档的测试集,聚合也需要几秒钟。对于索引,我们说的是大约 80 毫秒,以便给您留下印象。

【讨论】:

  • 感谢您的建议,但我认为这并不实用。使用我的原始模式,文档的大小将为每个传感器每个通道每小时 2.8 MB,采样率为 40 Hz。使用您的方法,即 每个传感器每个通道每小时 144,000 个文档。我对您的样本做了Object.bsonsize(),它是 65 字节,这意味着仅文档内容(不包括元数据和索引)每个传感器每个通道每小时 9.4 MB。使用 1M 传感器意味着每个通道每小时 9.36 TB 加上索引 ?
  • @HendyIrawan 我已经建立了一个测试数据库,由 50M 文档组成,磁盘空间消耗 3.2GB,平均 68 字节/文档。有这么多数据进来,无论如何你都需要shard。那些 1M 传感器是估计的还是固定大小的?鉴于您的数据模型,您很可能仅仅因为需要 IOPS 就需要进行更多扩展。
  • @HendyIrawan 您可以查看TICK Stack 来存储和分析数据。
  • 很遗憾,我目前无法选择使用其他基础架构。关于您的架构,这很有趣,但我预计每个样本大约 8 个字节而不是 68 个字节。是的 IOPS 将是一个问题,目前我计划预先分配数组并且只写入至少 1 秒的块(即一次 40 个样本)和最多 1 分钟的块(一次 2400 个样本)。
  • @HendyIrawan 预分配对你没有多大帮助,如果有的话。 WT 使用 COW,所以预分配是徒劳的。更新是我所关心的。您将有一个查询、(读取)、修改、写入循环。由于您期望每小时有数十亿个数据点,恕我直言,您应该立即从共享集群开始(即使它只有一个分片)。无论如何,祝你好运! ;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-23
相关资源
最近更新 更多