DolphinDB 与 MongoDB 在时序数据上的对比测试
DolphinDB 和 MongoDB 都是为大数据而生的数据库。但是两者有这较大的区别。前者是列式存储的多模型数据库,主要用于结构化时序数据的高速存储、查询和分析。后者是文档型的 NoSQL 数据库,可用于处理非结构化和结构化的数据,可以根据键值快速查找或写入一个文档。MongoDB 有着自己最合适的应用场景。但是市场上缺少优秀的大数据产品,不少用户试图使用 MongoDB 来存储和查询物联网和金融领域的结构化时序数据。本测试的目的是评估 MongoDB 是否适合此类海量时序数据集。
时间序列数据库 DolphinDB 和 MongoDB 在时序数据库集上的对比测试,主要结论如下:
DolphinDB 的数据导入速度比 MongoDB 高出两个数量级。数据量越大,性能差距越明显。数据导出方面,DolphinDB 比 MongoDB 快 50 倍左右。
磁盘空间占用方面,MongoDB 占用磁盘是 DolphinDB 的 2~3 倍。数
据库查询性能方面,DolphinDB 在 4 个查询性能测试中速度比 MongoDB 快 30 倍;在 5 个查询性能测试中速度比 MongoDB 快 10~30 倍;在 12 个查询性能测试中速度比 MongoDB 快数倍;仅在两个点查询测试中,DolphinDB 慢于 MongoDB。
1. 测试环境
本次测试在单机上进行,测试设备配置如下:
主机:DELL OptiPlex 7060
CPU:Intel® Core™ i7-8700 CPU@3.20GHZ,6 核 12 线程
内存:32 GB (8GB x 4, 2,666 MHz)
硬盘: 2T HDD (222MB/s 读取;210MB/s 写入)
OS:Ubuntu 18.04 LTS
DolphinDB 选用 Linux0.89 作为测试版本,所有节点最大连接数为 128,数据副本设置为 2,设置 1 个控制节点,1 个代理节点,3 个数据节点。
MongoDB 选用 Linux4.0.5 社区版作为测试版本,shard 集群线程数为 12,所有服务器的最大连接数均为 128。MongoDB 的 shard 集群设置为 1 个 config 服务器,1 个 mongos 路由服务器,3 个分片服务器,其中 config 服务器设置为有 1 个主节点和 2 个从节点的 replica 集群,3 个分片服务器均设置为有 1 个主节点,1 个从节点,1 个仲裁节点的 replica 集群。DolphinDB 和 MongoDB 的参数配置请参考附录 1。
2. 数据集
本报告测试了 DolphinDB 和 MongoDB 在小数据量级(4.2GB)和大数据量级(62.4GB)下的性能。
对于大小两种数据集,我们测试两种数据库在磁盘分区情况下的性能,查询时间均包含了磁盘 IO 的时间。为了保证测试的公平,我们在测试前通过 linux 命令:sync,echo1,2,3 | tee /proc/sys/vm/drop_caches 清空页面缓存,目录缓存和硬盘缓存,随后依次执行 13 条查询,并记录执行的时间。
以下是两个数据集的表结构和分区方法:
设备传感器信息小数据集(CSV 文件,4.2G, 3 千万条数据)
我们选用 TimescaleDB 官网提供的 devices_readings_big.csv(以下简称 readings 数据集)和 device_info_big.csv(以下简称 info 数据集)设备传感器数据作为小数据测试集。readings 数据集包含 3,000 个设备在 10,000 个时间间隔(2016.11.15-2016.11.19)上的传感器信息,包括传感器时间,设备 ID,电池,内存,CPU 等时序统计信息。info 数据集包括 3,000 个设备的设备 ID,版本号,制造商,模式和操作系统等统计信息。
数据来源:https://docs.timescale.com/v1.1/tutorials/other-sample-datasets
数据集共 3 千万 条数据(4.2G),压缩包内包含一张设备信息表和一张设备传感器信息记录表,表结构以及分区方式如下:
readings 数据集
info 数据集
数据集中 device_id 这一字段有 3000 个不同的值,且在 readings 数据集中重复出现,这种情况下使用 string 类型不仅占用大量空间而且查询效率低,DolphinDB 的 symbol 类型可以很好地解决占用空间和效率两个问题。
我们在 DolphinDB 中采用组合分区,将 time 字段作为分区的第一个维度,按天分为 4 个区,再将 device_id 作为分区的第二个维度,每天一共分 10 个区,最后每个分区所包含的原始数据大小约为 100MB。
我们在 MongoDB 中同样采用组合分区的方式,将 time 作为分区的第一维度,根据日期进行范围分区,再将设备 ID 作为第二分区维度,根据设备 ID 进行范围分区。MongoDB 的范围分区是根据块的大小进行分区的,当数据块大小大于某个阈值,数据库会自动将一个大的数据块分为两个小的数据块,实现分区。经过测试,我们发现当 chunkSize(数据块分区阈值)为 1024 时,性能最佳。最终,readings 数据集总共分为 17 个分区。
MongoDB 要求分区字段必须建立索引,因此我们建立日期+设备 ID 的复合索引,复合索引可以加快查询速度,但是 MongoDB 在建立索引时会消耗时间和空间。readings 数据集分区建立 time_1_device_id_1 增序索引耗时为 5 分钟,占用空间大小为 1.1G。建立索引的脚本如下所示:
股票交易大数据集(CSV 文件,62.4G,16 亿条数据)
我们选用纽约证券交易所(NYSE)提供的 2007.08.07-2007.08.10 四天的股市 Level1 报价数据(以下简称 TAQ 数据集)作为大数据测试集,数据集包含 8,000 多支股票在 4 天内的交易时间,股票代码,买入价,卖出价,买入量,卖出量等报价信息。
数据集有 4 个 csv 文件,每个文件在 14G 到 17G 之间,总共大小为 62.4G,大约 16 亿条数据,每个 CSV 文件保存一个交易日的交易信息,数据来源于(https://www.nyse.com/market-data/historical)。TAQ 数据集结构如下所示:
在 DolphinDB 中,我们采用组合分区,将 date 字段作为分区的第一维度,每天一个分区,共四个分区,再将 symbol 字段作为分区的第二维度,根据范围分区,每天分为 100 个分区。最后总共分为 400 个分区,每个分区大约 40MB。
在 MongoDB 中同样采用组合分区方式,分区维度与 DolphinDB 相同,将 chunkSize 设置为 1024,总共分为 385 个分区。
MongoDB 在对 TAQ 数据集分区时建立 date_1_symbol_1 增序索引消耗的时间为 53 分钟,占用空间大小为 19G,建立索引的脚本如下所示:
3. 数据库导入导出性能对比
3.1 导入性能
在 DolphinDB 中使用以下脚本导入:
在 MongoDB 导入 TAQ 数据集时,为了加快导入速度,将 63G 的数据分为 16 个小文件导入,每个文件大小在 3.5G~4.4G 之间,然后使用以下脚本导入:
导入性能如下表所示:
从上表可得,DolphinDB 在导入结构化的时序数据时,速度远快于 MongoDB,下面从几个方面分析导入结果。
(1)横向比较
由于两个数据集的字段数量和字段类型不一样,readings 数据集多为字符串类型,TAQ 数据集多为数值类型,相比于字符串类型,数值类型导入更快,因此可以看到 MongoDB 和 DolphinDB 在导入 TAQ 数据集时,速率更快。
(2)纵向比较
MongoDB 属于文档型数据库,导入速度受文档数量的影响很大,可以看出导入 3 千万条记录大约需 1 小时,导入 16 亿条记录大约需 55 小时,记录条数相差大约 53 倍,导入时间相差约 55 倍,考虑到每条记录字段类型和数据的影响,可以认为导入速度和记录条数大约成正比。
DolphinDB 属于列式数据库,存储这种结构化的数据时,DolphinDB 会将一个字段当成一个列,存储在一个列文件中。导入 readings 数据集时创建了 12 个列文件,导入 TAQ 数据集时,创建了 10 个列文件,列文件数量相差不多。导入 4.2G 数据,需要 63 秒,导入 62.4G 数据,需要 11 分 30 秒。数据集大小相差大约 14.8 倍,导入时间相差大约 11 倍,考虑到列文件数量和存储类型的不同,可以认为导入时间和文件大小大约成正比。
DolphinDB 在导入时序结构化数据时,在列字段类型和数量相差不大的情况下,导入时间和文件大小成正相关,符合列式存储数据库的特点。MongoDB 在导入时序结构化数据时,在字段相差不大的情况下,导入时间和记录条数成正相关,符合文档型存储数据库的特点。
(3)MongoDB 导入相对缓慢的原因分析
DolphinDB 采用列式存储,效率远远高于 MongoDB 的文档型存储。MongoDB 按照记录逐条导入,在记录条数很大的情况下,MongoDB 数据导入时长增加,性能下降。
MongoDB 在 sharding 集群配置时,必须开启 journaling 日志,先写入记录再进行导入操作,降低了其导入速度。
因为 MongoDB 属于 NoSQL 数据库,其没有主键概念,为了保证唯一性约束,数据在导入时必须创建一个数据库自动生成的唯一索引来表征每一条记录,数据导入和索引必须同时进行,因此降低了导入速度。
3.2 导出性能
在 DolphinDB 中使用以下脚本进行数据导出:
在 MongoDB 中使用以下脚本进行数据导出:
小数据集导出性能如下表所示:
4. 数据库磁盘空间占用对比
数据库磁盘空间占用性能对比主要对比 DolphinDB 和 MongoDB 数据库导入 readings 数据集和 TAQ 数据集这两种大小的数据集后,在分区情况下各数据库中的数据所占磁盘空间的大小。磁盘占用指标为数据在磁盘中的大小。
DolphinDB 直接通过读取所有列式数据文件的大小获取,MongoDB 通过 db.stats()获得数据存储的大小。两个数据库均有一个备份,MongoDB 中数据存储大小还包括索引的大小。测试结果如下表所示:
相同数据量,MongoDB 的磁盘占用空间大约是 DolphinDB 的 2~3 倍,主要有以下原因:
(1)DolphinDB 采用列式存储方式,每个列有固定的类型,通过 LZ4 压缩算法,将每个字段按照类型压缩存储为一个列文件。并且针对 symbol 类型,还采用位图压缩算法解决存储空间占用的问题,进一步提高了压缩率。MongoDB 本次测试采用的是 WiredTiger 存储引擎,选用 snappy 压缩算法。
(2)MongoDB 中建立分区数据库均要对分区字段建立索引,进一步导致其存储空间变大,经分析发现,readings 数据集对 time 和 device_id 字段建立的索引大小为 1.1G,TAQ 数据集对 date 和 symbol 字段建立的索引大小为 19G。
5. 数据库查询性能
对于 readings 数据集和 TAQ 数据集,我们对比了以下 8 种常用的 SQL 查询。
1、点查询:根据某一字段的具体值进行查询。
2、范围查询:根据一个或者多个字段的范围根据时间区间进行查询。
3、聚合查询:根据数据库提供的针对字段列进行计数,平均值,求和,最大值,最小值,标准差等聚合函数进行查询。
4、精度查询:根据不同标签维度列进行数据聚合,实现高维或者低维的字段范围查询,测试有 hour 精度,minute 精度。
5、关联查询:根据不同的字段,在进行相同精度,相同的时间范围内进行过滤查询的基础上,筛选出有关联关系的指标列并进行分组。
6、对比查询:根据两个维度将表中某字段的内容重新整理为一张表格(第一维度作为列,第二维度作为行)
7、抽样查询:根据数据库提供的数据采样 API,可以为每一次查询手动指定采样方式进行数据的稀疏处理,防止查询时间范围太大数据量过载的问题。
8、经典查询:实际业务中常用的查询。
执行时间是以毫秒为单位的。为了消除网络传输等不稳定因素的影响,查询性能比较的时间指标为服务器执行某个查询的时间,不包括结果传输和显示的时间。
4.2G 设备传感器信息小数据集查询测试
对于小数据集的测试,我们均测试磁盘分区数据,执行时间包括了磁盘 IO 的时间。为了保证测试的准确性和公正性,每次启动测试前均通过 Linux 系统命令 sync;echo 1,2,3 | tee /proc/sys/vm/drop_caches 清除系统的页面缓存,目录项缓存和硬盘缓存,启动程序后一次执行样例一遍,并记录执行的时间。
DolphinDB 中使用以下脚本得到数据库句柄:
MongoDB 中执行 use device_pt 语句切换数据库至 device_pt 数据库。在执行关联查询时,由于 info 数据集的数据量较小,因此可以把数据加载到内存中。在 MongoDB 中执行 db.device_info.find({})将 3,000 条设备记录全部加载,在 DolphinDB 中执行 loadText(dp_info)将 3,000 条设备记录加载至内存。在 DolphinDB 中使用 timer 计算查询执行耗时,在 MongoDB 中使用 explain()函数获取执行时间。下面是 DolphinDB 在小数据集上的查询脚本,MongoDB 的查询脚本见附录。
查询性能如下表所示:
对于范围查询,在包括了分区字段的查询中,如查询 3,4 所示,MongoDB 可以调用复合索引,DolphinDB 可以通过分区字段加快查询,这种情况下两个数据库的差距在 4 倍之内,并不是很大。在包括了未分区字段的查询中,如查询 5 所示,MongoDB 没法调用未分区字段的索引,需要进行全字段搜索过滤,DolphinDB 则无需搜索不在 where 过滤条件中的字段,这种情况下 DolphinDB 和 MongoDB 的差距进一步扩大。可以看出在处理这种结构化时序数据时, DolphinDB 采取的列式存储的方式的效率比 MongoDB 建立索引的方式更加高效,也更加适用于多维结构化数据的查询。
对于点查询,在查询 1 中,MongoDB 在建立有 time+device_id 索引的情况下可以快速的找到某个时间点的记录,DolphinDB 中按照日期分为 4 天,查找某一天的具体的时间点需要选定一个分区再进行检索,这种情况下 DolphinDB 比 MongoDB 慢。在查询 2 中,MongoDB 的过滤字段仅为设备 ID,没有包括 time 字段,我们从 explain()中发现查询过程中没有调用复合索引,这是因为查询字段必须包括复合索引的首字段,索引才会起作用,查询 1 仅仅根据设备 ID 过滤,不涉及 time 字段的过滤, MongoDB 不会调用复合索引,因此这种情况下,DolphinDB 比 MongoDB 快。
对于关联查询,MongoDB 作为 NoSQL 数据库,仅支持左外连接,并且因为其没有关系型数据库的主键约束,要实现表连接查询只能使用内嵌文档的方式,这种方式并不利于计算和聚合。DolphinDB 作为关系型数据,其支持等值连接,左连接,全连接,asof 连接,窗口连接和交叉连接,表连接查询功能丰富,可以高效方便地处理海量结构化时序数据。从查询 9~10 中我们可以看出 DolphinDB 快于 MongoDB,并且关联查询越复杂,性能差距越大。
对于抽样查询,MongoDB 可以在 aggregation 函数中通过 s a m p l e 语 句 实 现 抽 样 查 询 , 抽 样 方 式 取 决 于 集 合 的 大 小 , N ( 抽 样 数 ) 的 大 小 和 sample 语句实现抽样查询,抽样方式取决于集合的大小,N(抽样数)的大小和 sample 语句实现抽样查询,抽样方式取决于集合的大小,N(抽样数)的大小和 sample 语句在 pipeline 中的位置。DolphinDB 不支持全表抽样,仅支持分区字段抽样。因为两种数据抽样查询的实现过程差别较大,所以不做比较。
对于插值查询,MongoDB 并没有内置的函数可以实现插值查询,而 DolphinDB 支持 4 种插值方式,ffill 向后取非空值填充、bfill 向前去非空值填充、lfill 线性插值、nullFill 指定值填充。
对于对比查询,MongoDB 作为文档型数据库,其存储单元是文档,集合中包含若干文档,文档采用 BSON 格式,没有行和列的概念,因此无法实现选择两个维度将表中某字段的内容整理为一张表(第一个维度作为列,第二个维度作为行)的功能。DolphinDB 中内置有 pivot by 函数语句,选定分类的维度可以方便的将制定内容整理为一张表。因为 MongoDB 不支持对比查询,所以不做比较。
62.4G 股票交易大数据集查询测试
DolphinDB 中使用以下脚本得到数据库句柄:
MongoDB 中执行 use taq_pt_db 语句切换数据库至 taq_pt_db 数据库。
大数据集 DolphinDB 的查询脚本如下所示:
查询性能如下表所示:
在大数据量,两个数据库均做了分区的情况下,DolphinDB 依然比 MongoDB 大约快 5-20 倍。查询 1 是根据日期,股票代码进行的点查询,这种情况下 MongoDB 和 DolphinDB 的性能差距不大,DolphinDB 略慢于 MongoDB,这是 MongoDB 进行复合分区时会建立 date+symbol 复合索引可以较为快速的找到结果,是 MongoDB 比较好的应用场景,但是对比查询 5~9 可知,MongoDB 的计算性能仍不如 DolphinDB,差距大约在 10-20 倍之间。
6. 小结
在处理结构化的时序数据时,无论是数据导入导出、磁盘空间占用还是查询速度,DolphinDB 的性能都比 MongoDB 更加优越。但是,MongoDB 作为文档型的 NoSQL 数据库,在数据模型多变的场景下以及处理非结构化数据方面更有优势。
附录
1. DolphinDB 环境配置
2. MongoDB 环境配置
分片服务器配置:主节点:master_shard.txt从节点:slave_shard.txt仲裁节点:arbiter_shard.txt
路由服务器配置:master_mongos.txt
配置服务器:主节点:master_config.txt从节点:slave_config.txt仲裁节点:arbiter_config.txt
评论