InfluxDB是一款開源的時序數據庫,由Go語言實現。它的核心是一款定製的存儲引擎TSM Tree,對時間序列數據作了優化,優先考慮插入和查詢數據的性能。InfluxDB使用類SQL的查詢語言InfluxQL,並提供開箱即用的時間序列數學和統計函數。適用於監控、實時分析、物聯網、傳感器數據等應用場景。是目前最爲流行的時間序列數據庫。java
DolphinDB Database 是一款分析型的分佈式時序數據庫,內置處理流式數據處理引擎,具備內置的並行和分佈式計算的功能,並提供分佈式文件系統,支持集羣擴展。DolphinDB以C++編寫,響應速度極快。提供相似於Python的腳本語言對數據進行操做,支持類標準SQL的語法。提供其它經常使用編程語言的API,方便與已有應用程序集成。在金融領域中的歷史數據分析建模與實時流數據處理,以及物聯網領域中的海量傳感器數據處理與實時分析等場景中表現出色。數據庫
本文將會對DolphinDB和InfluxDB進行性能測試對比。編程
在本次測試中,硬件配置以下:app
設備:DellXPS 8920(07DC)編程語言
CPU:Inter® Core™ i7-7700 CPU @ 3.60GHz,4核心8線程分佈式
內存:16GB函數
硬盤:512GB SSD工具
操做系統:Ubuntu 16.04 x64性能
因爲InfluxDB集羣版本閉源,因此本次測試使用的DolphinDB和InfluxDB均爲單機版本,而且全部配置項都是默認配置。測試
在本次測試中,咱們使用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個區。
表1.數據類型映射
咱們對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。
表2. Quotes_Small表查詢性能測試結果(數據量:18,314,172)
表3. Quotes_Big表查詢性能測試結果(數據量:78,721,394)
咱們測試了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。
表4. Quotes_Small表計算性能測試結果(數據量:18,314,172)
表5. Quotes_Big表計算性能測試結果
咱們分別使用了DolphinDB和InfluxDB的JavaAPI,比較了在相同的環境下寫入相同規模的數據所須要的時間。因爲InfluxDB的默認batch-size爲5000,所以在兩個數據庫的寫入程序中,單次插入的數據量都爲5000條。咱們還比較了從數據庫中導出一樣規模的csv文件所須要的時間。比較的結果是,不管是寫入性能仍是導出性能,DolphinDB都優於InfluxDB。對於相同規模的18,314,172條數據,DolphinDB的導出速度爲InfluxDB的2倍;寫入速度約爲12倍。
測試結果以下表6所示。表格中的數據單位爲毫秒。測試代碼詳見附錄4。
表6. I/O性能測試結果
咱們比較了Quotes_Small表和Quotes_Big表由DolphinDB和InfluxDB存儲後在磁盤上的佔用空間。結果顯示,兩款數據庫都對數據進行了壓縮存儲,壓縮率大體處於同一個數量級,都在20%-30%之間,但DolphinDB的壓縮效果更佳。
測試結果以下表7所示,表中的數據單位爲MB。
表7. 磁盤佔用空間測試結果
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」) 12345678910111213141516171819202122232425262728293031 複製代碼
附錄2. 數據庫查詢性能腳本
表8. InfluxDB查詢性能測試用例
表9. DolphinDB查詢性能測試用例
附錄3. 內置函數計算性能測試用例
表10. 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); } } 123456789101112131415161718192021222324252627282930313233 複製代碼
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); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 複製代碼
數據導出:
InfluxDB:
$ influx -host localhost -database Quotes -format csv -execute 「」 >Quotes.csv 1 複製代碼
DolphinDB:
saveText(Quotes, DATA_DIR + "/Quotes.csv") 1 複製代碼
郵箱:info@dolphindb.com