金融市場高頻數據應當如何管理 —— DolphinDB與pickle的性能對比測試和分析

金融市場L1/L2的報價和交易數據是量化交易研究很是重要的數據。國內全市場L1/L2的歷史數據約爲20~50T,每日新增的數據量約爲20~50G。傳統的關係數據庫如MS SQL Server或MySQL均沒法支撐這樣的數據量級,即使分庫分表,查詢性能也遠遠沒法達到要求。例如Impala和Greenplum的數據倉庫,以及例如HBase的NoSQL數據庫,能夠解決這個數據量級的存儲,可是這類通用的存儲引擎缺少對時序數據的友好支持,在查詢和計算方面都存在嚴重的不足,對量化金融廣泛採用的Python的支持也極爲有限。html

數據庫的侷限性使得一部分用戶轉向文件存儲。HDF5,Parquet和pickle是經常使用的二進制文件格式,其中pickle做爲Python對象序列化/反序列的協議很是高效。因爲Python是量化金融和數據分析的經常使用工具,所以許多用戶使用pickle存儲高頻數據。但文件存儲存在明顯的缺陷,譬如大量的數據冗餘,不一樣版本之間的管理困難,不提供權限控制,沒法利用多個節點的資源,不一樣數據間的關聯不便,數據管理粒度太粗,檢索和查詢不便等等。node

目前,愈來愈多的券商和私募開始採用高性能時序數據庫DolphinDB來處理高頻數據。DolphinDB採用列式存儲,並提供多種靈活的分區機制,可充分利用集羣中每一個節點的資源。DolphinDB的大量內置函數對時序數據的處理和計算很是友好,解決了傳統關係數據庫或NoSQL數據庫處理時序數據方面的侷限性。使用DolphinDB處理高頻數據,既能夠保證查詢與計算的超高性能,又能夠提供數據管理、權限控制、並行計算、數據關聯等數據庫的優點。python

本文測試DolphinDB和pickle在數據讀取方面的性能。與使用pickle文件存儲相比,直接使用DolphinDB數據庫,數據讀取速度可最多可提高10倍以上;若爲了考慮與現有Python系統的集成,使用DolphinDB提供的Python API讀取數據,速度最多有2~3倍的提高。有關DolphinDB數據庫在數據管理等方面的功能,讀者可參考DolphinDB的在線文檔教程git

1. 測試場景和測試數據

本次測試使用瞭如下兩個數據集。數據庫

  • 數據集1是美國股市一天(2007.08.23) Level 1的報價和交易數據。該數據共10列,其中2列是字符串類型,其他是整型或浮點數類型,存儲在dolphindb中的表結構以下表,一天的數據約爲2億3000萬行。csv文件大小爲9.5G,轉換爲pickle文件後大小爲11.8G。
列名 類型
symbol SYMBOL
date DATE
time SECOND
bid DOUBLE
ofr DOUBLE
bidsiz INT
ofrsiz INT
mode INT
ex CHAR
mmid SYMBOL
  • 數據集2是中國股市3天(2019.09.10~2019.09.12)的Level 2報價數據。數據集總共78列,其中2列是字符串類型,存儲在dolphindb中的表結構以下表,一天的數據約爲2170萬行。一天的csv文件大小爲11.6G,轉換爲pickle文件後大小爲12.1G。
列名 類型 列名 類型
UpdateTime TIME TotalBidVol INT
TradeDate DATE WAvgBidPri DOUBLE
Market SYMBOL TotalAskVol INT
SecurityID SYMBOL WAvgAskPri DOUBLE
PreCloPrice DOUBLE IOPV DOUBLE
OpenPrice DOUBLE AskPrice1~10 DOUBLE
HighPrice DOUBLE AskVolume1~10 INT
LowPrice DOUBLE BidPrice1~10 DOUBLE
LastPrice DOUBLE BidVolume1~10 INT
TradNumber INT NumOrdersB1~10 INT
TradVolume INT NumOrdersS1~10 INT
Turnover DOUBLE LocalTime TIME

 

DolphinDB database的數據副本數設爲2。將這兩個數據集寫入DolphinDB後,磁盤佔用空間爲10.6G,單份數據僅佔用5.3G,壓縮比約爲8:1。pickle文件沒有采用壓縮存儲。測試發現pickle文件壓縮後,加載時間大幅延長。api

對比測試查詢一天的數據。對DolphinDB Python API與pickle,記錄從客戶端發出查詢到接收到數據並轉換成Python pandas的DataFrame對象的耗時。緩存

  • 對於DolphinDB Python API,整個過程包括三個步驟:(1)從DolphinDB數據庫查詢數據耗時,即若不使用Python API而直接使用DolphinDB查詢所需耗時;(2)把查詢到的數據從DolphinDB數據節點發送到python API客戶端須要的時間;(3) 在客戶端將數據反序列化成pandas DataFrame須要的時間。
  • 對於pickle,耗時爲使用pickle模塊加載pickle數據文件所須要的時間。

2. 測試環境

測試使用的三臺服務器硬件配置以下:安全

主機:PowerEdge R730xd服務器

CPU:E5-2650 24cores 48線程網絡

內存:512G

硬盤:HDD 1.8T * 12

網絡:萬兆以太網

OS:CentOS Linux release 7.6.1810

本次測試使用的是DolphinDB多服務器集羣模式。共使用3臺服務器,每臺服務器部署2個數據節點,每一個節點分配2個10K RPM的HDD磁盤,內存使用限制爲32G,線程數設置爲16。數據庫的副本數設置爲2。測試的客戶機安排在其中的一臺服務器上。

本次測試的DolphinDB服務器版本是1.30.0,Python API for DolphinDB的版本是1.30.0.4。

3. 測試方法

讀寫文件以後,操做系統會緩存相應的文件。從緩存讀取數據,至關於從內存讀取數據,這會影響測試結果。所以每一次測試前,都會清除操做系統的緩存和DolphinDB 的緩存。爲了對比,也測試有緩存時的性能,即不存在磁盤IO瓶頸時的性能。

3.1 測試DolphinDB

測試代碼以下:

#讀取Level 1數據集一天的數據
timer t1 = select * from loadTable("dfs://TAQ", "quotes") where TradeDate = 2007.08.23

#讀取Level 2數據集一天的數據
timer t2 = select * from loadTable("dfs://DataYesDB", "tick") where TradeDate = 2019.09.10

測試步驟以下:

(1)使用Linux命令sudo sh -c "echo 1 > /proc/sys/vm/drop_caches"清理操做系統緩存,可能須要在root用戶下執行。

(2)執行pnodeRun(clearAllCache)清理DolphinDB的數據庫緩存。

(3)執行查詢腳本,此時爲無操做系統緩存時的結果。

(4)再次執行pnodeRun(clearAllCache)清理DolphinDB的數據庫緩存。

(5)再次執行查詢腳本,此時爲有操做系統緩存時的結果。

3.2 測試DolphinDB的Python API

測試代碼以下:

import dolphindb as ddb
import pandas as pd
import time

s = ddb.session()
s.connect("192.168.1.13",22172, "admin", "123456")

讀取Level 1數據集一天的數據
st1 = time.time()
quotes =s.run(''' select * from loadTable("dfs://TAQ", "quotes") where TradeDate = 2007.08.23 ''')
et1 = time.time()
print(et1 - st1)

讀取Level 2數據集一天的數據
st = time.time()
tick = s.run(''' select * from loadTable("dfs://DataYesDB", "tick") where TradeDate = 2019.09.10 ''')
et = time.time()
print(et-st)

測試步驟以下:

(1)使用Linux命令sudo sh -c "echo 1 > /proc/sys/vm/drop_caches"清理操做系統緩存,可能須要在root用戶下執行。

(2)執行pnodeRun(clearAllCache)清理DolphinDB的數據庫緩存。

(3)執行查詢腳本,此時爲無操做系統緩存時的結果。

(4)再次執行pnodeRun(clearAllCache)清理DolphinDB的數據庫緩存。

(5)再次執行查詢腳本,此時爲有操做系統緩存時的結果。

3.3 測試pickle文件

pickle測試代碼以下:

import dolphindb as ddb
import pandas as pd
import time
import pickle

s = ddb.session()
s.connect("192.168.1.13", 22172, "admin", "123456")
tick = s.run('''
select * from loadTable("dfs://DataYesDB", "tick") where TradeDate = 2019.09.10
''')
quotes =s.run('''
select * from loadTable("dfs://TAQ", "quotes")
''')

#將數據集1的Level 1的數據轉換爲pkl文件
quotes.to_pickle("taq.pkl")

#將數據集2的Level 2一天的數據轉換爲pkl文件
tick.to_pickle("level2.pkl")

#使用pickle模塊讀取數據集1的Level 1一天的數據
st1 = time.time()
f = open('taq.pkl', 'rb')
c = pickle.load(f)
et1 = time.time()
print(et1 - st1)
f.close()

#使用pickle模塊讀取數據集2的Level 2一天的數據
f = open('level2.pkl', 'rb')
st = time.time()
c = pickle.load(f)
et = time.time()
print(et - st)
f.close()

測試步驟:

(1)第一次執行測試代碼,此爲pickle無操做系統緩存時的結果。

(2)第二次執行pickle模塊讀取數據的腳本,此時爲pickle有操做系統緩存時的結果。

4. 測試結果分析

如下是讀取美國股市Level 1數據集的測試結果:

場景 無緩存 (秒) 有緩存(秒)
DolphinDB 數據庫查詢 6 6
DolphinDB Python API 38 35
pickle 72 34

如下讀取中國股市Level 2數據集的測試結果:

場景 無緩存(秒) 有緩存(秒)
DolphinDB 數據庫查詢 5 5
DolphinDB Python API 22 22
pickle 70 20

4.1 DolphinDB的性能優點來源

從測試結果看,直接從DolphinDB數據庫查詢,速度最快,超過pickle查詢的10倍以上。在沒有操做系統緩存的狀況下(大部分的實際場景),DolphinDB Python API的查詢速度明顯優於pickle。在有緩存的狀況下,二者相差無幾。有無緩存,對DolphinDB Python API沒有顯著的影響,可是對pickle卻有顯著的影響。這些結果從DolphinDB Python API和pickle的耗時構成,能夠獲得進一步的解釋。

pickle文件存儲在單個HDD裸盤上,讀取性能的極限速度在每秒150MB~200MB之間。讀取一個12G大小的pickle文件,須要70秒左右的時間。可見在當前配置下,pickle文件讀取的瓶頸在磁盤IO。所以當有操做系統緩存時(等價於從內存讀取數據),性能會有大幅提高,耗時主要是pickle文件的反序列化。要提升讀取pickle文件的性能,關鍵在於提高存儲介質的吞吐量,譬如改用SSD或者磁盤陣列。

DolphinDB數據庫與Python API客戶端之間採用了改良的pickle協議。如第1章中所述,使用DolphinDB Python API進行查詢可分爲3個步驟,其中步驟2和3是能夠同時進行的,即一邊傳輸一邊反序列化。所以使用Python API從DolphinDB數據庫查詢的總耗時約等於第1個步驟查詢耗時與第2和3個步驟的較大值之和。在兩個數據集的測試中,不管是否有緩存,DolphinDB數據庫查詢部分的耗時均在5~6秒左右。一天的數據量在DolphinDB數據庫中約爲8G,分三個節點存儲,每一個節點的數據量約爲2.7G。從一個節點查詢,須要傳輸的數據量約爲5.4G(本地節點不須要網絡傳輸),對萬兆以太網而言,對應5~6秒的傳輸時間。所以當前的配置下,DolphinDB數據庫查詢的瓶頸在於網絡,而不是磁盤IO。一天的數據量壓縮以後約1.4G,分佈於12個磁盤中,按照每一個磁盤100mb/s的吞吐量,加載一天數據的磁盤時間約在1.2秒,遠遠低於網絡須要的5~6秒。簡而言之,DolphinDB時序數據庫經過壓縮技術和分佈式技術,大大縮短了加載一天的金融市場數據的時間,使得磁盤IO再也不成爲數據查詢的瓶頸。

如前所述,DolphinDB Python API的反序列化也採用了pickle協議,可是進行了改良,比原版的pickle協議節約了5~6秒時間,正好抵消了數據庫查詢消耗的5~6秒時間。因此在有緩存的狀況下,pickle和DolphinDB Python API耗時幾乎相等。若是要進一步提高DolphinDB Python API的查詢性能,有兩個方向:(1)採用更高速的網絡,譬如從10G升級到100G,第一步查詢的耗時可能從如今的5~6秒縮減到2秒。(2)繼續改良pickle協議。

4.2 字符串對性能的影響

以數據集1爲例,在沒有緩存的狀況下,DolphinDB Python API總共耗時38秒,可是數據庫端的耗時僅5~6秒,80%的時間耗費在pickle反序列化上。經過進一步分析,咱們發現字符串類型對pickle的反序列化有極大的影響。若是查詢時,剔除兩個字符串類型字段,數據集1的時間縮短到19秒,減小了一半。也就是說兩個字符串字段,以20%的數據量,佔了50%的耗時。數據集2總共78個字段,其中2個字段是字符串類型,若是不查詢這兩個字段,時間可從22秒縮減到20秒。

如下是讀取美國股市Level 1(去除字符串字段)數據集的測試結果:

場景 無緩存 (秒) 有緩存(秒)
DolphinDB 19 18
pickle 56 16

如下是讀取中國股市Level 2(去除字符串字段)數據集的測試結果:

場景 無緩存(秒) 有緩存(秒)
DolphinDB 20 20
pickle 66 18

剔除字符串字段,對提高pickle的查詢也有幫助。在數據集1有緩存的狀況下,耗時縮短一半。可是在沒有緩存的狀況下,提高有限,緣由是瓶頸在磁盤IO。

這兩個數據集中的字符串類型數據分別是股票ID和交易所名稱。股票ID和交易所名稱的個數極其有限,重複度很是高。DolphinDB數據庫專門提供了一個數據類型SYMBOL用於優化存儲此類數據。SYMBOL類型爲一個具體的Vector或Table配備一個字典,存儲所有不重複的字符串,Vector內部只存儲字符串在字典中的索引。在前面的測試中,字符串類型數據已經啓用了SYMBOL類型。若是改用STRING類型,DolphinDB的性能會下降。儘管經過SYMBOL類型的優化,爲DolphinDB服務端的查詢以及pickle的序列化節約了很多時間,可是pickle的反序列化這個步驟並無充分利用SYMBOL帶來的優點,存在大量的python對象屢次copy,這是進一步優化的方向之一。

4.3 多任務併發下的性能對比

在實際工做中,常常會多個用戶同時提交多個查詢。爲此咱們進一步測試了DolphinDB Python API和pickle在併發查詢下的性能。測試採用了數據集2。對於DolphinDB Python API,咱們分別測試了鏈接本地服務器的數據節點進行併發查詢,以及鏈接不一樣服務器的數據節點進行併發查詢的性能。對於pickle,咱們併發查詢了同一個節點同一個磁盤的不一樣文件。因爲全局鎖的限制,測試時,開啓多個Python進程從pickle文件或DolphinDB數據庫加載數據。下表是三個鏈接併發查詢數據集2中不一樣日期的Level 2數據的耗時。

場景 鏈接1(秒) 鏈接2(秒) 鏈接3(秒)
DolphinDB API 鏈接本地服務器數據節點 48 43 45
DolphinDB API 鏈接不一樣服務器數據節點 28 35 31
pickle 220 222 219

pickle的耗時線性的從70秒增長了到了220秒。因爲磁盤IO是pickle的瓶頸,在不增長磁盤吞吐量的狀況下,讀任務從1個增長到3個,耗時天然也增長到原先的3倍。當同一個客戶端節點的三個鏈接連到不一樣的服務器數據節點時,耗時增長了50%(10s左右),主要緣由是客戶端節點的網絡達到了瓶頸。當同一個客戶端節點的三個鏈接接入到同一個服務器數據節點時,耗時增長了100%(22s左右),主要緣由是客戶端節點和接入的數據節點的網絡同時達到了瓶頸。

若是要進一步提高DolphinDB併發的多任務查詢性能,最直接的辦法就是升級網絡,譬如將萬兆以太網升級成10萬兆以太網。另外一個方法是在數據節點之間以及數據節點和客戶端之間傳輸數據時引入壓縮技術,經過額外的CPU開銷提高網絡傳輸的效率,推遲瓶頸的到來。

5. 庫內分析的必要性

從前面的測試咱們能夠看到,不管單任務仍是多任務併發,DolphinDB數據庫端的查詢耗時佔整個查詢耗時的20%左右。所以,若是分析計算可以在數據庫內直接完成,或者數據清洗工做在數據庫內完成並下降須要傳輸的數據量,能夠大大下降耗時。例如對數據集1的查詢耗時38秒,而在DolphinDB數據庫端的查詢只需5~6秒。即便這5~6秒也是由於網絡瓶頸形成的,在數據庫集羣的每個節點上取數據的時間小於2秒。若是在每個節點上完成相應的統計分析,只把最後少許的結果合併,總耗時約爲2秒左右。可是使用Python API從DolphinDB數據庫獲取數據,而後再用pandas的單線程來完成數據分析,總耗時約爲50秒左右。因而可知,面對海量的結構化數據,庫內分析能夠大幅提升系統的性能。

爲支持時序數據的庫內分析,DolphinDB內置了一門完整的多範式腳本語言,包括千餘個內置函數,對時間序列、面板數據、矩陣的各類操做如聚合、滑動窗口分析、關聯、Pivoting、機器學習等量化金融經常使用功能都可在DolphinDB數據庫內直接完成。

6. 結論和展望

  • 數據庫在提供數據管理的便捷、安全、可靠等優點的同時,在性能上超越操做系統裸文件業已可行。這主要得益於分佈式技術、壓縮技術、列式存儲技術的應用以及應用層協議的改進,使得磁盤IO可能再也不成爲一個數據庫系統最早遇到的瓶頸。
  • 金融市場高頻數據的時序特性,使得時序數據庫DolphinDB成爲其最新最有前景的解決方案。DolphinDB相比pickle這樣的文件解決方案,不只爲金融市場高頻數據帶來了管理上的便利和性能上的突破,其內置的強大的時間序列數據、面板數據處理能力更爲金融的應用開發帶來了極大的便利。
  • 使用Python API進行數據查詢的整個鏈路中,DolphinDB數據庫查詢的耗時只佔了很小的一部分,大部分時間耗費在最後一千米,即客戶端的網絡傳輸和數據序列化/反序列化。要突破這個瓶頸,有幾個發展思路:(1)採用數據庫內分析技術,數據清洗和基本的數據分析功能選擇在數據庫內完成,使用分佈式數據庫的計算能力縮短數據處理時間,而且大幅下降網絡傳輸的數據量。(2)啓用相似Apache Arrow相似的通用內存數據格式,下降各應用之間序列化/反序列化的開銷。
  • 隨着數據庫技術的發展,尤爲是分佈式技術的推動,萬兆(10G)網絡會比預期更早成爲數據庫系統的瓶頸。在建設企業內網,尤爲在部署高性能數據庫集羣時,能夠開始考慮使用10萬兆(100G)以太網。
  • 數據壓縮能夠提高磁盤IO和網絡IO的效率,不管在大數據的存儲仍是傳輸過程當中都很是重要。
  • 字符串類型對數據系統性能有很是大的負面影響。因爲字符串類型長度不一致,在內存中不能連續存儲(一般每一個元素使用獨立的對象存儲),內存分配和釋放的壓力巨大,內存使用效率不高,CPU處理效率低下。長度不一致,也致使沒法在磁盤存儲中隨機讀取字符串元素。所以,數據系統的設計中儘量避免使用字符串類型。若是字符串類型的重複度較高,建議使用相似DolphinDB中的SYMBOL類型替代。
相關文章
相關標籤/搜索