高并發寫入最佳實踐
分布式融合數據庫HTAP支持線性擴展,通過均衡調度可以盡可能將業務的寫負載均勻地分布到不同的計算或存儲節點上,更好地利用上整體系統資源,以滿足業務高并發寫入場景下的數據存儲需求。然而,在某些場景下,仍然可能會出現部分業務寫負載不能被很好地分散,導致某些節點負載過高,引發性能瓶頸或不均衡的情況,無法發揮出分布式架構的優勢,從而影響了整體的性能,這種情況通常稱為寫熱點問題。
下文闡述了分布式融合數據庫HTAP的數據分布原理,高并發寫入場景下熱點產生的原因,以及如何規避或者優化寫熱點問題。
數據分布原理
數據分片
分布式融合數據庫HTAP采用 Key-Value 模型作為數據的存儲模型,數據按范圍分片存儲,每個數據分片負責存儲一段 Key 范圍(從 StartKey 到 EndKey 的左閉右開區間)的數據,每個行存節點會負責多個數據分片。當一個分片持續寫入數據,導致分片數據過大,超過一定閾值,系統會將該數據分片分裂為兩個數據分片。相反的,如果相鄰的兩個數據分片過小,系統則會自動觸發將它們合并成一個數據分片。
數據采用多副本存儲,通過 Multi-Raft 協議實現數據強一致性,確保少數副本發生故障時不影響可用性。每個分片對應一個Raft Group,由 Raft Leader 負責處理數據的讀寫。分布式融合數據庫HTAP的管理節點會動態地將分片 Raft Leader 調度到不同的存儲節點上,實現負載均衡。
SQL到KV映射
分布式融合數據庫HTAP對外兼容 MySQL 協議和 SQL 語法,存儲的數據包括以下兩種:
- 表數據:表中每一行的數據(聚簇索引也是一種特殊的表數據)。
- 索引數據:除聚簇索引以外的其他索引數據。
術語解釋
- TableID:系統為每個表分配的一個 ID,目的是保證同一個表數據放在一起,方便全表掃描、范圍查詢等查找任務。
- RowID:表內標識一行記錄的唯一 ID。當表有單列整型主鍵時,會直接使用該主鍵值來作為 RowID;當表沒有主鍵或表主鍵是非聚簇索引時,系統會自動分配一個隱式 RowID。
- IndexID: 與 TableID 類似,分布式融合數據庫HTAP會為表中每個索引分配了一個索引 ID。
- indexedColumnsValue:由索引列值組成的字符串。
表數據與 KV 映射關系
數據表中的每行數據,都會映射成一個 (Key, Value) 鍵值對,映射規則存在以下兩種情況:
-
表沒有主鍵,或表有單列整型主鍵或其他非聚簇索引主鍵(含聯合主鍵、非整型單列主鍵)。
Key: t{TableID}_r{RowID} Value: [col1, col2, col3, col4, ...]當表有單列整型主鍵時,會直接使用該主鍵值來作為 RowID。
-
聚簇索引主鍵。
Key: t{TableID}_i{IndexID}_indexedColumnsValue Value: [col1, col2, col3, col4, ...]
索引數據與 KV 映射關系
分布式融合數據庫HTAP支持非聚簇索引型主鍵和二級索引(包括唯一索引和非唯一索引)。按照索引是否唯一,有如下兩種情況,存儲規則如下:
-
表的唯一索引或非聚簇索引型主鍵,Key 存儲的是索引信息,而 Value 則存儲上述行數據中的 RowID。用戶可以根據索引查詢到表數據 RowID,并回表去查詢對應的行記錄。
Key: t{TableID}_i{IndexID}_indexedColumnsValue Value: RowID -
表的非唯一索引映射規則如下。由于索引非唯一,一個索引值可能對應多行數據,因此 Key 加上了 RowID 以保證 Key 的唯一。
Key: t{TableID}_i{IndexID}_indexedColumnsValue_{RowID} Value: null
最佳實踐
從上述數據映射規則可知,存在著以下這些寫熱點場景:
- 系統為表分配的隱式 RowID 存在寫熱點問題。
當表沒有主鍵,或表有非聚簇索引作為主鍵(含聯合主鍵、非整型單列主鍵)時,系統會為表數據自動分配 RowID。由于隱式 RowID 是遞增的,在表 insert 的過程中插入的行只會追加到表數據最后一個分片,導致該分片成為熱點。 - 表采用遞增型單列整型主鍵可能存在寫熱點問題。
對于單列整型主鍵,系統直接使用該值作為 RowID。如果主鍵值是遞增的,也會引發跟系統分配的隱式 RowID 一樣的寫熱點問題。單列整型主鍵常見的熱點場景包括自增 ID 作為主鍵、采用基于時間戳生成的 ID(如雪花算法)作為主鍵等。 - 當表其他索引的值出現遞增趨勢時,也存在寫入熱點問題。
業務在插入、修改、刪除數據時,如果生成的聯合主鍵、單列非整型主鍵或其他索引的值出現遞增、頻繁重復或相鄰的情況,也會導致表數據或索引數據的某個數據分片成為寫入熱點。
以下是針對寫熱點問題的最佳實踐,旨在幫助用戶更有效地使用數據庫,避免在設計中引入寫熱點問題,并通過一定的優化措施緩解已出現的寫熱點問題。
設計規避
- 創建數據表時一定要創建主鍵,且優先使用聚簇索引主鍵,這樣可以規避表數據的系統隱式 RowID 熱點問題。
- 對于單列整型主鍵,避免使用自增ID,建議使用隨機值,或在建表時添加 AUTO_RANDOM 屬性指定由系統自動生成隨機值,如下所示:
# 建表時主鍵指定 AUTO_RANDOM 屬性 CREATE TABLE t (a BIGINT PRIMARY KEY AUTO_RANDOM, b varchar(255)); # 插入新記錄 INSERT INTO t (b) VALUES ("test"); # 獲取上次分配的主鍵值 SELECT LAST_INSERT_ID(); - 除了單列整型主鍵,對于其他主鍵或索引,應避免頻繁插入相鄰值或大量重復值。
熱點優化
- 對于系統自動分配隱式 RowID 導致熱點的場景,可以通過設置 SHARD_ROW_ID_BITS 把 RowID 打散寫入多個不同的分片進行優化,緩解表熱點問題,使用方法如下所示:
# SHARD_ROW_ID_BITS = 4 表示 2^4=16 個分片 ALTER TABLE t SHARD_ROW_ID_BITS = 4; - 新建表的大批量寫入熱點,通過 Split Region 預切分來解決。
對于新建的表,開始時整個表只有一個分片,如果發生大批量寫入則會造成熱點。為了解決這種場景中的熱點問題,可以使用 Split Region 方法預先為表切分出多個分片,并打散到各個存儲節點上。Split Region 支持均勻切分和不均勻切分兩種用法:# 均勻切分:BETWEEN lower_value AND upper_value REGIONS region_num 語法是通過指定數據的上、下邊界和分片數量,然后在上、下邊界之間均勻切分出 region_num 個分片。 SPLIT TABLE table_name [INDEX index_name] BETWEEN (lower_value) AND (upper_value) REGIONS region_num # 不均勻切分:BY value_list… 語法將手動指定一系列的點值,然后根據這些指定的點值切分出多個分片,適用于數據不均勻分布的場景。 SPLIT TABLE table_name [INDEX index_name] BY (value_list) [, (value_list)] ...