我們是如何為算法交易系統(tǒng)進行數(shù)據(jù)庫選型的?
所有軟件系統(tǒng)中都必須包含一項關(guān)鍵組件,那就是用于存儲、檢索和分析數(shù)據(jù)的數(shù)據(jù)庫。本文中,我們將和大家一起探討適用于算法交易平臺的數(shù)據(jù)庫都會有哪些特性?有哪些可選擇的數(shù)據(jù)庫?
從廣義層面來看,數(shù)據(jù)庫提供了記錄和管理數(shù)據(jù)(OLTP)和分析數(shù)據(jù)(OLAP)的能力。大多數(shù)數(shù)據(jù)庫會擅長其中一種能力,同時在某些指標具備優(yōu)勢,而在另一些方面則有所欠缺。舉例來說,擅長一致和持久事務(wù)的關(guān)系型數(shù)據(jù)庫可能在性能方面表現(xiàn)不是很好,因為它需要鎖定其數(shù)據(jù)結(jié)構(gòu)并刷新所有磁盤寫入。相反,優(yōu)先考慮性能的數(shù)據(jù)庫可能需要使用寬松的一致性模型。
根據(jù)特定功能和特性的優(yōu)先級劃分,不同種類的數(shù)據(jù)庫適用于不同的場景。
1. 算法交易系統(tǒng)的數(shù)據(jù)存儲
如果我們想追求完美,即持久、一致地存儲大量數(shù)據(jù)并快速進行實時分析,這能做到嗎?盡管計算機科學(xué)理論警告我們不要太貪心,但是有一些工程思想值得參考。
主要設(shè)計目標包括:
- 快速將大量事件提取到持久存儲中(每天工作數(shù)小時,約 250K-1M 個事件 / 秒;我們預(yù)期每個事件約為 100-200 字節(jié),并具有 10-30 個字段)
- 實時分析,包括匯總
- 能夠處理大量模式和趨勢歷史數(shù)據(jù)
基于以上的設(shè)計目標,我們可以構(gòu)建幾種解決方案,不過基本上都需要對數(shù)據(jù)存儲進行分層,以提供多種附加能力。
其中的一種解決方案為:
- 將交易系統(tǒng)中的所有事件存儲到一個快速數(shù)據(jù)存儲中,例如僅附加文件日志(可能在高可用性系統(tǒng)的多個節(jié)點上復(fù)制或重新創(chuàng)建)。該文件提供持久存儲,但沒有真正的查詢功能。
- 將該日志文件的內(nèi)容順序提取到一個內(nèi)存數(shù)據(jù)庫 / 緩存中。這個步驟可以很快,甚至是實時的,因為在這一層沒有一致性檢查、復(fù)制或持久性要求。該層應(yīng)提供實時聚合和分析。
- 定期將內(nèi)存數(shù)據(jù)庫的內(nèi)容持久存儲到磁盤(每小時或一天結(jié)束時等)。這里使用可以對磁盤上存儲的大量數(shù)據(jù)進行操作的數(shù)據(jù)庫。本質(zhì)而言,對存儲的這些數(shù)據(jù)進行的操作被視為脫機或批處理模式,并且不期望瞬時響應(yīng)時間。
- (可選)僅將日志文件的相關(guān)部分提取到一個關(guān)系型數(shù)據(jù)庫中,以提交每日 / 每月報告。例如,只有訂單和執(zhí)行會被加載到關(guān)系型數(shù)據(jù)庫中,而市場報價會被跳過。
另外,這個解決方案也是可以簡化的,例如可以將步驟 2、3 和 4 組合起來,使用一個提供多種存儲和分析數(shù)據(jù)模式的工具。
接下來,我們就來一起討論一下細分需求下的數(shù)據(jù)庫工作。
2. 我們對數(shù)據(jù)庫的要求
我們對數(shù)據(jù)庫的要求可分為非技術(shù)需求和技術(shù)需求兩部分。
非技術(shù)需求列表:
成本:作為一家注重成本的初創(chuàng)企業(yè),我們正在尋找免費或相對便宜的產(chǎn)品。鑒于當今有許多 FOSS 方案,我們認為這不是什么瘋狂的要求。這也意味著 Oracle 和 MS SQL Server 等標準付費數(shù)據(jù)庫被排除在外了。
良好的文檔和社區(qū)支持:如果我們不支付許可和支持費用,就需要良好的文檔和另一種解答問題的途徑??尚型緩娇梢允青]件列表、活躍的在線社區(qū),也可能只需 StackOverflow 即可。
運營工具:我們更喜歡相對成熟的產(chǎn)品,自帶用于設(shè)置、管理和監(jiān)視部署(包括可能的多節(jié)點集群)的工具。
技術(shù)需求:
快速提?。何覀冃枰獢?shù)據(jù)庫能夠以每秒 250K 次插入的速度提取,越高越好。如果我們需要批量插入,那是可以接受的;如果我們需要使用多個線程或連接也可以。
快速聚合:我們打算在系統(tǒng)中使用事件源模式。按照這一架構(gòu)模式的規(guī)定,我們會將系統(tǒng)中的所有狀態(tài)更改記錄為離散的不可變事件。為了從這些事件中重新創(chuàng)建系統(tǒng)的最新狀態(tài),我們需要對內(nèi)存中快速聚合的支持,包括窗口函數(shù)、upsert 和其他可能的橫截面聚合。
時間序列操作:支持諸如時間段、移動窗口聚合和 as-of joins 之類的操作。
表達力強的查詢語言:SQL 可以,但對于高級分析來說表達能力還是不夠。理想情況下,數(shù)據(jù)庫將使用帶有矢量化操作的函數(shù)語言支持數(shù)據(jù)訪問和處理。創(chuàng)建用戶定義函數(shù)或服務(wù)端腳本的能力也很有用。
內(nèi)存中的表:用于快速分析工作數(shù)據(jù)集。
磁盤中的表:我們預(yù)期這一類別中的多數(shù)數(shù)據(jù)庫使用面向列的存儲。
數(shù)據(jù)庫應(yīng)支持優(yōu)化的磁盤數(shù)據(jù)布局,這會顯著提高性能。
- 數(shù)據(jù)按日期劃分并分段存儲,以便數(shù)據(jù)管理
- 對于每個分區(qū),數(shù)據(jù)由 Symbol(交易代碼)跨多個節(jié)點分片,以實現(xiàn)并行性和冗余
- 在每個分區(qū)和分片中,數(shù)據(jù)記錄按(Symbol + Exchange,代碼 + 交易所)進行聚類,以方便順序讀取磁盤
- 最后,在每個聚簇鍵的記錄中,按時間戳對數(shù)據(jù)排序,以實現(xiàn)更快的時序操作
- 此外,可以將數(shù)據(jù)壓縮在磁盤上,以減少從磁盤讀取的數(shù)據(jù)總量
- 分層數(shù)據(jù)存儲:數(shù)據(jù)庫還可以支持分層存儲策略,將較舊的數(shù)據(jù)移動到速度較慢的存儲上,從而降低存儲成本。
下面是我們評估的數(shù)據(jù)規(guī)模估算:
- 每日數(shù)據(jù)增長:50–100 GB(未壓縮)〜1B 條記錄
- 歷史數(shù)據(jù)(最終):100 TB(未壓縮)〜1T 條記錄
3. 如何進行測試?
我們所有的測試都是在單個或兩個 AWS 專用實例(m5n-2xlarge)上進行的。這些實例運行 Amazon Linux 2 AMI,包括 8 個 vCPU、32GB RAM 和 100–200GB SSD 卷。
我們知道,對于某些參與測試的數(shù)據(jù)庫來說,這些實例不算很大,尤其是在內(nèi)存指標方面。但我們這樣選擇也有我們的考量,首先,我們認為這些資源足以進行我們想要的測試,其次,我們想了解在資源不足時,這些工具將如何降級或失敗。
在我們的時間限制內(nèi),我們盡了最大的努力來配置各個工具以使其發(fā)揮最佳性能,但是我們可能并沒有一直使用推薦的配置、硬件或節(jié)點數(shù)。我們也嘗試了遵循文檔并以最佳方式設(shè)置數(shù)據(jù)布局(例如分片方案)。
我們執(zhí)行的實際測試包括:
- 加載一天的 NYSE TAQ 數(shù)據(jù)(20180730 的文件)。這會將 3500 萬筆交易加載到一個表中,并將 7.19 億個報價加載到另一個表中。我們不打算將此數(shù)據(jù)庫用于報價數(shù)據(jù)分析,但這肯定會成為一個很好的示例數(shù)據(jù)集。
- 對于每筆交易,在該交易所在的交易所中找到當前的報價。我們希望對單個繁忙的代碼(例如 SPY)的查詢將花費不到一分鐘的時間,對于所有代碼,我們希望查詢在 30 分鐘內(nèi)完成。這是對查詢語言表示復(fù)雜聯(lián)接的能力,以及數(shù)據(jù)庫在合理時間內(nèi)執(zhí)行聯(lián)接能力的測試。
- 對于每個交易代碼,計算交易日每分鐘的交易數(shù)量、平均大小和交易量加權(quán)平均成交價。我們希望在整個交易表上花費的時間不超過 10 秒。
- 在交易日的每一分鐘計算每個交易代碼的 OHLC 條形。
- 計算交易日每個交易品種的時間加權(quán)平均價差。這是一個有趣的測試,其原因有兩個:1)確定報價的持續(xù)時間需要使用諸如 LEAD 或 next 之類的窗口函數(shù);2)必須處理每個報價,因此這是對原始掃描速度的測試。
4. 備選方案
要說明一下,我們在 kdb+ 上擁有豐富的經(jīng)驗,因此,我們對響應(yīng)時間的預(yù)期大部分來自于這部分經(jīng)驗。在原始單核速度方面,我們還沒有發(fā)現(xiàn)比 kdb + 更快的工具。但因為價格、陡峭的學(xué)習曲線和缺乏可操作工作等原因,我們沒有把 kdb+ 列在備選名單中。
平面文件(Flat File)
雖然數(shù)據(jù)庫是最常見的數(shù)據(jù)存儲,但是直接處理平面文件是真正關(guān)鍵的競爭優(yōu)勢,因為它提供了存儲數(shù)據(jù)的最大靈活性。如今,有多種工具可以有效操作存儲在本地磁盤或 S3 存儲桶上的平面文件,例如:Python(帶有 Jupyter 的 Pandas)、Apache Spark、Amazon Redshift Spectrum 甚至 clickhouse-local。
我們在 AWS 上使用 Apache Spark 嘗試了 EMR(Elastic Map Reduce)集群,雖然設(shè)置起來相對容易,但我們?nèi)耘f花了一些時間才弄清楚如何從文件和 JDBC 源加載數(shù)據(jù),以及如何使用 Spark 數(shù)據(jù)集和 PySpark 數(shù)據(jù)幀。我們的結(jié)論是,這可以用于具有適當擴展能力的批處理分析,但不能用作主數(shù)據(jù)庫。不過,我們對 Hadoop 和 Spark 的了解有限,因此對于結(jié)論判斷也會有所影響。
不過,我們?nèi)匀徽J為這是一個精心設(shè)計的系統(tǒng),該系統(tǒng)以正確方式組織文件和目錄,還帶有相應(yīng)的工具和規(guī)劃好的作業(yè),對于能夠分配適當資源的高級用戶而言,這可能是一個可行的選擇。但是對于我們來說,我們認為它可能太脆弱且缺乏組織性,我們還需要其他一些花哨的功能。
MySQL
我們只把 MySQL 視為一個起點,主要是為了確認傳統(tǒng)的 RDBMS 對我們而言并不是真正的正確答案。MySQL 不是時間序列數(shù)據(jù)庫,也不是面向列的,并且不支持我們正在尋找的高級分析特性或性能指標。
它的優(yōu)點是免費,還有龐大的社區(qū)。它的支持者會聲稱,只要你知道方法,它就可以做任何事情。在我們的測試中,MySQL(InnoDB 引擎)無法跟上連接池中 250K/ 秒的快速批量插入,并且隨著表增加到幾百萬條記錄,插入速率也下降了。磁盤上的數(shù)據(jù)大小看起來非常大,查詢幾百萬條記錄時的響應(yīng)時間以秒為單位。即使可以添加索引,具有數(shù)百萬條記錄的聯(lián)接表也無法在可接受的時間內(nèi)完成。
在校對本文的草稿時,一位前同事向我們推薦了 MariaDB 列存儲,由于時間限制,我們無法對其進行全面評估。
PostgreSQL 和 TimescaleDB
在我們的負載測試中,PostgreSQL 比 MySQL 更好,尤其是在插入速率和表大小增加時響應(yīng)時間的退化水平方面,但對于實際需求而言還不夠好。
TimescaleDB 似乎很有競爭力——它是一個 PostgreSQL 擴展,使用大量常規(guī) PostgreSQL 表創(chuàng)建一個稱為超表的虛擬表。在超表上的所有查詢和操作都向下傳遞到適當?shù)膲K表。這里的主要目的是提高插入速率,并在處理大量數(shù)據(jù)時提供可預(yù)測的查詢時間。TimescaleDB 還提供了一些與時間序列相關(guān)的功能,以幫助分析。
宣傳的效果很好,但實際跑起來就不行了。最初的插入速率很不錯(250K / 秒),但我們無法提取 3500 萬筆交易記錄——它莫名其妙地耗盡了內(nèi)存。我們還注意到,文本文件加載器無法利用服務(wù)器上所有可用的內(nèi)核。提取數(shù)據(jù)時,我們發(fā)現(xiàn)服務(wù)器上的 IOWait 時間比其他數(shù)據(jù)庫長得多,這可能是由于缺少磁盤壓縮所致。磁盤空間使用率也很高——存儲的數(shù)據(jù)比完全未壓縮的文本數(shù)據(jù)占用的空間還要多,這是很奇怪的(也許是因為預(yù)分配?)。我們知道最近的版本支持原生壓縮了,但是我們無法將其自動用于新提取的數(shù)據(jù)。
ClickHouse
ClickHouse 基本可以算是一個新玩家,幾乎擁有我們夢寐以求的所有特性:
- 它是 FOSS、速度超快、水平可伸縮、容錯、硬件支持良好,并且具有磁盤(包括分層存儲)上的高級數(shù)據(jù)管理;
- 開發(fā)過程非常透明,在 Github 上有活躍的社區(qū),并且每 2 至 3 周發(fā)布一次更新,其中包含新功能、改進和修復(fù);
- 文檔很好,很容易從維護者那里得到問題的答案。
ClickHouse 主要是一個 OLAP 引擎,沒有真正的事務(wù)支持可言——例如,它不支持插入數(shù)據(jù)的更新和刪除,除非通過笨拙的異步 ALTER TABLE 命令。它還不支持窗口函數(shù)(neighbor 和 runningAccumulate 這類特殊情況除外),這讓人有些驚訝,畢竟它主要針對的是時間序列。
我們在未啟用任何復(fù)制功能的單個節(jié)點上測試了 ClickHouse。ClickHouse 能夠以超過 1M/sec 的速度加載 3500 萬筆交易和 7.19 億筆報價。它使用特殊的磁盤數(shù)據(jù)結(jié)構(gòu)(MergeTree)將數(shù)據(jù)盡快寫入臨時文件,然后在后臺合并,從而達到很高的速度。它永遠不會用完內(nèi)存(只有一個例外),并且使用壓縮過的源文件節(jié)省了將近一半磁盤空間,效率極高。
遺憾的是,我們無法克服一些關(guān)鍵障礙:
- 發(fā)出查詢的唯一方法是使用類似 SQL 的查詢語言,但有一些嚴格的限制:每個請求只能發(fā)出一個選擇語句,并且不支持用戶定義函數(shù)(UDF)或存儲過程(Stored Procedure)。
- 他們的哲學(xué)可以概括為"只能聽我的"。維護人員對一些合理的用戶請求(例如支持日期時間數(shù)據(jù)類型中的亞秒級精度)給出了無法令人滿意的答復(fù)。公平地說,有些回應(yīng)也有正當?shù)睦碛桑强吹竭@些交流仍然有些令人不安。
總而言之,我們還是認為 ClickHouse 具有很大的潛力,將密切關(guān)注其發(fā)展,甚至我們會在系統(tǒng)中的非關(guān)鍵部分部署 ClickHouse。
DolphinDB
DolphinDB 是一種奇特的專用產(chǎn)品,在這次評估之前我們完全沒注意過它。這是一個快速的分布式時間序列分析數(shù)據(jù)庫,是 kdb+ 的可行替代方案。來自 kdb+ 的背景激發(fā)了我們的興趣,即便它是付費產(chǎn)品,也足以讓我們試用一下。
我們對它的總體印象是積極的。它比 ClickHouse 更快,甚至可能比 kdb+ 更快。它擁有對多節(jié)點集群的原生支持、功能豐富的函數(shù)式編程語言以及優(yōu)化的內(nèi)存上以及磁盤上的數(shù)據(jù)結(jié)構(gòu)。它僅用 6 秒鐘就將我們的 3500 萬筆交易載入了一張表!它僅在 358 毫秒內(nèi)就執(zhí)行了所有 SPY 交易及其主要報價之間的 as-of join,在 25 秒鐘內(nèi)對所有代碼執(zhí)行了同樣的聯(lián)接,而在 kdb+ 上一次查詢大約需要 5 分鐘。另外,存儲數(shù)據(jù)的磁盤用量還不到壓縮后的源文件的一半。
它還有一些高級功能(我們未測試)包括:支持流和發(fā)布 / 訂閱、實時聚合 / 窗口引擎、實時異常檢測引擎、高級統(tǒng)計分析函數(shù)和機器學(xué)習函數(shù)
盡管它表現(xiàn)極佳,但仍有一些我們無法克服的負面因素:
- 成本:雖然它看起來比 kdb+ 便宜,但對我們來說仍然太貴了;
- 需要學(xué)習非標準語言(盡管比 kdb+ 容易得多),不過,好在它的文檔完整出色;
- 對于關(guān)鍵業(yè)務(wù)組件,我們真的可以考慮為(對我們而言)未經(jīng)驗證且尚無法判斷其局限性的閉源產(chǎn)品付費嗎?讓人猶豫不決的是,它出現(xiàn)了幾次崩潰和莫名其妙的內(nèi)存不足情況,這都是扣分點。
不過,看來我們可能已經(jīng)發(fā)現(xiàn)了比 kdb+ 更快、功能更豐富的產(chǎn)品,這一點得分很高。我們將密切注意這款產(chǎn)品,如果對具有這些能力的產(chǎn)品(例如 tick 數(shù)據(jù)研究環(huán)境)有強烈的需求,我們一定會考慮它的。
MemSQL
現(xiàn)在要講的是,我們最終的選擇——MemSQL 了。MemSQL 是一種付費產(chǎn)品,但它也為初始集群提供了免費的商業(yè)許可證,其最多可包含 4 個節(jié)點、128 GB 內(nèi)存和無限的磁盤數(shù)據(jù)。我們認為這足以滿足我們在考慮付費產(chǎn)品之前的初始需求了。
MemSQL 將自己定義為名為 HTAP(混合事務(wù) / 分析處理)的新數(shù)據(jù)庫種類。MemSQL 的主要賣點有:它提供快速的分析功能,同時具有豐富的事務(wù)支持并充分兼容 SQL。它甚至可以與 MySQL 兼容,因此你可以使用所有 MySQL 工具和驅(qū)動程序。與龐大的工具生態(tài)系統(tǒng)集成是很棒的,但也存在一些障礙,因為它很難使用純 SQL 表示某些高級分析。由于它以 UDF 和存儲過程提供了對過程語言的全面支持,我們接受了這一特殊缺點 [注意:過程方法比通常的矢量化操作至少慢一個數(shù)量級]。
MemSQL 支持內(nèi)存上行存儲表以及磁盤上列存儲表,帶有分片、排序和壓縮功能(它們最近還發(fā)布了混合單存儲格式。我們僅使用 Columnstore 進行了測試,特別是考慮到我們的測試實例只有 32GB 內(nèi)存。就部署、管理、監(jiān)視、集群設(shè)置甚至數(shù)據(jù)的加載和查詢而言,MemSQL 是最容易使用的工具之一。
我們能夠以超過 50 萬條記錄 / 秒的速度加載交易和報價。我們注意到,服務(wù)器上的加載過程能夠使用多個內(nèi)核并行化提取。加載的數(shù)據(jù)占用的空間與壓縮后的源文件大致相同。我們還觀察到,使用 JDBC 接口時,外部工具能夠以超過 1Gbps 的速度從 MemSQL 讀取數(shù)據(jù),這特別令人印象深刻。
大多數(shù)單表查詢以及多表聯(lián)接查詢的整體性能都很好。它在 as-of joins 中表現(xiàn)不佳,但畢竟它根本不是針對該用例設(shè)計的。我們花了很多時間試圖以最佳方式在 SQL 中表示一個 as-of join,最后我們強迫引擎執(zhí)行(相對)快速的 MergeJoin。可以預(yù)期,廠商將來可以作為自定義操作添加對 as-of-join 的專門支持。
總而言之,MemSQL 是我們在調(diào)查中可以找到的最平衡解決方案。它很成熟、易于使用、免費(暫時)、快速、高效且可與我們想要的所有標準工具互操作。
5. 結(jié)果統(tǒng)計
針對以上測試,我們做了一個詳細的數(shù)據(jù)統(tǒng)計和對比:
如果想要更詳細查看我們的測試結(jié)果,可以查看這里:
https://github.com/prerak-proof/dbtests
6. 總結(jié)
我們知道還有其他許多工具可以評估,尤其是各種 NoSQL 數(shù)據(jù)庫。我們的總體感受是,盡管這些選項也可能處理我們的數(shù)據(jù)規(guī)模,但它們大概無法滿足我們的性能期望。至少到現(xiàn)在,我們認為 MemSQL 是最適合我們的產(chǎn)品,既能滿足我們的需求,也符合我們的約束條件。