在論證了大規(guī)模運行Druid的挑戰(zhàn)之后,我想提出我對下一代開源時間序列存儲的看法,這應該不會出現(xiàn)Druid固有的問題。
“開源”是問題陳述的重要組成部分,因為提出的設計實質上是專有Google BigQuery的簡化版本。我主要從Dremel論文和帖子“ BigQuery under the hood”中獲取了有關BigQuery體系結構的信息,還從許多其他來源中獲取了一些信息。
其他目標和自我約束:
時間序列存儲可擴展到單個群集中的PB級壓縮數(shù)據(jù)和100k處理核心。
云優(yōu)先:利用云的優(yōu)勢。
從數(shù)十兆兆字節(jié)的數(shù)據(jù)和一千個處理內(nèi)核開始,具有成本效益。
在合理規(guī)模的群集中,處理少于5 TB數(shù)據(jù)的查詢應在3秒以內(nèi)(p99延遲)運行-涵蓋交互式廣告分析用例。
高度一致的查詢延遲:相似的查詢應始終花費相同的時間來完成,而不管集群中并行運行的查詢是什么。
新攝取的數(shù)據(jù)應立即可查詢。
仔細想想:提出的設計有望在3-5年內(nèi)變得越來越重要,而不是不那么重要。
非目標:
本地部署。
小規(guī)模的成本效益。
隨機更新和刪除舊數(shù)據(jù)的效率,盡管這些事情應該是可能的。
對于任何小的查詢,即使在沒有負載的系統(tǒng)中,p99的等待時間也不到半秒。
易于首次部署和軟件更新。
最后的介紹性說明:這篇文章基于在Metamarkets大規(guī)模運行Druid的經(jīng)驗和理論研究,但是所描述的設計尚未在生產(chǎn)中實施和測試。這篇文章中的某些陳述是錯誤的。如果您有任何意見或更正,請在此帖子下發(fā)表評論!
設計概述
具有三個解耦子系統(tǒng)的時間序列存儲的設計。淺藍色線表示未壓縮的面向行的數(shù)據(jù)流;深藍線-壓縮的柱狀數(shù)據(jù);紅線-查詢結果。
該系統(tǒng)由三部分組成,各部分之間有嚴格的職責分離:流處理系統(tǒng),存儲和計算樹。
流處理系統(tǒng)攝取數(shù)據(jù)(接受“寫入”),對其進行分區(qū),將每個時間間隔內(nèi)的數(shù)據(jù)轉換為壓縮的列格式并將其寫入Storage。流處理系統(tǒng)的工作人員還負責計算最新數(shù)據(jù)的部分查詢結果。
計算樹具有多個級別的節(jié)點:最低級別的節(jié)點從Storage中下載特定分區(qū)和間隔的數(shù)據(jù),并為其計算部分結果。如果查詢間隔包括最新數(shù)據(jù),則第二層中的節(jié)點合并特定分區(qū)的所有分區(qū)的結果,并接受最低層中的節(jié)點和Stream處理系統(tǒng)的工作程序的接受。第三級中的節(jié)點合并或合并第二級中節(jié)點的每個時間間隔結果,并包含每個時間間隔查詢結果的緩存。這些節(jié)點還可能負責群集平衡和較低級別的計算樹的自動縮放。
此設計的關鍵原則:
計算和存儲的分離。這個想法來自BigQuery。在我有關Druid問題的文章中,我解釋了Druid中缺少這種分隔如何使查詢延遲不可預測,因為查詢之間會相互干擾。
使計算樹中的節(jié)點(幾乎)是無狀態(tài)的,這意味著它們是“一次性”的。它們可能是亞馬遜的EC2或Google的可搶占實例,它們比普通實例便宜幾倍。同樣,計算樹可以在數(shù)分鐘之內(nèi)放大和縮小,從而有可能e。G。在查詢負載較低時,每晚和周末將其按比例縮小。
數(shù)據(jù)攝?。ㄔ诹魈幚硐到y(tǒng)中)和存儲分開。這個想法實際上已經(jīng)在Druid中實現(xiàn),它具有實時節(jié)點。這樣的關注點分離可以使Storage保持非常簡單,不需要分配資源來進行提取,列壓縮,查詢處理等。它只專注于從磁盤讀取字節(jié)塊并將其通過網(wǎng)絡發(fā)送到計算中的節(jié)點和樹。
流處理系統(tǒng)也可能比支持寫操作的存儲更動態(tài)。流處理系統(tǒng)可以根據(jù)數(shù)據(jù)攝取強度的變化而按比例放大或縮小,通常在晚上和周末較低。流處理系統(tǒng)可能具有在存儲中難以實現(xiàn)的功能,例如動態(tài)重新分區(qū)。
網(wǎng)絡是瓶頸
如果查詢的下載量沒有使Storage的出站網(wǎng)絡帶寬飽和,則網(wǎng)絡對總查詢延遲的貢獻是恒定的,并且與查詢大小無關。如果將云對象存儲用作存儲(請參閱下面的“云對象存儲”部分),或者相對于存儲中的歷史數(shù)據(jù)量,系統(tǒng)中的查詢負載不成比例地較小,則可以授予此權限。
如果這兩個條件都不適用,則可以使用Storage托管一些非時間序列的,下載頻率較低的數(shù)據(jù),以便人為地增加Storage群集的大小,從而增加其出站網(wǎng)絡帶寬。
否則,在存儲和計算樹之間的網(wǎng)絡吞吐量可能將成為限制所提出設計中查詢延遲的因素。有幾種方法可以減輕這種情況:
與僅生成一個表的典型SQL查詢不同,對該系統(tǒng)的查詢應組成所有子查詢,而這些子查詢是在分析界面的單個屏幕上所需的。Analytics(分析)界面通常包括至少幾個,有時是幾十個表,圖表等,它們是同一時間序列數(shù)據(jù)的子查詢的結果。
在第三級計算樹中慷慨地緩存查詢結果,以減少重做相同計算的負載。
投影下推:僅從存儲區(qū)下載查詢處理所需的列子集。
按維度鍵分區(qū)(最常出現(xiàn)在查詢過濾器中)僅下載和處理所需的分區(qū)-謂詞下推式。由于許多實際數(shù)據(jù)維度中的密鑰頻率是Poisson-,Zipf-或其他不均勻分布的,因此理想情況下,Stream處理系統(tǒng)應支持“部分”分區(qū),請參見下圖。由于這種分區(qū)的基數(shù)較低,因此可以在各個分區(qū)變得太小而無法以列格式和處理進行有效壓縮之前,將數(shù)據(jù)按多個維度進行分區(qū)。
部分分區(qū)可實現(xiàn)密鑰分配不均。每個盒子都是一個分區(qū)。具有“其他值”的分區(qū)可能具有數(shù)千個“長尾”值。
更一般而言,數(shù)據(jù)段(分區(qū))的元數(shù)據(jù)應包括有關所有維度的信息,該維度似乎在此分區(qū)中僅填充了一個(或很少)鍵,從而可以從“意外”分區(qū)中受益。
色譜柱壓縮應強烈支持壓縮率,而不是減壓或處理速度。
列數(shù)據(jù)應從存儲流式傳輸?shù)接嬎銟渲械墓?jié)點,并且一旦所有必需列的第一個塊到達計算節(jié)點,就開始子查詢處理。這樣可以使網(wǎng)絡和CPU的貢獻在總查詢延遲中盡可能地重疊。要從中受益,將列從存儲發(fā)送到計算樹的順序應該比僅在存儲中的磁盤上排列列的順序或列名稱按字母順序排列的順序更聰明。列也可以按小塊以交錯順序發(fā)送,而不是逐列發(fā)送。
一旦部分結果準備就緒,就遞增計算最終查詢結果,并將增量結果流式傳輸?shù)娇蛻舳?,以使客戶端感知查詢運行得更快。
在本文的后面,我將詳細介紹系統(tǒng)的每個部分。
存儲
在本節(jié)中,我想討論一些存儲的可能實現(xiàn)。它們可以作為可互換的選項共存,就像在Druid中一樣。
云對象存儲
它是Amazon S3,Google云存儲(GCS),Azure Blob存儲以及其他云提供商的類似產(chǎn)品。
從概念上講,這正是設計的時間序列存儲中應使用的存儲方式,因為GCS由名為Colossus的系統(tǒng)提供支持,并且它也是BigQuery的存儲層。
云對象存儲比我將在下面討論的選項便宜得多,所需的管理工作少得多,并且吞吐量幾乎不受限制,因此上面的整個“網(wǎng)絡是瓶頸”一節(jié)在很大程度上是不相關的(理論上)。
云對象存儲API不夠完善,不足以在單個請求中支持多個字節(jié)范圍的下載(用于多列的投影下推),因此每列的每次下載應是一個單獨的請求。我懷疑這不是BigQuery的工作方式,它與Colossus的集成更緊密,可以實現(xiàn)適當?shù)亩嗔型队跋峦啤?/p>
在我看來,“云對象存儲”選項的主要缺點可能是其p99延遲和吞吐量。一些基準測試表明,GCS和S3在100 ms的延遲中具有p99延遲(這是可以接受的),并且吞吐量僅受下載端VM功能的限制,但是如果在并發(fā)100個負載的情況下仍然如此,我將感到非常驚訝一個節(jié)點的請求,以及整個集群中一百萬個并發(fā)請求的規(guī)模。請注意,所有云提供商都沒有針對對象存儲延遲和吞吐量的SLA,對于GCS,公認吞吐量是“相當多的變量”。
(注意:之前,在上面的部分中,我提到了Cloud Object Storage API不支持范圍請求,這是不正確的,盡管它們?nèi)匀徊恢С郑ń刂?019年10月)單個請求中的多個范圍下載,因此并發(fā)查詢放大系數(shù)不會消失。)
HDFS中Parquet格式的數(shù)據(jù)分區(qū)
此選項的主要優(yōu)點是與Hadoop生態(tài)系統(tǒng)的其余部分很好地集成-計算樹甚至可以“附加”到某些已經(jīng)存在的數(shù)據(jù)倉庫中。大型聯(lián)接或多步查詢等不適用于時間序列范式的復雜查詢可以由同一HDFS群集頂部的Spark,Impala,Hive或Presto之類的系統(tǒng)處理。
同樣重要的是,旨在部署設計的時間序列存儲的組織可能已經(jīng)具有非常大的HDFS集群,該集群具有較大的出站網(wǎng)絡帶寬,并且如果時間序列存儲使用此HDFS集群存儲其數(shù)據(jù)分區(qū),則它可能會工作圍繞網(wǎng)絡的可擴展性問題。
但是,庫存HDFS通過單個NameNode路由所有讀取請求。100k并發(fā)讀取請求(假設只需要一個讀取請求就可以在計算樹中的一個節(jié)點上下載數(shù)據(jù)分區(qū))接近NameNode的絕對可伸縮性限制,因此,如果HDFS集群實際上忙于處理某些內(nèi)容,則超出該限制與時間序列存儲無關的操作。
此外,當HDFS用作“遠程”分布式文件系統(tǒng)時,即使對于Parquet格式的文件,它也不支持投影下推,因此整個數(shù)據(jù)分區(qū)應由計算樹中的節(jié)點下載。如果時間序列數(shù)據(jù)中有數(shù)百列,并且通常只使用一小部分進行查詢,則效果將不佳。正如云對象存儲所建議的那樣,使每個數(shù)據(jù)分區(qū)的每一列都成為一個單獨的文件,由于擴大了文件和讀取請求的數(shù)量,因此施加了更大的可擴展性限制。NameNode將無法處理一百萬個并發(fā)請求,并且HDFS并未針對小于10 MB的文件進行優(yōu)化,假設最佳數(shù)據(jù)分區(qū)的大小約為一百萬,則數(shù)據(jù)分區(qū)的各個列將具有的大小行。
但是,在某些情況下(例如,存在大量未充分利用的HDFS集群)并且在某些使用情況下,HDFS似乎是最經(jīng)濟高效的選擇,并且運行良好。
Apache Kudu
Apache Kudu是一種列式數(shù)據(jù)存儲,旨在在許多情況下替換HDFS Parquet對。它結合了節(jié)省空間的列式存儲以及快速進行單行讀寫的能力。設計的時間序列系統(tǒng)實際上不需要第二部分,因為寫入是由Stream處理系統(tǒng)處理的,而我們希望使Storage更加便宜并且不浪費CPU(例如用于后臺壓縮任務),每個Storage節(jié)點上的內(nèi)存和磁盤資源支持單行讀取和寫入。此外,在Kudu中對舊數(shù)據(jù)進行單行寫入的方式要求在Kudu節(jié)點上進行分區(qū)解壓縮,而在建議的時間序列存儲設計中,只有壓縮后的數(shù)據(jù)應在存儲和計算樹之間傳輸。
另一方面,Kudu具有多種功能,這些功能吸引了時間序列系統(tǒng),而HDFS沒有:
類似于RDBMS的語義。Kudu中的數(shù)據(jù)以表格的形式組織,而不僅僅是一堆文件。
Kudu中的平板電腦服務器(節(jié)點)比HDFS中的服務器更獨立,從而可以在進行讀取時繞過查詢主節(jié)點(Kudu等效于NameNode),從而大大提高了讀取可擴展性。
投影下推。
它是用C 編寫的,因此尾部延遲應該比用Java編寫并且會出現(xiàn)GC暫停的HDFS更好。
Kudu論文提到,從理論上講,它可能支持可插拔的存儲布局。如果實施的存儲布局放棄了Kudu對提取單行寫入和舊數(shù)據(jù)寫入的支持,但更適合于時間序列存儲設計,則Kudu可能會成為比HDFS更好的存儲選項。
Cassandra或Scylla
每個數(shù)據(jù)分區(qū)可以存儲在類似Cassandra的系統(tǒng)中的單個條目中。從Cassandra的角度來看,列具有二進制類型,并存儲數(shù)據(jù)分區(qū)的壓縮列。
該選項與Kudu共享許多優(yōu)點,甚至具有更好的優(yōu)點:出色的讀取可伸縮性,極低的延遲(尤其是如果使用ScyllaDB),表語義,僅下載所需列的能力(投影下推式)。
另一方面,類似Cassandra的系統(tǒng)并非設計用于多個MB的列值和大約100 MB的總行大小,并且在填充此類數(shù)據(jù)時可能開始遇到操作問題。而且,它們不支持在單行甚至單行中的單列級別上進行流讀取,但可以在這些系統(tǒng)中相對容易地實現(xiàn)。
Cassandra旨在承受高寫入負載,因此使用類似LSM的存儲結構和大量內(nèi)存,在時間序列系統(tǒng)中用作存儲時將浪費資源。
與我上面討論的其他選項相比,該選項最快,但成本效益最低。
將計算樹的節(jié)點重用為存儲(已在2019中添加)
請參閱此處的想法說明。https://github.com/apache/druid/issues/8575
流處理系統(tǒng)
如上所述,Druid已經(jīng)將數(shù)據(jù)攝取與所謂的索引子系統(tǒng)或實時節(jié)點中的存儲區(qū)分開了。但是,盡管該索引子系統(tǒng)實現(xiàn)了完整的分布式流處理系統(tǒng)的功能的子集,但它并未利用其中的任何功能,甚至也沒有利用Mesos或YARN之類的資源管理器,并且一切都在Druid源代碼中完成。Druid的索引子系統(tǒng)的效率要比現(xiàn)代流處理系統(tǒng)低得多,因為對其進行的開發(fā)工作少了數(shù)十倍。
同樣,時間序列數(shù)據(jù)通常在Druid之前的其他流處理系統(tǒng)中進行組合或豐富。例如,沃爾瑪(Walmart)通過Storm來做到這一點,而Metamarkets將Samza用于類似目的。從本質上講,這意味著兩個獨立的流處理系統(tǒng)正在數(shù)據(jù)管道中一個接一個地運行,從而阻止了映射運算符與Druid的提取終端運算符的融合,這是流處理系統(tǒng)中的常見優(yōu)化。
這就是為什么我認為在下一代時間序列中,數(shù)據(jù)提取應充分利用某些現(xiàn)有的流處理系統(tǒng)。
流處理系統(tǒng)與其余時間序列存儲之間需要緊密集成,例如允許計算樹中的節(jié)點查詢流處理系統(tǒng)中的工作程序。這意味著與Storage的情況不同,它可能很難支持多個流處理系統(tǒng)。應該只選擇一個,并將其與時間序列系統(tǒng)集成。
Flink,Storm和Heron都是可能的候選人。很難判斷當前哪個技術更合適,或者說在哪個技術上更合適,因為這些項目可以快速相互復制要素。如果設計的時間序列系統(tǒng)實際上是在某個組織中創(chuàng)建的,則選擇可能取決于該組織中已使用的流處理系統(tǒng)。
閱讀Druid Development郵件列表中的該線程,以獲取有關此主題的更多信息。
計算樹
對于系統(tǒng)的這一部分的外觀,我并不太費勁。上面的“設計概述”部分介紹了一些可能的方法。
這種方法至少存在一個問題:如果需要緩存太多查詢結果,則計算樹的第三(最高)級別的多個節(jié)點將無法有效地處理對特定時間序列(表)的查詢。為了始終將相似的子查詢(僅在總體查詢間隔上不同的子查詢)路由到相同的節(jié)點并捕獲緩存的結果,應將具有多個子查詢的一個“復合”查詢分解為多個獨立的查詢,進而使用網(wǎng)絡存儲和計算樹之間的效率較低:請參見上面的“網(wǎng)絡是瓶頸”部分,該列表中的第一項。
但是,可以在垂直方向上擴展第三級計算樹中的節(jié)點,以使其足夠大,從而能夠處理所有查詢并容納任何單個時間序列(甚至最繁忙的時間序列)的整個緩存。
垂直擴展意味著第三級計算樹中的一個節(jié)點應處理大量并發(fā)查詢。這就是為什么我認為如果從頭開始構建計算樹的原因之一,它應該選擇異步服務器體系結構而不是阻塞(Go風格的綠色線程也可以)。其他兩個原因是:
第一層計算樹中的節(jié)點通過存儲執(zhí)行大量的網(wǎng)絡I / O。這些節(jié)點上的計算取決于來自Storage的數(shù)據(jù)到達,并具有不可預知的延遲:來自Storage的數(shù)據(jù)請求通常會得到重新排序的響應。
計算樹所有級別的節(jié)點都應支持增量查詢結果計算,并可能以很長的間隔返回同一查詢的多個結果。如上文“網(wǎng)絡是瓶頸”一節(jié)所述,它使系統(tǒng)更具容錯能力(在我的第一篇文章中討論了運行Druid的挑戰(zhàn)),并使其變得更快。
平臺
理想情況下,構建計算樹的編程平臺應具有以下特征:
支持運行時代碼生成,以使查詢更快地完成并提高CPU利用率。這篇有關Impala中運行時代碼生成的博客文章對此進行了很好的解釋。
出于相同的原因,生成的機器代碼應該是“最佳”的,并在可能的情況下進行矢量化處理。
較低的堆/對象內(nèi)存開銷,因為內(nèi)存昂貴,因此使計算樹中的節(jié)點更便宜。
始終較短的垃圾回收暫停(對于具有托管內(nèi)存的平臺),以支持設計的時間序列存儲的“一致查詢延遲”目標。
從純技術角度來看,C 是贏家,它可以滿足所有這些要求。選擇C 與性能無關的缺點也是眾所周知的:開發(fā)速度,可調(diào)試性,使用插件體系結構擴展系統(tǒng)都很困難等。
JVM仍然是一個不錯的選擇,我相信該系統(tǒng)的效率可能比使用C 內(nèi)置的系統(tǒng)低不超過20%:
JVM允許搭載JIT編譯器以達到與運行時代碼生成目標相同的效果。
對于時間序列處理,主要在列解壓縮期間以及在數(shù)據(jù)上運行特定聚合時需要代碼矢量化。兩者都可以在JNI函數(shù)中完成。當為數(shù)十千字節(jié)的解壓縮數(shù)據(jù)支付一次時,JNI的開銷相對較?。ㄎ覀兛赡芟M赃@種大小的塊進行處理以適合L2緩存中的所有解壓縮數(shù)據(jù))。巴拿馬項目將使此開銷更小。如果將數(shù)據(jù)存儲在堆外內(nèi)存中并進行處理,則垃圾回收的JNI含義也很小或根本不存在。
可以通過將所有網(wǎng)絡IO,數(shù)據(jù)存儲,緩沖和處理都放在堆外內(nèi)存中,從而使堆內(nèi)存很小,從而僅對每個查詢分配一些堆。
使用Shenandoah GC可以縮短垃圾收集的暫停時間。如果核心處理循環(huán)中使用的所有數(shù)據(jù)結構都是非堆分配的,則堆內(nèi)存的讀取和寫入障礙不會對CPU利用率造成太大影響。
據(jù)我所知,盡管Go或Rust目前不支持運行時代碼生成,盡管添加這種支持可能不需要太多的黑客操作:請參閱gojit項目以及有關Rust的StackOverflow問題。對于其他條件,Go的運行時和生成的代碼可能效率較低,但是出于某些非技術性原因,它比Rust更有效。
提議的時間序列系統(tǒng)的缺點
該系統(tǒng)感覺不像是一個單一的“數(shù)據(jù)庫”,它具有三個獨立的子系統(tǒng),其中活動部件的總數(shù)很高,這使其在小規(guī)模上效率不高,難以部署和更新。
將系統(tǒng)與現(xiàn)有的說SQL的接口有效地集成可能是一個挑戰(zhàn),因為系統(tǒng)需要對同一張表運行帶有許多獨立子查詢的“復合”查詢。
該系統(tǒng)不適用于需要對查詢的響應速度超過一秒的用例。
系統(tǒng)的性能高度依賴于部署它的數(shù)據(jù)中心中的網(wǎng)絡性能。
在某些用例中,無法在第三級計算樹中水平縮放節(jié)點可能是主要的可伸縮性瓶頸。
(本文由聞數(shù)起舞翻譯自828 Followers的文章《Design of a Cost Efficient Time Series Store for Big Data》,轉載請注明出處,原文鏈接:https://leventov./design-of-a-cost-efficient-time-series-store-for-big-data-88c5dc41af8e)