在論證了大規模運行Druid的挑戰之後,我想提出我對下一代開源時間序列存儲的看法,這應該不會出現Druid固有的問題。
「開源」是問題陳述的重要組成部分,因為提出的設計實質上是專有Google BigQuery的簡化版本。我主要從Dremel論文和帖子「 BigQuery under the hood」中獲取了有關BigQuery體系結構的信息,還從許多其他來源中獲取了一些信息。
其他目標和自我約束:
- 時間序列存儲可擴展到單個群集中的PB級壓縮數據和100k處理核心。
- 雲優先:利用雲的優勢。
- 從數十兆兆位元組的數據和一千個處理內核開始,具有成本效益。
- 在合理規模的群集中,處理少於5 TB數據的查詢應在3秒以內(p99延遲)運行-涵蓋交互式廣告分析用例。
- 高度一致的查詢延遲:相似的查詢應始終花費相同的時間來完成,而不管集群中並行運行的查詢是什麼。
- 新攝取的數據應立即可查詢。
- 仔細想想:提出的設計有望在3-5年內變得越來越重要,而不是不那麼重要。
非目標:
- 本地部署。
- 小規模的成本效益。
- 隨機更新和刪除舊數據的效率,儘管這些事情應該是可能的。
- 對於任何小的查詢,即使在沒有負載的系統中,p99的等待時間也不到半秒。
- 易於首次部署和軟件更新。
最後的介紹性說明:這篇文章基於在Metamarkets大規模運行Druid的經驗和理論研究,但是所描述的設計尚未在生產中實施和測試。這篇文章中的某些陳述是錯誤的。如果您有任何意見或更正,請在此帖子下發表評論!
設計概述

具有三個解耦子系統的時間序列存儲的設計。淺藍色線表示未壓縮的面向行的數據流;深藍線-壓縮的柱狀數據;紅線-查詢結果。
該系統由三部分組成,各部分之間有嚴格的職責分離:流處理系統,存儲和計算樹。
流處理系統攝取數據(接受「寫入」),對其進行分區,將每個時間間隔內的數據轉換為壓縮的列格式並將其寫入Storage。流處理系統的工作人員還負責計算最新數據的部分查詢結果。
計算樹具有多個級別的節點:最低級別的節點從Storage中下載特定分區和間隔的數據,並為其計算部分結果。如果查詢間隔包括最新數據,則第二層中的節點合併特定分區的所有分區的結果,並接受最低層中的節點和Stream處理系統的工作程序的接受。第三級中的節點合併或合併第二級中節點的每個時間間隔結果,並包含每個時間間隔查詢結果的緩存。這些節點還可能負責群集平衡和較低級別的計算樹的自動縮放。
此設計的關鍵原則:
計算和存儲的分離。這個想法來自BigQuery。在我有關Druid問題的文章中,我解釋了Druid中缺少這種分隔如何使查詢延遲不可預測,因為查詢之間會相互干擾。
使計算樹中的節點(幾乎)是無狀態的,這意味着它們是「一次性」的。它們可能是亞馬遜的EC2或Google的可搶佔實例,它們比普通實例便宜幾倍。同樣,計算樹可以在數分鐘之內放大和縮小,從而有可能e。G。在查詢負載較低時,每晚和周末將其按比例縮小。
數據攝取(在流處理系統中)和存儲分開。這個想法實際上已經在Druid中實現,它具有實時節點。這樣的關注點分離可以使Storage保持非常簡單,不需要分配資源來進行提取,列壓縮,查詢處理等。它只專註於從磁盤讀取位元組塊並將其通過網絡發送到計算中的節點和樹。
流處理系統也可能比支持寫操作的存儲更動態。流處理系統可以根據數據攝取強度的變化而按比例放大或縮小,通常在晚上和周末較低。流處理系統可能具有在存儲中難以實現的功能,例如動態重新分區。
網絡是瓶頸
如果查詢的下載量沒有使Storage的出站網絡帶寬飽和,則網絡對總查詢延遲的貢獻是恆定的,並且與查詢大小無關。如果將雲對象存儲用作存儲(請參閱下面的「雲對象存儲」部分),或者相對於存儲中的歷史數據量,系統中的查詢負載不成比例地較小,則可以授予此權限。
如果這兩個條件都不適用,則可以使用Storage託管一些非時間序列的,下載頻率較低的數據,以便人為地增加Storage群集的大小,從而增加其出站網絡帶寬。
否則,在存儲和計算樹之間的網絡吞吐量可能將成為限制所提出設計中查詢延遲的因素。有幾種方法可以減輕這種情況:
- 與僅生成一個表的典型SQL查詢不同,對該系統的查詢應組成所有子查詢,而這些子查詢是在分析界面的單個屏幕上所需的。Analytics(分析)界面通常包括至少幾個,有時是幾十個表,圖表等,它們是同一時間序列數據的子查詢的結果。
- 在第三級計算樹中慷慨地緩存查詢結果,以減少重做相同計算的負載。
- 投影下推:僅從存儲區下載查詢處理所需的列子集。
- 按維度鍵分區(最常出現在查詢過濾器中)僅下載和處理所需的分區-謂詞下推式。由於許多實際數據維度中的密鑰頻率是Poisson-,Zipf-或其他不均勻分佈的,因此理想情況下,Stream處理系統應支持「部分」分區,請參見下圖。由於這種分區的基數較低,因此可以在各個分區變得太小而無法以列格式和處理進行有效壓縮之前,將數據按多個維度進行分區。
部分分區可實現密鑰分配不均。每個盒子都是一個分區。具有「其他值」的分區可能具有數千個「長尾」值。
- 更一般而言,數據段(分區)的元數據應包括有關所有維度的信息,該維度似乎在此分區中僅填充了一個(或很少)鍵,從而可以從「意外」分區中受益。
- 色譜柱壓縮應強烈支持壓縮率,而不是減壓或處理速度。
- 列數據應從存儲流式傳輸到計算樹中的節點,並且一旦所有必需列的第一個塊到達計算節點,就開始子查詢處理。這樣可以使網絡和CPU的貢獻在總查詢延遲中儘可能地重疊。要從中受益,將列從存儲發送到計算樹的順序應該比僅在存儲中的磁盤上排列列的順序或列名稱按字母順序排列的順序更聰明。列也可以按小塊以交錯順序發送,而不是逐列發送。
- 一旦部分結果準備就緒,就遞增計算最終查詢結果,並將增量結果流式傳輸到客戶端,以使客戶端感知查詢運行得更快。
在本文的後面,我將詳細介紹系統的每個部分。
存儲
在本節中,我想討論一些存儲的可能實現。它們可以作為可互換的選項共存,就像在Druid中一樣。
雲對象存儲
它是Amazon S3,Google雲存儲(GCS),Azure Blob存儲以及其他雲提供商的類似產品。
從概念上講,這正是設計的時間序列存儲中應使用的存儲方式,因為GCS由名為Colossus的系統提供支持,並且它也是BigQuery的存儲層。
雲對象存儲比我將在下面討論的選項便宜得多,所需的管理工作少得多,並且吞吐量幾乎不受限制,因此上面的整個「網絡是瓶頸」一節在很大程度上是不相關的(理論上)。
雲對象存儲API不夠完善,不足以在單個請求中支持多個位元組範圍的下載(用於多列的投影下推),因此每列的每次下載應是一個單獨的請求。我懷疑這不是BigQuery的工作方式,它與Colossus的集成更緊密,可以實現適當的多列投影下推。
在我看來,「雲對象存儲」選項的主要缺點可能是其p99延遲和吞吐量。一些基準測試表明,GCS和S3在100 ms的延遲中具有p99延遲(這是可以接受的),並且吞吐量僅受下載端VM功能的限制,但是如果在並發100個負載的情況下仍然如此,我將感到非常驚訝一個節點的請求,以及整個集群中一百萬個並發請求的規模。請注意,所有雲提供商都沒有針對對象存儲延遲和吞吐量的SLA,對於GCS,公認吞吐量是「相當多的變量」。
(注意:之前,在上面的部分中,我提到了Cloud Object Storage API不支持範圍請求,這是不正確的,儘管它們仍然不支持(截至2019年10月)單個請求中的多個範圍下載,因此並發查詢放大係數不會消失。)
HDFS中Parquet格式的數據分區
此選項的主要優點是與Hadoop生態系統的其餘部分很好地集成-計算樹甚至可以「附加」到某些已經存在的數據倉庫中。大型聯接或多步查詢等不適用於時間序列範式的複雜查詢可以由同一HDFS群集頂部的Spark,Impala,Hive或Presto之類的系統處理。
同樣重要的是,旨在部署設計的時間序列存儲的組織可能已經具有非常大的HDFS集群,該集群具有較大的出站網絡帶寬,並且如果時間序列存儲使用此HDFS集群存儲其數據分區,則它可能會工作圍繞網絡的可擴展性問題。
但是,庫存HDFS通過單個NameNode路由所有讀取請求。100k並發讀取請求(假設只需要一個讀取請求就可以在計算樹中的一個節點上下載數據分區)接近NameNode的絕對可伸縮性限制,因此,如果HDFS集群實際上忙於處理某些內容,則超出該限制與時間序列存儲無關的操作。
此外,當HDFS用作「遠程」分佈式文件系統時,即使對於Parquet格式的文件,它也不支持投影下推,因此整個數據分區應由計算樹中的節點下載。如果時間序列數據中有數百列,並且通常只使用一小部分進行查詢,則效果將不佳。正如雲對象存儲所建議的那樣,使每個數據分區的每一列都成為一個單獨的文件,由於擴大了文件和讀取請求的數量,因此施加了更大的可擴展性限制。NameNode將無法處理一百萬個並發請求,並且HDFS並未針對小於10 MB的文件進行優化,假設最佳數據分區的大小約為一百萬,則數據分區的各個列將具有的大小行。
但是,在某些情況下(例如,存在大量未充分利用的HDFS集群)並且在某些使用情況下,HDFS似乎是最經濟高效的選擇,並且運行良好。
Apache Kudu
Apache Kudu是一種列式數據存儲,旨在在許多情況下替換HDFS + Parquet對。它結合了節省空間的列式存儲以及快速進行單行讀寫的能力。設計的時間序列系統實際上不需要第二部分,因為寫入是由Stream處理系統處理的,而我們希望使Storage更加便宜並且不浪費CPU(例如用於後台壓縮任務),每個Storage節點上的內存和磁盤資源支持單行讀取和寫入。此外,在Kudu中對舊數據進行單行寫入的方式要求在Kudu節點上進行分區解壓縮,而在建議的時間序列存儲設計中,只有壓縮後的數據應在存儲和計算樹之間傳輸。
另一方面,Kudu具有多種功能,這些功能吸引了時間序列系統,而HDFS沒有:
- 類似於RDBMS的語義。Kudu中的數據以表格的形式組織,而不僅僅是一堆文件。
- Kudu中的平板電腦服務器(節點)比HDFS中的服務器更獨立,從而可以在進行讀取時繞過查詢主節點(Kudu等效於NameNode),從而大大提高了讀取可擴展性。
- 投影下推。
- 它是用C ++編寫的,因此尾部延遲應該比用Java編寫並且會出現GC暫停的HDFS更好。
Kudu論文提到,從理論上講,它可能支持可插拔的存儲布局。如果實施的存儲布局放棄了Kudu對提取單行寫入和舊數據寫入的支持,但更適合於時間序列存儲設計,則Kudu可能會成為比HDFS更好的存儲選項。
Cassandra或Scylla
每個數據分區可以存儲在類似Cassandra的系統中的單個條目中。從Cassandra的角度來看,列具有二進制類型,並存儲數據分區的壓縮列。
該選項與Kudu共享許多優點,甚至具有更好的優點:出色的讀取可伸縮性,極低的延遲(尤其是如果使用ScyllaDB),表語義,僅下載所需列的能力(投影下推式)。
另一方面,類似Cassandra的系統並非設計用於多個MB的列值和大約100 MB的總行大小,並且在填充此類數據時可能開始遇到操作問題。而且,它們不支持在單行甚至單行中的單列級別上進行流讀取,但可以在這些系統中相對容易地實現。
Cassandra旨在承受高寫入負載,因此使用類似LSM的存儲結構和大量內存,在時間序列系統中用作存儲時將浪費資源。
與我上面討論的其他選項相比,該選項最快,但成本效益最低。
將計算樹的節點重用為存儲(已在2019中添加)
請參閱此處的想法說明。
https://github.com/apache/druid/issues/8575
流處理系統
如上所述,Druid已經將數據攝取與所謂的索引子系統或實時節點中的存儲區分開了。但是,儘管該索引子系統實現了完整的分佈式流處理系統的功能的子集,但它並未利用其中的任何功能,甚至也沒有利用Mesos或YARN之類的資源管理器,並且一切都在Druid源代碼中完成。Druid的索引子系統的效率要比現代流處理系統低得多,因為對其進行的開發工作少了數十倍。
同樣,時間序列數據通常在Druid之前的其他流處理系統中進行組合或豐富。例如,沃爾瑪(Walmart)通過Storm來做到這一點,而Metamarkets將Samza用於類似目的。從本質上講,這意味着兩個獨立的流處理系統正在數據管道中一個接一個地運行,從而阻止了映射運算符與Druid的提取終端運算符的融合,這是流處理系統中的常見優化。
這就是為什麼我認為在下一代時間序列中,數據提取應充分利用某些現有的流處理系統。
流處理系統與其餘時間序列存儲之間需要緊密集成,例如允許計算樹中的節點查詢流處理系統中的工作程序。這意味着與Storage的情況不同,它可能很難支持多個流處理系統。應該只選擇一個,並將其與時間序列系統集成。
Flink,Storm和Heron都是可能的候選人。很難判斷當前哪個技術更合適,或者說在哪個技術上更合適,因為這些項目可以快速相互複製要素。如果設計的時間序列系統實際上是在某個組織中創建的,則選擇可能取決於該組織中已使用的流處理系統。
閱讀Druid Development郵件列表中的該線程,以獲取有關此主題的更多信息。
計算樹
對於系統的這一部分的外觀,我並不太費勁。上面的「設計概述」部分介紹了一些可能的方法。
這種方法至少存在一個問題:如果需要緩存太多查詢結果,則計算樹的第三(最高)級別的多個節點將無法有效地處理對特定時間序列(表)的查詢。為了始終將相似的子查詢(僅在總體查詢間隔上不同的子查詢)路由到相同的節點並捕獲緩存的結果,應將具有多個子查詢的一個「複合」查詢分解為多個獨立的查詢,進而使用網絡存儲和計算樹之間的效率較低:請參見上面的「網絡是瓶頸」部分,該列表中的第一項。
但是,可以在垂直方向上擴展第三級計算樹中的節點,以使其足夠大,從而能夠處理所有查詢並容納任何單個時間序列(甚至最繁忙的時間序列)的整個緩存。
垂直擴展意味着第三級計算樹中的一個節點應處理大量並發查詢。這就是為什麼我認為如果從頭開始構建計算樹的原因之一,它應該選擇異步服務器體系結構而不是阻塞(Go風格的綠色線程也可以)。其他兩個原因是:
- 第一層計算樹中的節點通過存儲執行大量的網絡I / O。這些節點上的計算取決於來自Storage的數據到達,並具有不可預知的延遲:來自Storage的數據請求通常會得到重新排序的響應。
- 計算樹所有級別的節點都應支持增量查詢結果計算,並可能以很長的間隔返回同一查詢的多個結果。如上文「網絡是瓶頸」一節所述,它使系統更具容錯能力(在我的第一篇文章中討論了運行Druid的挑戰),並使其變得更快。
平台
理想情況下,構建計算樹的編程平台應具有以下特徵:
- 支持運行時代碼生成,以使查詢更快地完成並提高CPU利用率。這篇有關Impala中運行時代碼生成的博客文章對此進行了很好的解釋。
- 出於相同的原因,生成的機器代碼應該是「最佳」的,並在可能的情況下進行矢量化處理。
- 較低的堆/對象內存開銷,因為內存昂貴,因此使計算樹中的節點更便宜。
- 始終較短的垃圾回收暫停(對於具有託管內存的平台),以支持設計的時間序列存儲的「一致查詢延遲」目標。
從純技術角度來看,C ++是贏家,它可以滿足所有這些要求。選擇C ++與性能無關的缺點也是眾所周知的:開發速度,可調試性,使用插件體系結構擴展系統都很困難等。
JVM仍然是一個不錯的選擇,我相信該系統的效率可能比使用C ++內置的系統低不超過20%:
- JVM允許搭載JIT編譯器以達到與運行時代碼生成目標相同的效果。
- 對於時間序列處理,主要在列解壓縮期間以及在數據上運行特定聚合時需要代碼矢量化。兩者都可以在JNI函數中完成。當為數十千位元組的解壓縮數據支付一次時,JNI的開銷相對較小(我們可能希望以這種大小的塊進行處理以適合L2緩存中的所有解壓縮數據)。巴拿馬項目將使此開銷更小。如果將數據存儲在堆外內存中並進行處理,則垃圾回收的JNI含義也很小或根本不存在。
- 可以通過將所有網絡IO,數據存儲,緩衝和處理都放在堆外內存中,從而使堆內存很小,從而僅對每個查詢分配一些堆。
- 使用Shenandoah GC可以縮短垃圾收集的暫停時間。如果核心處理循環中使用的所有數據結構都是非堆分配的,則堆內存的讀取和寫入障礙不會對CPU利用率造成太大影響。
據我所知,儘管Go或Rust目前不支持運行時代碼生成,儘管添加這種支持可能不需要太多的黑客操作:請參閱gojit項目以及有關Rust的StackOverflow問題。對於其他條件,Go的運行時和生成的代碼可能效率較低,但是出於某些非技術性原因,它比Rust更有效。
提議的時間序列系統的缺點
- 該系統感覺不像是一個單一的「數據庫」,它具有三個獨立的子系統,其中活動部件的總數很高,這使其在小規模上效率不高,難以部署和更新。
- 將系統與現有的說SQL的接口有效地集成可能是一個挑戰,因為系統需要對同一張表運行帶有許多獨立子查詢的「複合」查詢。
- 該系統不適用於需要對查詢的響應速度超過一秒的用例。
- 系統的性能高度依賴於部署它的數據中心中的網絡性能。
- 在某些用例中,無法在第三級計算樹中水平縮放節點可能是主要的可伸縮性瓶頸。
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/251229.html