火山引擎ByteHouse:分析型數據庫如何設計並發控制?

2024年2月6日 16点热度 0人点赞

更多技術交流、求職機會,歡迎關註字節跳動數據平臺微信公眾號,回復【1】進入官方交流群

分析型數據庫設計並發控制的主要原因是為了確保數據的完整性和一致性,同時提高數據庫的吞吐量和響應速度。並發控制可以防止多個事務同時對同一數據進行修改,導致數據不一致的情況發生。通過合理的並發控制策略,分析型數據庫可以在保證數據一致性的前提下,最大限度地提高數據庫的並發處理能力,從而提高整體性能。

此外,並發控制也可以有效減少事務因等待鎖釋放而造成的延遲,確保數據庫能夠快速響應用戶的查詢和更新操作。因此,設計合理的並發控制機制是分析型數據庫中非常重要的一個環節,它能夠確保數據庫系統高效、穩定地運行,為數據分析、查詢等應用提供強有力的支持。

作為火山引擎推出的一款分析型數據庫,ByteHouse 通過並發控制,讓多個用戶或應用程序可以同時訪問和操作數據庫,而不會產生沖突或破壞數據,提高數據庫的利用率和響應速度,為用戶提供更好的數據分析服務。

事務和並發控制

事務概覽

在 ByteHouse 裡,為了保證數據質量,我們提供了事務語義的支持。每條 SQL 語句都會轉換為一個事務去執行,事務提供了原子性、一致性、隔離性和持久性 (ACID) 屬性的保證,旨在在並發讀寫,軟件異常,硬件異常等各種情況下仍然可以保證數據的正確性和完整性。

原子性(Atomicity)保證每一個事物被視為一個單元,事物要麼完全成功要麼徹底失敗。在事務成功之前,寫入的數據不可見,不會出現部分數據可見的情況。事務失敗之後,會把寫入的部分數據自動清理掉,不會導致垃圾數據的殘留。ByteHouse 在各種情況下等會保證原子性,包括掉電,錯誤和宕機等各種異常情況。

一致性(consistency)保證數據庫隻會從一個有效的狀態變成另外一個有效的狀態,任何數據的寫入必須遵循已經定義好的規則。

隔離性(isolation)確保數據庫 SQL 並發執行(例如,同一時刻讀寫同一張表)的正確性,確保數據庫的狀態在並發場景下能等價於某種順序執行的狀態,事務之間互不影響。隔離性是並發控制的目標,可以有多種隔離級別的實現,ByteHouse 為用戶提供的是 read committed(rc)隔離級別的支持。未完成的事務的寫入對於其他事務是不可見的。

持久性(Durability)保證數據的高可用性。一旦事務成功提交,其寫入的數據會被持久化,及時在出現各種系統 failure 的情況下不丟失。ByteHouse 采取的存儲計算分離結構,利用了成熟的高可用分佈式文件系統或者對象存儲(例如 hdfs,S3),保證成功事務所提交數據的高可用。

技術選型

ByteHouse 是一款分析型數據庫(OLAP),跟事務型數據庫(OLTP)在事務上的需求是不同。分析型在事務上針對高吞吐低延遲的場景,相反,事務性數據庫針對的是高 QPS 實時的場景。除了基本的 ACID 屬性需要保證,ByteHouse 在事務實現選型上主要有 3 個特別的需求。首先,ByteHouse 單個事務可能涉及到海量數據(例如,上億行級別),事務對數據吞吐和寫入性能有較高要求,並且需要保證其原子性。其次,分析型數據庫的 workload 中讀的比例高於寫,事務需要保證讀 workload 不會被寫 workload 影響和阻塞。最後,事務需要具備靈活可控的並發控制的功能,ByteHouse 裡除了需要處理用戶側並發的 workload,還需要處理並發的後臺任務。

ByteHouse 事務處理主要是對用戶數據的元數據進行管理,元數據包括用戶的 db,table 和 part(part 是數據文件的元數據,包括了 part 名字,columns,行數,狀態,版本,提交時間等信息)。隨著數據的增長,元數據本身數量級也會線性增長,不能丟失並且需要高可用,所以需要一個分佈式存儲/數據庫的方案。我們選擇了成熟的分佈式 key-value 數據庫的作為 ByteHouse 的元數據的存儲方案,通過抽象元數據讀寫 API,後端適配了字節自研的 ByteKV 和蘋果公司開發的 FoundationDB。

分佈式時鐘

事務在分佈式系統中的執行需要在分佈式不同節點中進行時鐘同步。ByteHouse 采取了簡單實用的 Timestamp Oracle(TSO)方案。其優點首先簡單易懂,采取中心授時,能夠確定唯一時間。然後是性能好,通常一個 tso 節點能支持 1m 的 QPS。缺點是不適合跨數據中心的場景,所有事務從 tso 獲取時間延遲較高。由於 TSO 是中心化授時方案,ByteHouse 為其提供了高可用服務。

TSO 使用混合邏輯時鐘,時鐘由物理部分和邏輯部分組成,64 位表示一個時間。為了避免 TSO 宕機導致的時間戳丟失,需要對時間戳持久化。但是如果每次授時都持久化將會降低性能,所以 TSO 會預申請一個可分配的時間窗口(例如 3s)申請成功之後,TSO 可以在內存中直接分配 3 秒窗口之內的所有時間戳。客戶端請求時間戳,邏輯時鐘部分隨著請求遞增。如果出現邏輯部分溢出情況,會睡眠 50ms 等待物理時鐘被推進。TSO 會每 50ms 檢查時鐘,如果當前 TSO 的物理時鐘已經落後於當前時間,需要更新 TSO 的物理時鐘部分為當前物理時間。如果邏輯時鐘部分過半,也會增加 TSO 的物理時鐘,一旦物理時鐘增長,邏輯時鐘清零。如果當前時間窗口已經用完,需要申請下一個時間窗口。同時更新持久化的窗口上界。

事務處理

  • Atomicity(原子性)

ByteHouse 單個事務在元數據管理上有高吞吐讀寫的需求,由於分佈式 key-value 數據庫(例如 ByteKV,FoundationDB)對單次原子寫入的 value 都有大小限制(例如 10MB),ByteHouse 自己在分佈式 key-value 存儲之後實現了 2 階段,使得單次寫入大小不受限並且更加靈活可控。在第一階段可以分批多次寫入任意數據,並且不可見。第二階段對事務進行提交,提交成功之後所有寫入的數據同時可見。下面以一個 insert sql 為例,描述了 2 階段原子提交的一個詳細流程。

  • 階段 1
  • 1. a: 在 kv 裡寫入事務記錄(txn record),唯一標識當前事務;
  • 1. b: 解析 insert sql 並執行;
  • 1. c: 在遠端文件系統或者對象存儲寫入數據之前,先把要寫入數據的位置信息寫入 undo buffer(供失敗情況下清理使用);
  • 1. d: 把數據寫入到遠端文件系統或者對象存儲;
  • 1. e: 提交數據的元信息 part,寫入到 kv 中;
  • 階段 2
  • 提交事務,並更新事務記錄的提交時間;
  • 異步更新 part 數據的提交時間為事務的提交時間(part 未更新提交時間之前,需要反查事務記錄的提交時間);

事務提交詳細流程圖

  • Consistency(一致性)

ByteHouse 選擇的分佈式 key-value 存儲系統,ByteKV 和 Foundation 已經提供了一致性的支持,直接復用即可。

  • Isolation(隔離性)

ByteHouse 對用戶提供 Read Committed(RC)隔離級別的支持。每個事務初始化的時候會從 TSO 服務獲取一個 timestamp 作為其 id 和開始時間,提交的時候會再從 TSO 服務獲取一個提交時間,在事務提交的時候更新 kv 裡事務記錄的提交時間並異步更新 part 的提交時間。讀事務可以讀取到已經提交成功(對應事務提交即成功)並且提交時間小於讀事務開始時間的 part 元數據信息,從而實現 RC 語意。相比更加嚴格的隔離級別,RC 隔離級別可以最大化讀性能。而更嚴格的隔離級別例如 Serializable Snapshot Isolation(SSI),讀可能會被寫入 block。

  • Durability(持久性)

ByteHouse 元數據持久到 ByteKV 或者 FoundationDB 中,2 個分佈式 key-value 存儲提供了持久化和高可用的保障。

並發控制

ByteHouse 利用多版本和鎖來保證並發讀寫場景下數據的正確性。ByteHouse 除了來自用戶的 workload,內部還有後臺任務(merge/alter 任務和唯一鍵表的去重任務)的並發讀寫需要處理。ByteHouse 選擇了 RC 隔離級別,對於新的寫入(例如 insert),由於不可見,可以無鎖執行。對於已有數據,在並發讀寫時,需要進行並發控制。對於並發讀和寫這種場景,ByteHouse 利用多版本解決了讀和寫沖突,提供了讀寫性能。對於並發寫寫的場景(例如 merge 和唯一鍵表的去重任務),利用了加鎖來保證數據的正確性。

多版本

每個 part 的元數據除去其原有基本信息之外,都有一個對應的版本(version),每次對已有數據進行變更,都會產生一個新的版本,而不是直接在原有數據上進行更新。對於 RC 隔離級別,已經開始的讀事務,仍然繼續讀取舊的版本,新版本對其不可見,這樣讀和寫互相不影響,最大化讀寫性能。

  • 分佈式 KV 鎖

ByteHouse 對於 DDL 提供了全局 KV 排他鎖避免並發的對 table schema 進行變更,分佈式 kv 鎖是全局共享,不同的節點都可以共享。

  • 內存讀寫鎖
  • 支持共享鎖和排他鎖
  • 支持等待
  • 支持不同粒度

ByteHouse 提供了多級細粒度 DML 讀寫鎖的支持,DML 相關的任務可以根據需求在不同粒度持不同類型的鎖。

        Table
       /      \
      bucket   \
      /         \
   partition   partition

復制代碼

垃圾回收

ByteHouse 對於不可見的 part 和版本會定期進行回收,例如 merge 任務生成新的 part 之後,對於舊的 part,當不再被查詢引用之後,就會進行回收,釋放空間,降低成本。

點擊跳轉ByteHouse-火山引擎了解更多