时间序列数据和MongoDB:第二部分 - 架构设计佳实践

2020-05-28 00:00:00 数据 文档 时间 应用程序 大小

作者:Robert Walters

译者:刘东华 (Martin Liu)


之前的文章“ 时间序列数据和MongoDB:部分 - 简介 ”中,介绍了时间序列数据的概念,然后介绍了一些常见问题,可用于帮助收集时间序列应用程序。这些问题的答案有助于指导支持大批量生产应用程序部署所需的架构和 MongoDB 数据库配置。现在,我们将重点介绍两种不同的模式设计如何影响读取,写入,更新和删除操作下的内存和磁盘利用率。


在分析结束时,您可能会发现应用程序的佳模式设计可能正在利用模式设计的组合。按照我们下面列出的建议,您将有一个良好的起点,为您的应用程序开发设计佳架构,并适当调整您的环境。


设计时间序列模式



让我们首先说,没有一个架构设计能适合所有的应用场景规范。无论哪种架构,都需要权衡利弊。理想情况下,您希望在内存和磁盘利用率之间实现佳平衡,以获得满足应用程序要求的佳读写性能,并使您能够同时支持数据读取和时间序列数据流分析。


在这篇博文中,我们将介绍各种架构设计配置。首先,每个数据样本存储一个文档,然后使用每个时间序列时间范围的一个文档和每个固定大小的一个文档来存储数据。每个文档存储多个数据样本称为分组。这将在应用程序级别实现,并且不需要在 MongoDB 中专门配置任何内容。借助MongoDB 灵活的数据模型,您可以优化数据,从而为应用程序的要求提供佳性能和粒度。


这种灵活性还允许您的数据模型随着时间的推移适应新的要求 - 例如从不属于原始应用程序设计的新硬件传感器捕获数据。这些新传感器提供的元数据和属性与您在原始设计中使用的传感器不同。有了这些灵活性,您可能会认为 MongoDB 数据库是无主之地,无论发生什么事情,您都可以快速找到一个充满无组织数据的数据库。MongoDB通过模式验证提供尽可能多的控制,允许您完全控制并强制执行诸如必填字段和可接受值范围之类的事情,仅举几例。


为了帮助说明架构设计和分组如何影响性能,请考虑我们要存储和分析历史股票价格数据的场景。我们的样本股票价格生成器应用程序每秒为其跟踪的给定数量的股票创建样本数据。一秒是本例中每个股票代码收集的小数据时间间隔。如果您想在自己的环境中生成样本数据,可以在GitHub上使用 StockGen工具。值得注意的是,尽管本文档中的样本数据使用了股票代码作为示例,但您可以将这些相同的设计概念应用于任何时间序列场景,例如物联网传感器的温度和湿度读数。


用于生成样本数据的 StockGen 工具将生成相同的数据并将其存储在两个不同的集合中:StockDocPerSecond 

和 StockDocPerMinute,每个集合包含以下模式:


场景一:


每个数据点一个文档

图一:表示每秒一个文档粒度的示例文档


场景二:


每分钟一个文档的基于时间的分段

图2:表示一分钟粒度的示例文档


请注意,字段“p”包含一个子文档,其中包含每分钟的值。


设计架构比较


让我们根据 StockGen 工具生成的4周数据,比较和对比存储大小和内存影响的数据库指标。在评估数据库性能时,衡量这些指标非常有用。


对数据存储的影响


在我们的应用程序中,小级别的时间粒度是秒。如方案1中所述,每秒存储一个文档对于来自关系数据库背景的人来说是舒适的模型概念。这是因为我们每个数据点使用一个文档,这类似于表格模式中每个数据点的行。如图3和图4所示,该设计将产生每单位时间大数量的文档和集合大小。


图3:文档计数随时间的变化,比较每秒与每分钟架构设计


图4:每种方案的数据大小和存储大小之间的比较


图4显示了每个集合的两种尺寸。系列中的个值是存储在磁盘上的集合的大小,而第二个值是数据库中数据的大小。这些数字不同,因为 MongoDB 的 WiredTiger 存储引擎支持静态数据压缩。从逻辑上讲,PerSecond 集合是605MB,但在磁盘上它占用大约190 MB的存储空间。


对内存利用率的影响


大量文档不仅会增加数据存储消耗,还会增加索引大小。在每个集合上创建了一个索引,并覆盖了符号和日期字段。与将自己定位为时间序列数据库的一些键值数据库不同,MongoDB提供了二级索引,使您可以灵活地访问数据并允许您优化应用程序的查询性能。


图5:PerSecond 和 PerMinute之间的索引大小(MB)比较


两个集合中每个集合中定义的索引的大小如图5所示。当索引和近使用的文档适合由WiredTiger 缓存分配的内存(我们称之为“工作集”)时,提供 MongoDB 的佳性能。在我们的例子中,我们在4周内仅生成了5只股票的数据。鉴于这个小测试用例,我们的数据已经为 PerSecond 场景生成了一个大小为103MB的索引。请记住,有一些优化,如索引前缀压缩这有助于减少索引的内存占用。但是,即使使用这些优化,正确的模式设计对于防止失控的索引大小也很重要。鉴于增长的轨迹,应用程序需求的任何变化,例如在我们的示例场景中跟踪超过5种股票或超过4周的价格,将对内存施加更大的压力,并终需要索引分页到磁盘。发生这种情况时,表现会降低。要缓解这种情况,请考虑水平扩展。


水平缩放


随着数据大小的增加,当达到 MongoDB 副本集中托管的主要 mongod 服务器的物理限制时,终可能会水平扩展。


通过 MongoDB Sharding 水平扩展,可以提高性能,因为索引和数据将分布在多个MongoDB 节点上。查询不再针对特定的主节点。相反,它们由称为查询路由器(mongos)的中间服务处理,该服务将查询发送到包含满足查询的数据的特定节点。这对应用程序完全透明 - MongoDB会处理所有路由。


场景三:


基于大小的分组


比较之前的场景时的关键点是,分段数据具有显着的优势。方案2中描述的基于时间的分段将整整一分钟的数据存储到单个文档中。在诸如 IoT 的基于时间的应用中,传感器数据可以以不规则的间隔生成,并且一些传感器可以提供比其他传感器数据更多的数据。在这些场景中,基于时间的分段可能不是架构设计的佳方法。另一种策略是基于大小的分组。


通过基于大小的分组,我们根据一定数量的发射传感器事件或一整天(以先到者为准)围绕一个文档设计我们的模式。


要查看基于大小的存储分区,请考虑存储传感器数据并将存储区大小限制为每个文档200个事件或一天(以先到者为准)的方案。注意:200限制是任意数字,可以根据需要进行更改,无需更改应用程序或模式迁移。

图6:稀疏数据的基于大小的分段


图6中显示了一个基于大小的示例存储桶。在此设计中,尝试将每个文档的插入限制为任意数量或特定时间段似乎很困难; 但是,使用 upsert 很容易,如下面的代码示例所示:

图7:要添加到基于大小的存储桶的示例代码


当新的传感器数据进入时,它只是附加到文档,直到样本数达到200,然后由于我们的upsert:true子句而创建一个新文档。


此方案中的佳索引将在 {deviceid:1,sensorid:1,day:1,nsamples:1} 上。当我们更新数据时,这一天完全匹配,这是超级高效的。查询时,我们可以在单个字段上指定日期或日期范围,这也是有效的,并且使用 UNIX 时间戳首先和后一个进行过滤。请注意,我们使用整数值。这些实际上存储为 UNIX 时间戳,仅占用32位存储,而 ISODate占用64位。虽然与 ISODate 相比没有显着的查询性能差异,但如果您计划终获得数 TB的摄取数据并且不需要存储小于一秒的粒度,则存储为UNIX时间戳可能会很重要。


固定大小的分段数据将产生非常类似的数据库存储和索引改进,如在场景2中每次分段时所见。这是在 MongoDB 中存储稀疏的 IoT 数据的有效方法之一。


如何处理旧数据


我们应该存储所有数据吗?超过特定时间的数据对您的组织有用吗?旧数据应该如何访问?它是否可以在您需要时从备份中简单地恢复,还是需要在线并且可以作为历史分析的活动存档实时访问用户?正如我们在本系列博文的第1部分中所述,这些是在上线之前应该提出的一些问题。


处理旧数据有多种方法,根据您的具体要求,某些方法可能比其他方法更适用。选择符合您要求的产品。


预聚合


您的应用程序是否真的需要为多年前生成的每个事件提供单个数据点?在大多数情况下,保持这种数据粒度的资源成本超过了能够随时查询到这个级别的好处。在大多数情况下,可以预先聚合和存储数据以便快速查询。在我们的股票示例中,我们可能只想将每天的收盘价存储为值。在大多数体系结构中,预聚合值存储在单独的集合中,因为通常对历史数据的查询与实时查询不同。通常使用历史数据,查询会查找随时间推移的趋势与个别实时事件。通过将此数据存储在不同的集合中,您可以通过创建更高效的索引来提高性能,而不是在实时数据之上创建更多索引。


离线档案策略


归档数据时,与数据检索相关的 SLA 是什么?是否恢复可接受的数据备份,或者数据是否需要在线并准备好在任何给定时间查询?这些问题的答案将有助于推动您的档案设计。如果您不需要实时访问归档数据,则可能需要考虑备份数据并将其从实时数据库中删除。可以使用 MongoDB Ops Manager 备份生产数据库,或者如果使用 MongoDB Atlas 服务,则可以使用完全托管的备份解决方案。


使用 remove 语句删除文档


通过数据库备份或 ETL 过程将数据复制到归档存储库后,可以通过 remove 语句从MongoDB集合中删除数据,如下所示:

尽管TTL索引很方便,但请记住每分钟都会进行一次检查,并且无法配置间隔。如果您需要更多控制以便在一天的特定时间内不会发生删除,则可能需要安排执行删除的批处理作业,而不是使用TTL索引。


删除集合删除文档


请务必注意,使用 remove 命令或 TTL 索引会导致高磁盘I / O。 在可能处于高负载的数据库上,这可能是不可取的。从实时数据库中删除记录的有效和快捷的方法是删除集合。如果您可以设计应用程序,使每个集合代表一段时间,当您需要存档或删除数据时,您需要做的就是删除集合。这可能需要您的应用程序代码中的一些查询才能知道应该删除哪些集合。当您发出删除时,MongoDB 也必须从所有受影响的索引中删除数据,这可能需要一段时间,具体取决于数据和索引的大小。


在线档案策略


如果仍需要实时访问归档数据,请考虑这些查询发生的频率以及仅存储预聚合结果是否足够。


分片存档数据


归档数据和保持数据实时可访问的一种策略是使用分区分片来对数据进行分区。分片不仅有助于跨多个节点水平扩展数据,还可以标记分片范围,以便将数据分区固定到特定分片。节省成本的措施可能是将存档数据存储在运行成本较低的磁盘的分片上,并定期调整分片本身定义的时间范围。这些范围将使平衡器自动在这些存储层之间移动数据,为您提供分层的多维度存储。


通过可查询备份访问存档数据


如果不经常访问您的归档数据并且查询性能不需要满足任何严格的延迟 SLA,请考虑使用 MongoDB Atlas 或 MongoDB OpsManager 的可查询备份功能备份数据。 可查询备份允许您连接到备份并向备份本身发出只读命令,而无需先恢复备份。


查询数据池中的数据


MongoDB 是一种廉价的解决方案,不仅适用于长期存档,也适用于您的数据池。投资Apache Spark 等技术的公司可以利用 MongoDB Spark Connector。此连接器将MongoDB 数据实现为 DataFrames 和 Datasets,以便与 Spark 和机器学习,图形,数据流和 SQL API 一起使用。


关键要点


一旦应用程序在生产中生效并且大小为 TB 级,从资源的角度来看,任何重大变化都可能非常昂贵。考虑这样一种情况,即您拥有6 TB的 IoT 传感器数据,并以每秒5万次插入的速率累积新数据。读取的性能开始成为一个问题,您意识到您没有正确扩展数据库。除非您愿意停止应用,否则此配置中的架构更改(例如,从原始数据存储迁移到分区存储)可能需要构建填充程序,临时暂存区域和各种临时解决方案以将应用程序移动到新的架构。文章的寓意是规划增长并正确设计适合您的应用程序的 SLA 和要求的佳时间序列模式。


本文分析了两种不同的模式设计,用于存储股票价格的时间序列数据。终赢得此股票价格数据库的架构是否是您方案中佳的架构?也许。由于时间序列数据的性质和典型的数据快速提取,答案实际上可能是利用针对读取或写入大量用例的集合的组合。好消息是,使用 MongoDB 灵活的架构,很容易进行更改。实际上,您可以运行两个不同版本的应用程序,将两个不同的模式写入同一个集合。但是,不要等到查询性能开始受到影响才能找到佳设计,因为将现有文档的 TB 迁移到新架构可能需要时间和资源,并延迟应用程序的未来版本。在进行终设计之前,您应该进行实际测试。引用一句着名的谚语:“三思而后行”。


在下一篇博客文章“ 使用 MongoDB 查询,分析和呈现时间序列数据 ”中,我们将研究如何有效地从MongoDB 中存储的时间序列数据中获取价值。


主要提示:


  • 不推荐使用 MMAPV1 存储引擎,因此请使用默认的 WiredTiger 存储引擎。请注意,如果您从几年前开始阅读较旧的架构设计佳实践,则它们通常基于较旧的 MMAPV1 技术构建。

  • 了解时间序列应用程序的数据访问要求。

  • 架构设计会影响资源。关于模式设计和索引,“三思而后行”。

  • 如果可能,使用真实数据和实际应用程序测试模式模式。

  • 分段数据减少了索引大小,从而大大降低了硬件要求。

  • 时间序列应用程序传统上捕获非常大量的数据,因此只创建它们对应用程序的查询模式有用的索引。

  • 考虑多个集合:一个集中于编写大量插入和近的数据查询,另一个集合具有重叠在预聚合数据上的历史查询的分块数据。

  • 当索引的大小超过托管 MongoDB 的服务器上的内存量时,请考虑水平扩展以扩展索引并加载多个服务器。

  • 确定数据到期的时间点以及要采取的操作,例如归档或删除。



/

译者简介

/

刘东华 (Martin Liu):

野生程序猿, 有 Web 前后端开发经验。

主要方向: 服务器运维, 数据库, 后端开发。


往期回顾

热门活动:

2018年MongoDB中文社区广州大会 参会指南

干货分享 | MongoDB 中文社区2018北京大会PPT及视频下载


技术文章:

浅析MongoDB中的意向锁

使用MongoDB开发过程常见错误分析

MongoDB Aggregate 业务场景实战

MongoDB 集群请求连接被拒绝的分析

完美数据迁移-MongoDB Stream的应用

MongoDB 新功能介绍-Change Streams

使用mlaunch和m快速搭建MongoDB测试集群


精彩译文:

时间序列数据和MongoDB:部分 - 简介

Java与MongoDB 4.0多文档事务新特性体验



相关文章