首頁>技術>

Druid是時間序列資料儲存。我是一名Druid程式碼的維護者,並且是Metamarkets中的一個人,該人操作著最大的已知Druid叢集,在撰寫本文時,它擁有數百個節點,該節點可提供數百TB的資料,複製因子為2。

Metamarkets非常具體地使用Druid,在我們的叢集中,我們只有大約100個數據源(Druid術語,在其他資料庫中通常稱為“表”),並且每秒僅執行少量查詢,但是這些資料來源的寫操作非常密集:它們每小時增長多達數十億位元組的壓縮資料,並且對這些資料來源的查詢涵蓋了成千上萬個段,每個段約100 MB和一百萬行,整個資料量達到TB級,數十億行查詢,應該在幾秒鐘內完成。雖然,實際上只有一部分資料用於計算查詢結果:在應用所有查詢過濾器後,幾十個列中的幾列,每個段平均只有幾萬行(低於一百萬)。但是,這又只是一個典型的查詢,而有些查詢所覆蓋的資料量卻高達一百倍。

我想重點介紹Druid面臨的壓力最大的問題,這會增加查詢失敗率,降低可用性並使我們這個規模的Druid叢集無法快速完成查詢(它承諾“不到一秒鐘”),當負載增加時。

首先,我想回顧一下Druid叢集的架構:

> Druid cluster view, simplified and without “indexing” part

歷史節點從深度儲存區(可能是Amazon S3,HDFS,Google Cloud Storage,Cassandra等)將段(壓縮的資料片段)下載到其本地或網路連線的磁碟(如Amazon EBS)中。所有下載的段都對映到歷史節點的記憶體中。歷史節點上的剩餘記憶體由查詢處理引擎以及可選的本地查詢結果快取使用。歷史節點下載分段時,它將在ZooKeeper中宣佈。代理節點訂閱此類更新,並將有關哪些歷史記錄服務於每個特定段的資訊保留在其記憶體中。

客戶端向隨機選擇的代理傳送查詢。代理確定分段集,覆蓋查詢資料來源的間隔,然後將子查詢傳送到歷史記錄,這些歷史記錄為所需的分段提供服務。經紀人還彙總歷史記錄返回的每個細分結果。

有關更多詳細資訊,請參閱此頁面上的“ Druid群集”部分,以及Druid論文。

查詢執行路徑上沒有容錯能力

儘管Druid是一個大規模的並行查詢系統,單個查詢最終可能會在數百個歷史節點上進行處理,但它完全沒有查詢執行路徑上的任何容錯能力。當代理將子查詢傳送到歷史記錄時,它需要所有這些子查詢成功完成,然後才能將任何內容返回給客戶端。如果任何子查詢失敗,則整個查詢都會失敗。儘管每個細分都由多個歷史記錄提供服務,並且經紀人都知道所有這些歷史記錄,但是如果對主要歷史記錄的子查詢失敗,經紀人就不會嘗試要求其他歷史記錄來計算相同細分市場的部分查詢結果。

因此,當前在Druid中,僅當歷史記錄從ZooKeeper的角度完全下降時,複製才起作用,然後,在通知代理有關此訊息之後,它開始路由分段的子查詢,該查詢曾經由網段提供消失的歷史,到其他歷史。但是,例如,如果歷史節點與ZooKeeper的連線良好,但是由於查詢引擎中的錯誤而無法完成任何真實查詢,則整個Druid叢集都會生病,直到人工干預。如果歷史記錄與經紀人之間存在網路問題,但此歷史記錄與ZooKeeper之間沒有網路問題,則會發生同樣的遺憾情況。

代理→歷史子查詢失敗的常見原因如下:

網路故障當歷史節點被AWS銷燬時,所有進行中的子查詢將失敗+所有查詢,代理將繼續路由到該歷史節點,直到ZooKeeper通知他們該節點已消失。與上面類似,但是由於Linux OOM殺手與上面類似,但是由於JVM歷史程序中的OutOfMemoryError與上述類似,但由於馬拉松變得“瘋狂”,並且沒有充分的理由重新開始歷史程序。我將在以下部落格文章中對此進行討論。歷史的滾動更新。當完成飛行中查詢時,Druid不支援正常關閉和更新歷史節點,但是不接受新查詢。如上所述,如果沒有在經紀人方面進行子查詢重試,它將無法正常工作。錯誤的金絲雀推出與查詢處理引擎中的錯誤。將子查詢散佈到歷史節點

這個問題與前一個問題密切相關。經紀人對歷史的散亂子查詢不做任何事情,因此整個查詢始終比此查詢中使用的最慢的歷史慢。儘管歷史記錄應該具有相似的表現,並且在Druid中有一個複雜的段平衡系統來確保這一點,但它也存在問題,並且我們發現歷史記錄之間的效能差異很大。下一節將對此進行更詳細的討論。

Dremel論文中也指出了這個問題,這是當今Google BigQuery的基礎。本文指出,基於99%的藥片(相當於Druid中的片段)產生結果會使平均查詢延遲降低幾倍。

歷史節點效能的巨大差異

德魯伊需要智慧地選擇哪些歷史節點載入哪些段,以確保歷史記錄之間的負載平均分配。當前的段平衡演算法使用一些相當複雜的公式來計算在某個歷史記錄上載入某個段的“成本”,同時考慮到已在歷史記錄上載入的同一資料來源中段的間隔。然後,它選擇歷史記錄,將其作為載入段的“最便宜的”記錄。(這種基於成本的決策演算法在Druid節點的特殊Coordinator型別上執行,尚未提及。)

儘管對段平衡演算法進行了改進,但在我們的Druid叢集中,最慢的歷史記錄仍然比最快的歷史記錄差幾倍:

Average latency of some query type on a segment, in milliseconds. Each point is one historical node

我們認為之所以會這樣,是因為在查詢這些段的過程中,訪問了段記憶體的隨機部分方面,我們儲存在叢集中的跨不同資料來源的段非常不均勻。如果同一歷史記錄為許多“隨機訪問”的段提供服務,則會導致在此歷史記錄上浪費更多的檔案對映記憶體,並使對節點的所有查詢執行速度變慢。

這個特定問題與Metamarkets如何使用Druid有關,可以透過對Druid的細分格式和查詢處理引擎進行一些改進來解決。但是,可擴充套件性的更具概念性的問題是,在大型叢集中,很難或不可能使用任何“理論”公式來平衡各部分,並避免節點之間的效能差異,因為誰知道潛在的原因。該問題的建議解決方案是根據歷史節點的實際效能統計資訊做出平衡決策。請參閱Druid開發郵件列表中的討論。

甚至更根本的解決方案是使歷史節點不將段儲存在本地磁碟和記憶體中,而是在執行子查詢時始終從深度儲存下載段。e。解耦儲存和計算。這將使從經紀人到歷史記錄的“靜態路由”變得不必要,並使平衡系統過時。除此之外,它還可以使德魯伊在許多其他方面大大簡化。順便說一句,這是BigQuery的體系結構,或者至少是兩年前的樣子。

該解決方案的主要缺點是,使用商品深度儲存(Amazon S3)和網路,它將使用例中的大多數查詢執行數十秒,而不是當前的0到3秒。但是,隨著網路和儲存技術變得越來越快,越來越便宜,我認為儲存和計算的分離是“大資料”系統(包括時間序列資料庫)的未來。瞭解BookKeeper + Pulsar如何將訊息傳遞與儲存解耦,以及Kafka如何將訊息傳遞與儲存耦合在同一節點上。這也是Snowflake的工作方式。谷歌最近也提出了這一原則,他們開始將計算和洗牌分開。

令人著迷的是,在短短十年左右的時間裡,Hadoop的座右銘“將計算轉移到資料”發生了完全逆轉。

超大型查詢的問題

在廣告分析中,時間序列資料來源通常非常“厚”。我們叢集中多個月的歷史資料報告查詢涵蓋了數百萬個細分。此類查詢所需的計算量足以使整個歷史層的處理能力達到數十秒鐘。

如果我們只是讓這樣的查詢與對最近資料的互動式查詢一起執行,則會使互動式查詢的等待時間和使用者體驗變得可怕。當前,我們在歷史層(即i)中使用基於時間的分層將互動式查詢與報告查詢隔離開來。e。資料來源中的最近段被載入到一組歷史節點(我們稱為熱層)中,而較舊的段(則構成報告查詢需要處理的大部分段)被載入到另一組歷史節點中(冷層)。

這種方法有一些缺陷:

它仍然不能提供完全的隔離,因為報告查詢不僅針對舊資料,還包括新舊資料,因此報告查詢仍然會在熱層中嚴重干擾互動式查詢。分層限制了對新資料和舊資料的查詢可用的計算能力,因為它在例項級別而不是程序或執行緒級別隔離CPU資源。由於之前的原因,也因為我們試圖透過給它提供更少的記憶體和CPU(相對於它儲存和有時需要大量突發處理的資料量,以及網路連線的磁碟)來減少其記憶體和CPU的價格,從而使便宜的價格更低如果可以更有效地利用整個叢集的資源,則查詢有時實際上要執行幾十分鐘而不是一分鐘。

> Tiers in Druid historical layer, too rigid resource isolation

這組問題已在Druid社群中得到認可多年。提出的一種解決方案是使Spark執行此類查詢,以使超重查詢執行得更快,而不佔用Druid叢集的資源。但是,這樣做效率極低,因為Spark只能從Druid的深度儲存中下載整個段,有時執行查詢需要數十列,而不是幾列。同樣,Spark查詢處理引擎並未像Druid查詢處理引擎那樣針對時間序列資料和特定查詢型別進行嚴格最佳化。另一方面,如果仍然存在具有大量備用資源的大型Spark叢集,則此解決方案是實用的。

當前Druid架構中更有效的解決方案是隔離執行緒和記憶體資源,以便在歷史節點內執行互動式查詢和報告查詢(以允許兩種型別的查詢機會性地使用節點的所有資源),而不是在歷史節點之間以及將段載入到歷史節點的記憶體中時可以繞過頁面快取。該解決方案的缺點是難以實施。它使Druid變得更加複雜,而不是簡單的工程。

前面文章中提出的將儲存與計算分離,也可以有效地解決大型查詢的問題。首先,由於可用於某些網段處理的計算資源(CPU和記憶體)沒有與該網段所在的節點耦合,因此,只要整個計算資源足夠,就不會出現“查詢推斷”的問題。歷史層(順便說一下,由於歷史節點幾乎是短暫的,因此可以非常迅速地進行縮放)。為了更快地處理某些特別大的查詢,可以及時設定新的歷史節點。

Broker需要將整個叢集的檢視儲存在記憶體中

我們的Druid叢集中儲存著大約一千萬個段。當前實現時,所有代理節點將有關叢集中所有段的元資料保留在其JVM堆記憶體中,而這已經需要10 GB以上的記憶體,這是導致JVM GC大量中斷的原因。

中間解決方案是從公告中跳過細分受眾群元資料的某些部分,但是在10倍規模上,此問題將再次出現。應該可以在代理之間劃分段,以便每個代理僅在整個叢集檢視的子集上執行。

Marathon問題

這與Druid本身無關,而與我們如何大規模部署它有關。Marathon是Mesos之上的業務流程解決方案。我們對此有負面經驗。典型的問題是:

Marathon做的事情是徹頭徹尾的錯誤,或者某些失敗後無法恢復,例如參見ticket4729、7429和7828。有時,Marathon突然變得非常緩慢(有幾分鐘的延遲啟動新的應用程式例項),或者只是卡住而無所事事,或者變得“瘋狂”並無緣無故地連續重啟例項,或者UI顯示錯誤資訊,例如。與部署的實際狀態不一致。通常,手動重新啟動Marathon服務可以解決這些問題。Marathon缺乏一些大規模應用所需的功能,例如它沒有任何縮減規模的策略,也不允許在金絲雀和生產部署之間轉移資源。當使用數十個或數百個例項的應用程式時,網路使用者介面中存在許多小的不便之處,例如G。參見ticket MARATHON_UI-87,154,155,156。Marathon不提供基本的應用程式統計資訊,例如應用程式大小的歷史資料(按例項狀態細分),有關例項平均例項生存期的統計資訊,例項重新啟動的次數。參見ticket7922。
7
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Java,NIO,MappedByteBuffer,記憶體對映