DolphinDB与InfluxDB对比测试报告

Stella981
• 阅读 766

DolphinDB Database 是一款分析型的分布式时序数据库,内置处理流式数据处理引擎,具有内置的并行和分布式计算的功能,并提供分布式文件系统,支持集群扩展。DolphinDB以C++编写,响应速度极快。提供类似于Python的脚本语言对数据进行操作,支持类标准SQL的语法。提供其它常用编程语言的API,方便与已有应用程序集成。在金融领域中的历史数据分析建模与实时流数据处理,以及物联网领域中的海量传感器数据处理与实时分析等场景中表现出色。

InfluxDB是一款开源的时序数据库,由Go语言实现。它的核心是一款定制的存储引擎TSM Tree,对时间序列数据做了优化,优先考虑插入和查询数据的性能。InfluxDB使用类SQL的查询语言InfluxQL,并提供开箱即用的时间序列数学和统计函数。适用于监控、实时分析、物联网、传感器数据等应用场景。是目前最为流行的时间序列数据库。

本文将会对DolphinDB和InfluxDB进行性能测试对比。

在本次测试中,硬件配置如下:

设备:DellXPS 8920(07DC)

CPU:Inter® Core™ i7-7700 CPU @ 3.60GHz,4核心8线程

内存:16GB

硬盘:512GB SSD

操作系统:Ubuntu 16.04 x64

由于InfluxDB集群版本闭源,所以本次测试使用的DolphinDB和InfluxDB均为单机版本,并且所有配置项都是默认配置。

1. 数据生成

在本次测试中,我们使用NYSE网站上2016年10月24日纽约交易所的股票交易数据生成了Quotes_Big表。在其中取出一部分数据构成Quotes_Small表。表结构如表1所示。Quotes_Big表中的数据量为78,721,394条,Quotes_Small表中数据量为18,314,172条。数据下载链接及预处理脚本详见附录1。

由于DolphinDB和InfluxDB在存储方式上的差异,我们采用如下设计:我们将Time列指定为InfluxDB中的timestamp;将Exchange和Symbol列指定为InfluxDB中的Tag列(类似于带索引的列);将Bid_Price、Bid_Size、Offer_Price、Offer_Size指定为InfluxDB中的field列(类似于无索引的列)。在DolphinD系统中根据Symbol列在磁盘上进行RANGE分区,分为8个区。

DolphinDB与InfluxDB对比测试报告
表1.数据类型映射

2. 数据库查询性能测试

我们对11种常用的SQL查询进行了对比测试。Quotes_Small表的测试结果如下表2所示,Quotes_Big表的测试结果如下表3所示。其中,对于DolphinDB的测试,我们使用的是DolphinDB官方的GUI;对于InfluxDB,我们使用的是官方的PythonAPI。执行时间以毫秒为单位,只包含查询本身执行的时间,而不包含结果显示的时间。为了减少特殊值的影响,每个查询都执行10次,表中数据是10次查询的总时间。本次测试的脚本详见附录2。

在几乎所有测试中,DolphinDB的性能都领先InfluxDB多倍,某些情况下的差距甚至超过2个数量级(100倍)。InfluxDB唯一领先DolphinDB的测试是在Quotes_Small表的第4个测试项目,这是由于在数据量较小时DolphinDB并行搜索分区的优势不明显。然而,在数据量较大的情况下,DolphinDB在所有测试中上的性能都优于InfluxDB。DolphinDB可以并行搜索分区的设计使它在过滤性查询方面的性能全面超过了InfluxDB。

DolphinDB与InfluxDB对比测试报告
表2. Quotes_Small表查询性能测试结果(数据量:18,314,172)
DolphinDB与InfluxDB对比测试报告
表3. Quotes_Big表查询性能测试结果(数据量:78,721,394)

3. 内置函数计算性能测试

我们测试了8个DolphinDB和InfluxDB都提供的常用内置函数。在所有测试中,DolphinDB的性能都优于InfluxDB 1-2个数量级(10-100倍)。由于DolphinDB对带有滑动窗口的函数极好的优化,在“滑动平均值”(moving_average)的测试中,DolphinDB的性能领先InfluxDB 3个数量级以上(>1000倍)。

InfluxDB在Quotes_Big表的标准差与滑动平均值计算中在长时间的卡顿后引发内存不足的问题(out of memory),说明InfluxDB对大规模数据的复杂分析场景支持不足。而DolphinDB在此方面性能优异。

Quotes_Small表的测试结果如下表4所示,Quotes_Big表的测试结果如下表5所示。表格中数据的单位为毫秒。为减少特殊值的影响,每个计算都进行了10次。测试脚本详见附录3。

DolphinDB与InfluxDB对比测试报告
表4. Quotes_Small表计算性能测试结果(数据量:18,314,172)
DolphinDB与InfluxDB对比测试报告
表5. Quotes_Big表计算性能测试结果

4. I/O性能测试

我们分别使用了DolphinDB和InfluxDB的JavaAPI,比较了在相同的环境下写入相同规模的数据所需要的时间。由于InfluxDB的默认batch-size为5000,因此在两个数据库的写入程序中,单次插入的数据量都为5000条。我们还比较了从数据库中导出同样规模的csv文件所需要的时间。比较的结果是,无论是写入性能还是导出性能,DolphinDB都优于InfluxDB。对于相同规模的18,314,172条数据,DolphinDB的导出速度为InfluxDB的2倍;写入速度约为12倍。

测试结果如下表6所示。表格中的数据单位为毫秒。测试代码详见附录4。
DolphinDB与InfluxDB对比测试报告
表6. I/O性能测试结果

5. 磁盘占用空间测试

我们比较了Quotes_Small表和Quotes_Big表由DolphinDB和InfluxDB存储后在磁盘上的占用空间。结果显示,两款数据库都对数据进行了压缩存储,压缩率大致处于同一个数量级,都在20%-30%之间,但DolphinDB的压缩效果更佳。

测试结果如下表7所示,表中的数据单位为MB。
DolphinDB与InfluxDB对比测试报告
表7. 磁盘占用空间测试结果

6.其他方面对比

DolphinDB除了在基准测试中体现出优越的性能之外,还具有如下优势:

(1)InfluxDB通过InfluxQL来操作数据库,这是一种类SQL语言;而DolphinDB内置了完整的脚本语言,不仅支持SQL语言,而且支持命令式、向量化、函数化、元编程、RPC等多种编程范式,可以轻松实现更多的功能。

(2)InfluxDB对于特定文件格式数据例如csv文件的批量导入没有很好的官方支持。用户只能通过开源第三方工具或自己实现文件的读取,规整为InfluxDB指定的输入格式,再通过API进行批量导入。单次只能导入5000行,不仅操作复杂,效率也极其低下。与之对比,DolphinDB的脚本语言中提供了loadText、loadTextEx函数,用户可以在脚本中直接导入txt或csv文件,而且效率更高,对用户更友好。

(3)DolphinDB提供400余种内置函数,可满足金融领域的历史数据建模与实时流数据处理,及物联网领域中的实时监控与数据实时分析处理等不同的场景需求。提供时序数据处理需要的领先、滞后、累积窗口、滑动窗口等多种指标的函数,且在性能上进行了优化,性能极优。 因而与InfluxDB相比,DolphinDB拥有更多的适用场景。

(4)InfluxDB不支持表连接,而DolphinDB不仅支持表连接,还对asof join及window join等非同时连接方式做了优化。

(5)InfluxDB中,对时间序列的分组(GroupBy)最大单位是星期(week);而DolphinDB支持对所有内置时间类型的分组,最大单位为月(month)。因此在时间序列这一特性上,DolphinDB也有更好的支持。

(6)DolphinDB采用分布式表引擎时支持事务,而且在一个分区的多个副本写入时,保证强一致性。

附录

附录1. 数据下载即预处理脚本

数据下载链接:nyxdata

在DolphinDB中进行数据预处理,预处理脚本如下:

DATA_DIR = "…"
PTNDB_DIR = DATA_DIR + "/NYSETAQSeq"    
db = database(PTNDB_DIR, SEQ, 16)
def convertTime(x) {
    return string(nanotimestamp(2016.10.24).temporalAdd(x/1000, "us")).substr(0, 26)
}

// 产生 Quotes_Big.csv
Quotes = loadTextEx(db, `Quotes, , DATA_DIR+"/EQY_US_ALL_NBBO_20161024", '|')
Quotes_Big = select convertTime(time) as Time, Exchange, Symbol, Bid_Price, Bid_Size, Offer_Price, Offer_Size from Quotes
saveText(Quotes_Big, DATA_DIR + "/Quotes_Big.csv")

//产生Quotes_Small.csv
symbols = exec distinct Symbol from Quotes
symbols = symbols[0..(symbols.size() - 1) % 4 == 3]
Quotes_Small = select * from Quotes_Big where Symbol in symbols
saveText(Quotes_Small, DATA_DIR + "/Quotes_Small.csv")

// 建立数据库和数据表
if (existsDatabase(PTNDB_DIR)) {
dropDatabase(PTNDB_DIR)
}

sym = `A`D`G`J`M`P`S`V`Z
db = database(PTNDB_DIR, RANGE_PTN, symbol(sym))

// Quotes_Big表
Quotes = loadTextEx(db, `Quotes, `Symbol, DATA_DIR + “/Quotes_Big.csv”)

// Quotes_Small表
Quotes = loadTextEx(db, `Quotes, `Symbol, DATA_DIR + “/Quotes_Small.csv”)

附录2. 数据库查询性能脚本

DolphinDB与InfluxDB对比测试报告
表8. InfluxDB查询性能测试用例

DolphinDB与InfluxDB对比测试报告
表9. DolphinDB查询性能测试用例

附录3. 内置函数计算性能测试用例
DolphinDB与InfluxDB对比测试报告
表10. InfluxDB内置函数计算性能测试用例
DolphinDB与InfluxDB对比测试报告
表11. DolphinDB内置函数计算性能测试用例

附录4. I/O性能测试程序

InfluxDBInsert.java:

package test;

import org.influxdb.InfluxDB;
import org.influxdb.InfluxDBFactory;
import org.influxdb.dto.BatchPoints;
import org.influxdb.dto.Point;
import java.util.concurrent.TimeUnit;

public class InfluxDBInsert {
    public static void main(String[] args) {
InfluxDBinfluxDB = InfluxDBFactory.connect("http://192.168.1.30:8086");
        int size = 18314172;
        long timer = 0;
        for (int i = 0; i< size; i++) {
BatchPoints.BuilderbatchBuilder = BatchPoints.database("TEST");
            for (int j = 0; j < 5000 &&i< size; i++, j++) {
Point.Builder builder = Point.measurement("TEST")
.time(System.nanoTime(), TimeUnit.NANOSECONDS)
.addField("Bid_Price", 2.5)
.addField("Bid_Size", 5)
.addField("Offer_Price", 12.5)
.addField("Offer_Size", 6)
.tag("Exchange", "B")
.tag("Sym", "ALV");
batchBuilder.point(builder.build());
            }
            long start = System.currentTimeMillis();
influxDB.write(batchBuilder.build());
            timer += (System.currentTimeMillis() - start);
        }
System.out.println(timer);
    }
}

DolphinDBInsert.java:

package test;

import com.xxdb.DBConnection;
import com.xxdb.data.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class DolphinDBInsert {
    public static void main(String[] args) throws IOException {
        int size = 18314172;
        int range = 5000;
BasicLongVector Time = new BasicNanoTimestampVector(range);
BasicDoubleVectorBid_Price = new BasicDoubleVector(range);
BasicIntVectorBid_Size = new BasicIntVector(range);
BasicDoubleVectorOffer_Price = new BasicDoubleVector(range);
BasicIntVectorOffer_Size = new BasicIntVector(range);
BasicStringVector Exchange = new BasicStringVector(range);
BasicStringVector Symbol = new BasicStringVector(range);

DBConnection conn = new DBConnection();
conn.connect("192.168.1.30", 8888);
conn.run("if(existsDatabase('/home/psui/Desktop/workspace/data/NYSETAQ')){dropDatabase('/home/psui/Desktop/workspace/data/NYSETAQ')}");
conn.run("db = database('/home/psui/Desktop/workspace/data/NYSETAQ', RANGE, 0 5 10)");
conn.run("tbl = table(200:0,`Time`Bid_Price`Bid_Size`Offer_Price`Offer_Size`Exchange`Symbol,[NANOTIMESTAMP,DOUBLE,INT,DOUBLE,INT,STRING,SYMBOL])");
conn.run("Quotes = db.createPartitionedTable(tbl,'Quotes','Bid_Size')");

        long timer = 0;
        for (int i = 0; i< size; i++) {
            if (i + 5000 >= size) {
                Time = new BasicNanoTimestampVector(size-i);
Bid_Price = new BasicDoubleVector(size-i);
Bid_Size = new BasicIntVector(size-i);
Offer_Price = new BasicDoubleVector(size-i);
Offer_Size = new BasicIntVector(size-i);
                Exchange = new BasicStringVector(size-i);
                Symbol = new BasicStringVector(size-i);

                for (int j = 0; i< size; i++, j++) {
Time.setLong(j, System.nanoTime());
Bid_Price.setDouble(j, 2.4);
Bid_Size.setInt(j, 6);
Offer_Price.setDouble(j, 3.5);
Offer_Size.setInt(j, 2);
Exchange.setString(j, "A");
Symbol.setString(j, "ALV");
                }
            } else {
                for (int j = 0; j < 5000 &&i< size; i++, j++) {
Time.setLong(j, System.nanoTime());
Bid_Price.setDouble(j, 2.4);
Bid_Size.setInt(j, 6);
Offer_Price.setDouble(j, 3.5);
Offer_Size.setInt(j, 2);
Exchange.setString(j, "A");
Symbol.setString(j, "ALV");
                }
            }
            Map<String, Entity> map = new HashMap<>();
map.put("Time", Time);
map.put("Bid_Price", Bid_Price);
map.put("Bid_Size", Bid_Size);
map.put("Offer_Price", Offer_Price);
map.put("Offer_Size", Offer_Size);
map.put("Exchange", Exchange);
map.put("Symbol", Symbol);
            long start = System.currentTimeMillis();
conn.upload(map);
conn.run("tbl = table(Time,Bid_Price,Bid_Size,Offer_Price,Offer_Size,Exchange,Symbol)");
conn.run("Quotes.append!(tbl)");
            timer += (System.currentTimeMillis()-start);
        }
conn.run("Quotee = loadTable(db, 'Quotes')");
        Entity entity = conn.run("select count(*) from Quotee");
System.out.println(entity.getString());
System.out.println(timer);
    }
}

数据导出:

InfluxDB:

$ influx -host localhost -database Quotes -format csv -execute “” >Quotes.csv

DolphinDB:

saveText(Quotes, DATA_DIR + "/Quotes.csv")

官网:www.dolphindb.com

邮箱:info@dolphindb.com

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Python3:sqlalchemy对mysql数据库操作,非sql语句
Python3:sqlalchemy对mysql数据库操作,非sql语句python3authorlizmdatetime2018020110:00:00coding:utf8'''
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Stella981 Stella981
3年前
DolphinDB与InfluxDB对比测试报告 第二期
!时序数据库DolphinDB与InfluxDB对比测试报告2(https://pic1.zhimg.com/v292d2e16fa4520f745861616780d0bc16_1440w.jpg?source172ae18b)近日,我们曾发布测试报告DolphinDB与InfluxDB对比测试报告(https://www.oschina
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这