劉丹琪, 蔡 鵬
(華東師范大學 數(shù)據(jù)科學與工程學院, 上海 200062)
傳統(tǒng)的數(shù)據(jù)倉庫通常面向可預測的數(shù)據(jù)量和重復的查詢操作, 例如企業(yè)內(nèi)部的資源規(guī)劃管理系統(tǒng)、客戶關(guān)系管理系統(tǒng)以及年度業(yè)務(wù)報告分析等. 然而, 隨著數(shù)據(jù)量的持續(xù)增長、數(shù)據(jù)多樣性和復雜性的增加以及業(yè)務(wù)需求的不斷變化, 數(shù)據(jù)倉庫需要處理的工作負載變得不可預測, 并且要求其在實時或近實時的范圍內(nèi)響應(yīng), 以支持公司實時運營和決策. 這種新的工作負載給傳統(tǒng)的查詢引擎和數(shù)據(jù)倉庫帶來了新的挑戰(zhàn). 傳統(tǒng)的數(shù)據(jù)倉庫通常采用Shared-Nothing 架構(gòu), 該架構(gòu)可能會面臨高成本、缺乏靈活性、性能低下和效率低下等問題, 無法滿足新的工作負載需求[1]. Shared-Nothing 架構(gòu)存在兩個問題[2-3], 首先, 數(shù)據(jù)被分片后發(fā)送到各個節(jié)點, 每個節(jié)點僅負責處理本節(jié)點上的數(shù)據(jù), 節(jié)點之間不共享數(shù)據(jù), 計算和存儲緊密耦合在一起, 導致計算節(jié)點所提供的CPU、內(nèi)存、存儲和帶寬等硬件資源與工作負載所需硬件資源之間難以實現(xiàn)完美匹配. 其次, 該架構(gòu)缺乏彈性, 在增加和減少節(jié)點時需要對大量數(shù)據(jù)進行重新劃分, 增加了網(wǎng)絡(luò)帶寬消耗, 導致延遲增加, 系統(tǒng)性能顯著下降.
ClickHouse 是一種基于Shared-Nothing 架構(gòu)的列式數(shù)據(jù)管理系統(tǒng), 憑借其卓越的性能、高可用性和可擴展性, 成為許多企業(yè)的首選系統(tǒng)[4]. ClickHouse 因其優(yōu)異的查詢性能和數(shù)據(jù)壓縮技術(shù), 已經(jīng)在很多大型互聯(lián)網(wǎng)企業(yè)中得到了廣泛應(yīng)用, 如頭條、阿里、騰訊、攜程等. 盡管ClickHouse 在許多方面表現(xiàn)出色, 但是其架構(gòu)也存在一些限制. 首先, 由于計算和存儲緊密耦合, 當工作負載發(fā)生變化, 例如查詢量激增時, ClickHouse 存儲和計算等硬件資源的利用可能會出現(xiàn)不平衡, 從而導致系統(tǒng)出現(xiàn)瓶頸和延遲. 其次, ClickHouse 缺少彈性, 無法獨立擴展計算和存儲資源.
為了解決Shared-Nothing 架構(gòu)中存在的硬件資源利用不充分和缺少彈性的問題, 當前的主流云數(shù)據(jù)倉庫中都采用了存儲與計算分離的架構(gòu)[5-6]. 在存算分離架構(gòu)下, 當前的主流云數(shù)據(jù)倉庫也對元數(shù)據(jù)進行了單獨管理. 作為存儲系統(tǒng)的關(guān)鍵組成部分, 元數(shù)據(jù)包含了關(guān)于數(shù)據(jù)的描述信息, 如類型、格式、位置和訪問權(quán)限等. 存儲系統(tǒng)中存在大量的元數(shù)據(jù)增、刪、改、查操作, 這些操作頻繁進行且對系統(tǒng)性能影響較大. 如果元數(shù)據(jù)訪問性能不佳, 將導致數(shù)據(jù)訪問效率低下、系統(tǒng)負載過高、響應(yīng)時間延長等問題, 從而影響存儲系統(tǒng)的穩(wěn)定性和可靠性[7-8]. 因此, 優(yōu)化元數(shù)據(jù)訪問性能成為實現(xiàn)高效數(shù)據(jù)管理的關(guān)鍵因素之一. 此外, ClickHouse 節(jié)點啟動的過程需要從磁盤上讀取所有元數(shù)據(jù)文件, 并在內(nèi)存中構(gòu)建. 對于大量數(shù)據(jù)而言, 啟動過程可能需要花費幾十分鐘甚至幾個小時. 因此, 必須對遠程共享存儲上的數(shù)據(jù)進行高效管理.
當前主流的云數(shù)倉都將元數(shù)據(jù)存儲于分布式數(shù)據(jù)庫中進行管理, Snowflake[3]將數(shù)據(jù)庫模式、訪問控制信息、加密信息、統(tǒng)計信息等元數(shù)據(jù)信息存儲于FoundationDB[9]上. Amazon Redshift[10-11]基于AWS Glue 的catalog 實現(xiàn)元數(shù)據(jù)管理, 存儲了數(shù)據(jù)庫表、視圖、列的定義信息等元數(shù)據(jù). Google BigQuery[12-13]采用Goods 提供元數(shù)據(jù)自動管理技術(shù). 但是, 這些未對元數(shù)據(jù)內(nèi)容進行詳細分類和細粒度管理, 而大多數(shù)查詢僅需要使用元數(shù)據(jù)的一小部分即可進行加速和優(yōu)化. 因此, 對于元數(shù)據(jù)的內(nèi)容進行詳細分類和定制化管理顯得尤為必要. 在存算分離的環(huán)境下, 對元數(shù)據(jù)進行詳細分類和定制化管理, 不僅能夠有效地管理遠程共享存儲中的數(shù)據(jù), 而且更能夠提高元數(shù)據(jù)利用效率, 加速系統(tǒng)查詢過程. 這樣的優(yōu)化方案有望滿足多種各樣的企業(yè)級數(shù)據(jù)處理場景的需求. 為達到最佳管理效果, 元數(shù)據(jù)應(yīng)當被細分為多個類別, 并根據(jù)其屬性進行分類. 在此基礎(chǔ)上, 可以制定出相應(yīng)的管理策略, 以進一步提高元數(shù)據(jù)的利用效率, 從而為云數(shù)倉的優(yōu)化和發(fā)展提供有力的支持.
本文調(diào)研了ClickHouse 的各類元數(shù)據(jù), 并發(fā)現(xiàn)Part 元數(shù)據(jù)在所有元數(shù)據(jù)中的占比最大. 利用Part 元數(shù)據(jù)可以開發(fā)各類元數(shù)據(jù)工具, 例如: 利用列信息、索引信息等來加速查詢, 根據(jù)訪問頻率標記熱點數(shù)據(jù), 追蹤異常元數(shù)據(jù)以維護數(shù)據(jù)安全等. 通過實驗發(fā)現(xiàn), 在ClickHouse 的節(jié)點啟動的過程中,Part 元數(shù)據(jù)的加載速度非常緩慢, 節(jié)點在此期間無法提供服務(wù), 嚴重影響了系統(tǒng)的可用性. 因此, 本文的研究目標是在存算分離架構(gòu)下對ClickHouse 的Part 元數(shù)據(jù)進行單獨管理.
本文的主要貢獻如下:
(1) 調(diào)研Shared-Nothing 架構(gòu)存在的問題及ClickHouse 中當前存在的各類元數(shù)據(jù), 并通過實驗測試了ClickHouse 節(jié)點啟動時間.
(2) 在存算分離架構(gòu)下, 提出了一套Part 元數(shù)據(jù)管理策略. 首先, 采集ClickHouse 當前的Part 元數(shù)據(jù)文件, 并對各個小的元數(shù)據(jù)文件進行合并. 然后, 設(shè)計相應(yīng)的鍵值組織策略, 并經(jīng)過序列化和反序列化后, 將元數(shù)據(jù)存儲到分布式鍵值數(shù)據(jù)庫中. 此外, 為了保證遠程共享存儲中的數(shù)據(jù)和分布式鍵值數(shù)據(jù)庫中的元數(shù)據(jù)一致, 設(shè)計了一套同步策略.
(3) 采用以上策略, 實現(xiàn)了一個針對Part 元數(shù)據(jù)單獨管理的元數(shù)據(jù)管理系統(tǒng), 并通過實驗驗證了其可以加速ClickHouse 節(jié)點啟動過程, 同時支持存算分離架構(gòu)下的高效節(jié)點動態(tài)擴縮容.
如圖1 所示, Shared-Nothing 架構(gòu)中的每個節(jié)點都擁有自己獨立的CPU、內(nèi)存和磁盤等硬件資源, 計算和存儲資源之間緊密耦合, 不存在共享資源. 因此, 將表進行水平分割, 分配給多個獨立工作的服務(wù)器, 通過增加服務(wù)器就可以增加處理能力和容量. 雖然Shared-Nothing 架構(gòu)具有強擴展能力、多租戶的隔離性和數(shù)據(jù)局部性等優(yōu)勢, 但也存在以下缺點:

圖1 Shared-Nothing 架構(gòu)Fig. 1 Shared-Nothing architecture
(1) 硬件資源利用不充分. 不同的工作負載對于CPU、內(nèi)存和磁盤的需求各不相同. 然而, 在Shared-Nothing 架構(gòu)中, 每臺服務(wù)器所擁有的硬件資源是固定的, 無法根據(jù)工作負載的需求靈活調(diào)整.不同類型的查詢對存儲和計算資源的需求也不同. 為了滿足各種類型的查詢需求, 用戶不愿為每個查詢類型配置獨立的集群. 因此, 為了支持這些查詢, 需要在一個集群中同時滿足計算和存儲資源的要求, 這會導致計算或存儲資源出現(xiàn)過剩的情況. 此外, 中小型業(yè)務(wù)通常集中在一個集群中, 大型查詢會消耗大量的計算或存儲資源, 對該集群中的其他租戶造成影響.
(2) 缺少彈性. 工作負載是動態(tài)變化的, 同時, 客戶對數(shù)據(jù)的訪問存在偏斜性. 然而, 在Shared-Nothing 架構(gòu)中, 數(shù)據(jù)被劃分為多個片區(qū), 并分發(fā)給各個節(jié)點, 無法根據(jù)動態(tài)變化的工作負載和數(shù)據(jù)的偏斜性靈活調(diào)整各個節(jié)點需要處理的負載. 此外, 添加和刪除節(jié)點時需要重新劃分大量數(shù)據(jù), 這不僅增加了網(wǎng)絡(luò)帶寬的消耗, 還會導致性能顯著下降.
Shared-Nothing 架構(gòu)的主要特點在于計算和存儲之間的緊密耦合. 為了應(yīng)對上述問題, 需要將計算和存儲分離開來, 使它們能夠獨立進行擴展.
在ClickHouse 中, 數(shù)據(jù)以數(shù)據(jù)片段 (Part) 的形式進行組織, 每個數(shù)據(jù)片段中都有相應(yīng)的元數(shù)據(jù)文件, Part 元數(shù)據(jù)是ClickHouse 中最主要的元數(shù)據(jù). 本文將ClickHouse 中的元數(shù)據(jù)分為7 個類型: 數(shù)據(jù)庫定義、表定義、虛擬集群、數(shù)據(jù)信息、函數(shù)和字典信息、用戶信息、日志信息. 其中, Part 元數(shù)據(jù)的數(shù)據(jù)量在所有元數(shù)據(jù)中占比最大, 并且遠遠超出其他元數(shù)據(jù)類型.
當Part 元數(shù)據(jù)的數(shù)據(jù)量龐大時, 其加載過程對ClickHouse 的啟動時間產(chǎn)生巨大影響. 在節(jié)點啟動期間, ClickHouse 需要讀取各類元數(shù)據(jù)文件, 并在內(nèi)存中構(gòu)建相應(yīng)信息. 鑒于Part 元數(shù)據(jù)占比很大,讀取所有Part 元數(shù)據(jù)文件并在內(nèi)存中構(gòu)建數(shù)據(jù)片段 (Part) 的過程非常耗時, 在此期間系統(tǒng)無法提供服務(wù), 嚴重影響系統(tǒng)的可用性.
在4.2 節(jié)中, 我們模擬了生產(chǎn)環(huán)境, 在ClickHouse 中插入數(shù)據(jù), 并測試了其啟動時間. 實驗結(jié)果顯示, 啟動時間在小時級別. 這樣小時級別的啟動時間是無法被容忍的. 在存算分離架構(gòu)下, 將Part 元數(shù)據(jù)放置于遠程共享存儲中會導致更高的延遲. 因此, 本研究將重點放在Part 元數(shù)據(jù)的管理和利用上, 通過合并各個小的Part 元數(shù)據(jù)文件并存儲于分布式鍵值數(shù)據(jù)庫中, 利用分布式鍵值數(shù)據(jù)庫的快速讀寫能力, 加速節(jié)點啟動過程中Part 元數(shù)據(jù)的加載過程.
本文提出了一個高效的Part 元數(shù)據(jù)單獨管理架構(gòu), 通過降低存算分離架構(gòu)中從遠程共享存儲中讀取Part 元數(shù)據(jù)的延遲, 能夠更高效地管理數(shù)據(jù). 系統(tǒng)架構(gòu)如圖2 所示, 從底部向上依次為元數(shù)據(jù)存儲層、元數(shù)據(jù)管理層和應(yīng)用層. 元數(shù)據(jù)管理層涵蓋了元數(shù)據(jù)管理系統(tǒng)的各個功能模塊: Part 元數(shù)據(jù)采集模塊、鍵值映射模塊、序列化反序列化模塊和數(shù)據(jù)與元數(shù)據(jù)同步模塊. 這些模塊實現(xiàn)了元數(shù)據(jù)管理以及提供各種數(shù)據(jù)服務(wù)等功能. 數(shù)據(jù)存儲層由分布式鍵值數(shù)據(jù)庫構(gòu)成, 如RocksDB[7]、FoundationDB[8]等, 這些數(shù)據(jù)庫具有優(yōu)異的讀寫性能, 能夠提供高效的元數(shù)據(jù)存儲服務(wù). 應(yīng)用層包含ClickHouse、Spark[14]、Hive[9]等主流云數(shù)據(jù)倉庫, 其查詢效率相比傳統(tǒng)數(shù)據(jù)倉庫提高數(shù)倍, 單個查詢的峰值處理性能高達每秒數(shù)TB, 提供快速數(shù)據(jù)分析處理和高效查詢等能力.

圖2 Part 元數(shù)據(jù)管理架構(gòu)Fig. 2 Part metadata management architecture
本文對Part 元數(shù)據(jù)進行了系統(tǒng)整理, 如圖3 所示. Part 元數(shù)據(jù)來源于數(shù)據(jù)存儲層上的Amazon S3 (amazon simple storage service)[15]、 HDFS (hadoop distributed file system)[16]、 Azure Blob Storage[17]等文件系統(tǒng)存儲的數(shù)據(jù). 在ClickHouse 中, 數(shù)據(jù)以數(shù)據(jù)片段 (Part) 形式進行組織, 每個數(shù)據(jù)片段對應(yīng)磁盤上的一個數(shù)據(jù)目錄. 每次數(shù)據(jù)寫入都會生成一個新的數(shù)據(jù)片段, 其目錄下包含data.bin 數(shù)據(jù)文件以及uuid.txt、columns.txt、checksums.txt 等元數(shù)據(jù)文件. 這些元數(shù)據(jù)文件存儲了數(shù)據(jù)片段的唯一標識信息、列信息、文件校驗信息等內(nèi)容. 利用Part 元數(shù)據(jù)可以開發(fā)各類元數(shù)據(jù)工具[12],對數(shù)據(jù)進行有效管理. 例如, 利用索引文件可以加速查詢過程, 利用文件校驗信息可以對數(shù)據(jù)集進行重復檢測和相似度判斷, 利用TTL (time-to-live) 能夠回收和清理舊版本數(shù)據(jù). 此外, 還可以采用監(jiān)控異常元數(shù)據(jù)、追溯元數(shù)據(jù)的來源信息、標記敏感數(shù)據(jù)等方式維護數(shù)據(jù)安全, 通過計算訪問元數(shù)據(jù)頻率方式查找熱點數(shù)據(jù)等.

圖3 Part 元數(shù)據(jù)Fig. 3 Part metadata
FoundationDB、RocksDB 等分布式鍵值數(shù)據(jù)庫具有可擴展性強、高可用性和高性能等特性, 能夠提供高效的數(shù)據(jù)存儲服務(wù). 為了將Part 元數(shù)據(jù)存儲在分布式鍵值數(shù)據(jù)庫上, 需要采用統(tǒng)一的鍵值存儲模型, 其具有數(shù)據(jù)存儲靈活、數(shù)據(jù)結(jié)構(gòu)簡單等特點.
在節(jié)點首次啟動時, 為了獲取數(shù)據(jù)位置信息, 需要遍歷磁盤中的分區(qū)目錄. 然而, 在存算分離架構(gòu)下, 數(shù)據(jù)存儲于遠程共享存儲上, 遍歷遠程共享存儲上的分區(qū)目錄代價高昂, 進一步增加了磁盤I/O 的開銷. 因此, 本文在分布式鍵值數(shù)據(jù)庫上單獨設(shè)計了鍵值映射模型, 用于存儲數(shù)據(jù)位置信息. 在存算分離架構(gòu)下, 獲取數(shù)據(jù)位置信息的過程不再需要遍歷遠程共享存儲上的分區(qū)目錄, 而是直接從分布式鍵值數(shù)據(jù)庫中獲取相關(guān)信息.
如圖4 所示, 針對Part 元數(shù)據(jù), 為確保所有數(shù)據(jù)片段的鍵具有唯一性, 每個鍵包含節(jié)點ID、分割符、table 的唯一標識和數(shù)據(jù)片段名稱. 分割符′x00′的設(shè)計用于實現(xiàn)范圍查詢操作. 對于范圍查詢操作, 具有相同前綴的鍵在邏輯空間上是連續(xù)的, 因此可以利用get_range(key_prefix+′x00′,key_prefix+′xff′)獲取所有具有相同前綴的鍵值對象. 鍵值包含了3.1 節(jié)中調(diào)研的所有元數(shù)據(jù)信息.針對數(shù)據(jù)位置信息元數(shù)據(jù), 鍵中添加了 p artDisk_prefix 前綴, 用于區(qū)分Part 元數(shù)據(jù)和數(shù)據(jù)位置信息元數(shù)據(jù), 并在鍵值中包含了分區(qū)目錄的位置信息, 以便從遠程共享存儲中查找數(shù)據(jù).

圖4 鍵值組織方式Fig. 4 Key-value organization
由于元數(shù)據(jù)存儲層采用了分布式鍵值數(shù)據(jù)庫, 元數(shù)據(jù)需要被序列化為二進制或字符流的形式, 以便存儲和讀取. 序列化將元數(shù)據(jù)對象轉(zhuǎn)換為可以被存儲的字節(jié)流或字符串, 反序列化則將字節(jié)流或字符串轉(zhuǎn)換為原始的元數(shù)據(jù)對象. 在進行序列化和反序列化時, 需要考慮效率和壓縮率的因素.
本文采用基于Protocol Buffer 的序列化與反序列化方式. 這種方式不僅在效率上具有優(yōu)勢, 而且支持豐富的數(shù)據(jù)類型, 并且能夠提供良好的壓縮率. 圖5 展示了將ClickHouse 中存儲的Part 元數(shù)據(jù)文件序列化為分布式鍵值數(shù)據(jù)庫中的元數(shù)據(jù)對象的過程. 在ClickHouse 中, Part 元數(shù)據(jù)通常包含許多小的元數(shù)據(jù)文件, 但是小文件的存儲存在以下3 個缺點[18]:

圖5 序列化與反序列化Fig. 5 Serialization and deserialization
(1) 小文件的數(shù)據(jù)內(nèi)容較少, 管理效率較低.
(2) 對于小文件, 數(shù)據(jù)塊可能分散存儲在磁盤上的不同位置, 產(chǎn)生大量的磁盤碎片, 這不僅降低了訪問性能, 還浪費了磁盤空間.
(3) ClickHouse 中存在大量必要的小文件合并操作, 但是合并小文件的代價非常昂貴.
因此, 為了更加方便地對小文件進行管理, 本研究首先提取元數(shù)據(jù)文件中的內(nèi)容, 再將各個小的Part 元數(shù)據(jù)文件進行合并. 其次, 根據(jù)Protocol Buffer 所提供的數(shù)據(jù)結(jié)構(gòu)類型, 如double、uint32、uint64 等基本數(shù)據(jù)類型、數(shù)組類型、map 類型等, 為元數(shù)據(jù)文件中的內(nèi)容選擇合適的數(shù)據(jù)結(jié)構(gòu). 最后,將這些定義好的數(shù)據(jù)結(jié)構(gòu)保存在后綴為.proto 的文件中, 并轉(zhuǎn)換成proto 對象, 存儲到分布式鍵值數(shù)據(jù)庫中. 通過合并小文件為大文件, 首先, 提高了元數(shù)據(jù)的檢索和查詢效率, 減少了文件讀寫的I/O 操作延遲. 其次, 將可能連續(xù)訪問的小文件合并存儲, 增加了文件之間的局部性, 將原本小文件的隨機訪問轉(zhuǎn)變?yōu)轫樞蛟L問, 極大地提升了性能.
在存算分離架構(gòu)下, 數(shù)據(jù)放在遠程共享存儲上, 而元數(shù)據(jù)放在分布式鍵值數(shù)據(jù)庫中. 讀寫流程如圖6 所示. 在讀請求中, 首先查詢元數(shù)據(jù), 若能成功查詢到元數(shù)據(jù), 則通過元數(shù)據(jù)位置信息去遠程共享存儲中查詢對應(yīng)的數(shù)據(jù); 若未查到元數(shù)據(jù), 則直接在遠程共享存儲上查詢數(shù)據(jù)并將相應(yīng)的元數(shù)據(jù)更新至鍵值數(shù)據(jù)庫. 然后返回查詢到的數(shù)據(jù). 在寫請求中, 先從鍵值數(shù)據(jù)庫中查詢所需要的元數(shù)據(jù)信息, 在遠程共享存儲中將數(shù)據(jù)修改完成后, 回到鍵值數(shù)據(jù)庫更新相應(yīng)的元數(shù)據(jù)信息.

圖6 讀寫流程Fig. 6 Read and write process
然而, 由于數(shù)據(jù)和元數(shù)據(jù)分別存儲在遠程共享存儲和鍵值數(shù)據(jù)庫上, 一旦涉及寫操作, 就可能出現(xiàn)數(shù)據(jù)版本不一致的情況. 如圖7 所示, 當從客戶端發(fā)來兩個并發(fā)的寫請求時, 首先, 線程1 和線程2的寫請求依次從鍵值數(shù)據(jù)庫中讀取元數(shù)據(jù)信息; 接著, 線程1 的寫請求更新了遠程存儲中的數(shù)據(jù) (步驟三); 然后, 線程2 的寫請求再次更新了遠程存儲中的數(shù)據(jù) (步驟四). 然而, 由于網(wǎng)絡(luò)延遲等原因, 線程1 對鍵值數(shù)據(jù)庫中元數(shù)據(jù)的更新可能會晚于線程2 (步驟六晚于步驟五), 這導致最終寫入遠程共享存儲中的數(shù)據(jù)是線程2 的新值, 而寫入的元數(shù)據(jù)是來自線程1 的舊值, 即元數(shù)據(jù)版本落后于數(shù)據(jù)版本.在這種情況下, 如果客戶端再次發(fā)送讀請求 (步驟七), 將會讀取到舊的元數(shù)據(jù)版本. 此外, 在寫操作中還可能出現(xiàn)一種情況, 即數(shù)據(jù)更新成功但元數(shù)據(jù)更新失敗. 這種情況也會導致元數(shù)據(jù)版本落后于數(shù)據(jù)版本. 另外, 在3.2 節(jié)中, 每次更新涉及兩類元數(shù)據(jù)時, 即Part 元數(shù)據(jù)和數(shù)據(jù)位置信息元數(shù)據(jù), 必須保證對這兩類元數(shù)據(jù)的更新是原子操作, 否則可能導致這兩類元數(shù)據(jù)不一致.

圖7 并發(fā)寫入沖突Fig. 7 Concurrent write conflicts
由于每次讀寫操作都需要先查詢鍵值數(shù)據(jù)庫中的元數(shù)據(jù), 為了確保遠程共享存儲中的數(shù)據(jù)和鍵值數(shù)據(jù)庫中的元數(shù)據(jù)的一致性, 以及Part 元數(shù)據(jù)和數(shù)據(jù)位置信息元數(shù)據(jù)兩類元數(shù)據(jù)的一致性, 本文針對元數(shù)據(jù)設(shè)計了一套同步策略.
在鍵值數(shù)據(jù)庫中維護了兩組鍵值對. 第一組鍵值對為
基于以上同步策略, 圖8 展示了在本文提出的數(shù)據(jù)與元數(shù)據(jù)同步策略下的讀寫流程.

圖8 同步策略下的讀寫流程Fig. 8 Read and write process under synchronization strategy
對于寫請求的具體流程: ① 客戶端給服務(wù)器發(fā)送一條寫請求. ② 在鍵值數(shù)據(jù)庫中查找數(shù)據(jù)位置信息和鎖隊列. 若鎖隊列不為空則等待, 否則獲取寫鎖并更新鎖隊列. ③ 在遠程共享存儲中修改數(shù)據(jù)及版本號. ④ 在鍵值數(shù)據(jù)庫中更新元數(shù)據(jù)及鎖隊列. ⑤ 若第④步操作失敗, 則將該Part 元數(shù)據(jù)的鍵值放入消息隊列中, 并進行異步重試更新, 同時針對異步重試更新設(shè)定了超時時間. 若在超時時間內(nèi)修改失敗, 在后續(xù)的讀請求中發(fā)現(xiàn)數(shù)據(jù)版本更新時, 會進行元數(shù)據(jù)的更新.
對于讀請求的具體流程: ① 客戶端給服務(wù)器發(fā)來一條讀請求. ② 在鍵值數(shù)據(jù)庫中查找數(shù)據(jù)位置信息和鎖隊列, 若鎖隊列不為空則等待. ③ 在遠程共享存儲中查找相應(yīng)版本的數(shù)據(jù). ④ 若第②步中未查找到數(shù)據(jù)位置信息, 說明該數(shù)據(jù)不在鎖隊列中, 并且元數(shù)據(jù)版本號小于當前讀取到的數(shù)據(jù)版本號.此時, 更新該數(shù)據(jù)的元數(shù)據(jù)及位置信息元數(shù)據(jù).
在該策略下, 當存在兩個并發(fā)的寫請求時, 第一個寫請求在鍵值數(shù)據(jù)庫上查詢元數(shù)據(jù)時, 會獲取寫鎖并更新鎖隊列, 從而避免與第二個寫請求的沖突. 同時, 當存在兩個并發(fā)的讀寫請求時, 讀請求在鍵值數(shù)據(jù)庫上查詢元數(shù)據(jù)時發(fā)現(xiàn)鎖隊列不為空也會進行等待, 從而避免讀寫沖突的問題.
實驗部署了ClickHouse 集群和FoundationDB 集群, FoundationDB 集群作為存儲Part 元數(shù)據(jù)的數(shù)據(jù)庫. 實驗在Ubuntu 20.04.4 LTS 操作系統(tǒng)上進行, CPU 型號為AMD EPYC 7K62 48-Core Processor CPU @ 2.53 GHz, 4 核, 4 線程; 16 GB 內(nèi)存. FoundationDB 版本為7.1.27, ClickHouse 版本為22.3.2.2-lts.
本實驗選取了ClickHouse 官方提供的數(shù)據(jù)集OpenSky 作為研究對象. 該數(shù)據(jù)集是在對完整OpenSky 數(shù)據(jù)進行清洗處理后得到的, 記錄了COVID-19 新冠肺炎期間空中交通的演變情況. 數(shù)據(jù)集包含了自2019 年1 月1 日以來超過2 500 個基站接收到的所有航班信息, 總計約6 600 萬條數(shù)據(jù). 為了盡量模擬實際生產(chǎn)環(huán)境, 本實驗利用OpenSky 數(shù)據(jù)集創(chuàng)建了2 000 張MergeTree 表, 每張表包含16 個字段、300 個數(shù)據(jù)片段, 一共60 萬個數(shù)據(jù)片段. 目前ClickHouse 已經(jīng)支持將S3 作為遠程共享存儲系統(tǒng). 本實驗設(shè)置了3 組實驗對象進行比較. 第1 組采用原生的ClickHouse 架構(gòu), 數(shù)據(jù)和元數(shù)據(jù)存儲在本地磁盤上. 第2 組在第1 組的基礎(chǔ)上, 引入S3 作為遠程數(shù)據(jù)存儲. 第3 組在第2 組的基礎(chǔ)上,將Part 元數(shù)據(jù)單獨存儲在FoundationDB 上進行管理.
將OpenSky 數(shù)據(jù)集導入后, 本文對3 組實驗對象中ClickHouse 節(jié)點的啟動時間進行了對比, 得到的實驗結(jié)果如圖9 所示. ClickHouse 在啟動過程中需要讀取Part 元數(shù)據(jù)文件, 并在內(nèi)存中構(gòu)建相應(yīng)信息. 由于Part 元數(shù)據(jù)數(shù)量龐大, 這一過程極其漫長, 因此, 原生ClickHouse 的啟動時間達到了小時級別. 當將數(shù)據(jù)存儲在遠程共享存儲上時, 啟動時間變?yōu)榱嗽瓉淼?.5 倍. 這是因為從遠程共享存儲獲取數(shù)據(jù)會增加網(wǎng)絡(luò)開銷, 并且在啟動過程中需要從遠程共享存儲中讀取眾多Part 元數(shù)據(jù)文件. 然而, 通過將Part 元數(shù)據(jù)單獨存儲在FoundationDB 上, 并利用其快速的讀寫性能, 可以顯著縮短從FoundationDB 中讀取Part 元數(shù)據(jù)并在內(nèi)存中構(gòu)建相應(yīng)信息所需的時間, 大大降低了ClickHouse 節(jié)點的啟動時間, 僅需要不到20 min.

圖9 啟動時間對比Fig. 9 Comparison of startup time
為了評估存算分離架構(gòu)下將Part 元數(shù)據(jù)單獨管理后節(jié)點的擴縮容效率, 本實驗設(shè)計了兩組對象進行比較. 第1 組采用S3 作為遠程數(shù)據(jù)存儲的ClickHouse 架構(gòu), 第2 組在第1 組的基礎(chǔ)上將Part 元數(shù)據(jù)單獨存儲在FoundationDB 上. 實驗步驟如下: 首先, 初始化TPC-H 數(shù)據(jù)集并將其導入兩個對照組集群. 然后, 針對兩組對照組進行擴容耗時實驗和縮容耗時實驗. 擴容實驗將3 節(jié)點集群擴展為4 節(jié)點, 而縮容實驗將4 節(jié)點集群收縮為3 節(jié)點. 實驗結(jié)果的評估指標設(shè)定為擴縮容耗時, 即從接收新增或移除節(jié)點請求開始到成功執(zhí)行擴縮容并達到集群正常服務(wù)性能所需的時間, 結(jié)果如圖10 所示.

圖10 擴縮容效率對比Fig. 10 Comparison of expansion and shrinkage efficiency
對于第1 組對象而言, 當集群中存在3 個ClickHouse 節(jié)點時, 會建立一張3 節(jié)點分布式表. 當集群擴展為4 個節(jié)點時, 創(chuàng)建一個4 節(jié)點的分布式表, 并將數(shù)據(jù)從舊的分布式表復制到新表中. 因此, 擴縮容耗時主要取決于全量數(shù)據(jù)復制的時間. 而對于第2 組對象而言, 集群擴容時只需在元數(shù)據(jù)管理系統(tǒng)上選擇一部分數(shù)據(jù)分配給新的節(jié)點, 無需進行大量數(shù)據(jù)遷移, 擴縮容效率將大幅提升. 在數(shù)據(jù)量從20 GB 增長到60 GB 的范圍內(nèi), 原生ClickHouse 架構(gòu)的擴縮方案耗時呈現(xiàn)線性增長, 而采用FoundationDB 對元數(shù)據(jù)進行單獨管理后, 擴縮容時間整體維持在4 s 以內(nèi).
本文調(diào)研了傳統(tǒng)的數(shù)據(jù)倉庫中主流的Shared-Nothing 架構(gòu)存在的問題, 并在存算分離架構(gòu)下提出了一套Part 元數(shù)據(jù)管理策略, 以及利用該策略實現(xiàn)了一個針對Part 元數(shù)據(jù)單獨管理的元數(shù)據(jù)管理系統(tǒng). 鑒于Shared-Nothing 架構(gòu)計算和存儲緊密耦合的特性, 本文提出了將存儲和計算分離的需求.具體而言, 首先對Shared-Nothing 架構(gòu)中存在的問題和ClickHouse 當前的各類元數(shù)據(jù)進行了調(diào)研, 并通過實驗測試了ClickHouse 節(jié)點的啟動時間. 接著, 在存算分離架構(gòu)下提出了一套Part 元數(shù)據(jù)管理策略. 該策略首先采集ClickHouse 當前的Part 元數(shù)據(jù)文件, 合并各個小的元數(shù)據(jù)文件, 并設(shè)計相應(yīng)的鍵值組織策略. 在序列化和反序列化后, 將元數(shù)據(jù)存儲在分布式鍵值數(shù)據(jù)庫中. 此外, 為了確保遠程共享存儲中的數(shù)據(jù)和分布式鍵值數(shù)據(jù)庫中的元數(shù)據(jù)的一致性, 設(shè)計了一套同步策略. 通過采用以上策略,成功實現(xiàn)了一個針對Part 元數(shù)據(jù)單獨管理的元數(shù)據(jù)管理系統(tǒng), 并通過實驗驗證了該系統(tǒng)能夠加速ClickHouse 節(jié)點的啟動過程, 并支持存算分離架構(gòu)下的高效的節(jié)點動態(tài)擴縮容.