為了協助您深入理解ApsaraDB for SelectDB的分區分桶的概念和如何實施分區分桶,本文檔詳細闡釋了分區分桶的原理和操作樣本,以協助您理解分區分桶原理和如何實施分區分桶。
概述
為了能高效處理巨量資料量的儲存和計算,ApsaraDB for SelectDB按分治思想對資料進行分割處理,將資料分散到分布式系統中進行處理。
SelectDB中所有的表引擎都支援如下兩種的資料劃分。
一層:僅使用一層分區時。
建表時不寫分區語句即可,此時SelectDB會產生一個預設分區,預設分區是透明的。使用一層分區時,只支援Bucket劃分。
二層:使用兩層分區時。
第一層是分區(Partition),支援Range和List的劃分方式。
第二層是分桶(Bucket,即Tablet),僅支援Hash的劃分方式。
分區
分區用於將資料劃分成不同區間,可以理解為將原始表劃分成了多個子表,這樣可以對資料進行分區管理。分區具有如下特性。
分區(Partition)列可以指定一列或多列,分區列必須為Key列。
不論分區列是什麼類型,在寫分區值時,都需要加雙引號。
分區數量理論上沒有上限。
當不使用分區(Partition)建表時,系統會自動產生一個和表名同名的,全值範圍的分區(Partition)。該分區(Partition)對使用者不可見,並且不可刪改。
建立分區時不可添加範圍重疊的分區。
Range分區
Range分區的分區列通常為時間列,以方便管理新資料和舊資料。Range分區(Partition)支援通過VALUES LESS THAN (...)僅指定上界,系統會將前一個分區的上界作為該分區的下界,產生一個左閉右開的區間。同時,也可通過VALUES [...)指定上下界,產生一個左閉右開的區間。
單列分區
當使用VALUES LESS THAN (...)語句進行分區的增刪操作時,分區範圍的變化情況。樣本如下。
建立樣本表
test_table。CREATE TABLE IF NOT EXISTS test_db.test_table ( `user_id` LARGEINT NOT NULL COMMENT "使用者id", `date` DATE NOT NULL COMMENT "資料灌入日期時間", `timestamp` DATETIME NOT NULL COMMENT "資料灌入的時間戳記", `city` VARCHAR(20) COMMENT "使用者所在城市", `age` SMALLINT COMMENT "使用者年齡", `sex` TINYINT COMMENT "使用者性別", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "使用者最後一次訪問時間", `cost` BIGINT SUM DEFAULT "0" COMMENT "使用者總消費", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "使用者最大停留時間", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "使用者最小停留時間" )ENGINE=OLAP AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) PARTITION BY RANGE(`date`) ( PARTITION `p201701` VALUES LESS THAN ("2017-02-01"), PARTITION `p201702` VALUES LESS THAN ("2017-03-01"), PARTITION `p201703` VALUES LESS THAN ("2017-04-01") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;建立樣本表
test_table完成後,會自動產生如下3個分區。p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201703: [2017-03-01, 2017-04-01)通過
ALTER TABLE test_db.test_table ADD PARTITION p201705 VALUES LESS THAN ("2017-06-01");命令增加一個分區p201705,則分區結果如下。p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201703: [2017-03-01, 2017-04-01) p201705: [2017-04-01, 2017-06-01)通過
ALTER TABLE test_db.test_table DROP PARTITION p201703;命令刪除分區p201703,則分區結果如下。p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01)重要上述樣本中,刪除分區
p201703後,p201702和p201705的分區範圍沒有發生變化,而這兩個分區之間,出現了一個空缺:[2017-03-01,2017-04-01)。即如果匯入的資料範圍在這個空缺範圍內,是無法匯入的且已經存在在空缺範圍內的資料也會被刪除。繼續刪除分區
p201702,則分區結果如下。p201701: [MIN_VALUE, 2017-02-01) p201705: [2017-04-01, 2017-06-01)空缺範圍變為:[2017-02-01,2017-04-01)。
繼續增加一個分區
`p201702new` VALUES LESS THAN ("2017-03-01"),則分區結果如下。p201701: [MIN_VALUE, 2017-02-01) p201702new: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01)空缺範圍變為:[2017-03-01,2017-04-01)。
繼續刪除分區p201701,並添加分區
`p201612` VALUES LESS THAN ("2017-01-01"),則分區結果如下。p201612: [MIN_VALUE, 2017-01-01) p201702new: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01)空缺範圍變為:[2017-01-01,2017-02-01)和[2017-03-01,2017-04-01)。
通過上述樣本表明,分區的刪除不會改變已存在分區的範圍。刪除分區可能出現空缺。通過VALUES LESS THAN語句增加分區時,分區的下界需要緊接上一個分區的上界。
多列分區
在建立表分區時,添加如下多列分區設定。樣本如下。
PARTITION BY RANGE(`date`, `id`)
(
PARTITION `p201701_1000` VALUES LESS THAN ("2017-02-01", "1000"),
PARTITION `p201702_2000` VALUES LESS THAN ("2017-03-01", "2000"),
PARTITION `p201703_all` VALUES LESS THAN ("2017-04-01")
)指定date(DATE類型)和id(INT類型)作為分區列。則分區結果如下。
* p201701_1000: [(MIN_VALUE, MIN_VALUE), ("2017-02-01", "1000") )
* p201702_2000: [("2017-02-01", "1000"), ("2017-03-01", "2000") )
* p201703_all: [("2017-03-01", "2000"), ("2017-04-01", MIN_VALUE)) 最後一個分區只指定了date列的分區值,所以id列的分區值會預設填充MIN_VALUE。當使用者插入資料時,分區列值會按照順序依次比較,最終得到對應的分區,樣本如下。
* 資料 --> 分區
* 2017-01-01, 200 --> p201701_1000
* 2017-01-01, 2000 --> p201701_1000
* 2017-02-01, 100 --> p201701_1000
* 2017-02-01, 2000 --> p201702_2000
* 2017-02-15, 5000 --> p201702_2000
* 2017-03-01, 2000 --> p201703_all
* 2017-03-10, 1 --> p201703_all
* 2017-04-01, 1000 --> 無法匯入
* 2017-05-01, 1000 --> 無法匯入List分區
List分區的分區列支援BOOLEAN, TINYINT, SMALLINT, INT, BIGINT, LARGEINT, DATE, DATETIME, CHAR, VARCHAR的資料類型,分區值為枚舉值。當資料為目標資料分割枚舉值之一時,才可以命中分區。
分區(Partition)支援通過來指定每個分區包含的枚舉值。
單列分區
當使用VALUES IN (...)語句進行分區的增刪操作時,分區的變化,樣本如下。
建立樣本表
test_table1。CREATE TABLE IF NOT EXISTS test_db.example_list_tbl1 ( `user_id` LARGEINT NOT NULL COMMENT "使用者id", `date` DATE NOT NULL COMMENT "資料灌入日期時間", `timestamp` DATETIME NOT NULL COMMENT "資料灌入的時間戳記", `city` VARCHAR(20) NOT NULL COMMENT "使用者所在城市", `age` SMALLINT COMMENT "使用者年齡", `sex` TINYINT COMMENT "使用者性別", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "使用者最後一次訪問時間", `cost` BIGINT SUM DEFAULT "0" COMMENT "使用者總消費", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "使用者最大停留時間", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "使用者最小停留時間" ) ENGINE=olap AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) PARTITION BY LIST(`city`) ( PARTITION `p_cn` VALUES IN ("Beijing", "Shanghai", "Hong Kong"), PARTITION `p_usa` VALUES IN ("New York", "San Francisco"), PARTITION `p_jp` VALUES IN ("Tokyo") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;建立樣本表
test_table1完成後,會自動產生如下3個分區。p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_jp: ("Tokyo")增加一個分區
`p_uk` VALUES IN ("London"),則分區結果如下。p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_jp: ("Tokyo") p_uk: ("London")刪除分區
p_jp,則分區結果如下。p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_uk: ("London")
多列分區
在建立表分區時,添加如下多列分區設定。樣本如下。
PARTITION BY LIST(`id`, `city`)
(
PARTITION `p1_city` VALUES IN (("1", "Beijing"), ("1", "Shanghai")),
PARTITION `p2_city` VALUES IN (("2", "Beijing"), ("2", "Shanghai")),
PARTITION `p3_city` VALUES IN (("3", "Beijing"), ("3", "Shanghai"))
)我們指定id(INT類型)和city(VARCHAR類型)作為分區列。則分區結果如下。
* p1_city: [("1", "Beijing"), ("1", "Shanghai")]
* p2_city: [("2", "Beijing"), ("2", "Shanghai")]
* p3_city: [("3", "Beijing"), ("3", "Shanghai")]當插入資料時,分區列值會按照順序依次比較,最終得到對應的分區。樣本如下。
* 資料 ---> 分區
* 1, Beijing ---> p1_city
* 1, Shanghai ---> p1_city
* 2, Shanghai ---> p2_city
* 3, Beijing ---> p3_city
* 1, Tianjin ---> 無法匯入
* 4, Beijing ---> 無法匯入分桶
根據分桶列的Hash值,資料將被劃分進不同的桶(Bucket)進行儲存。
如果使用了分區(Partition),則
DISTRIBUTED...語句描述的是資料在各個分區內的劃分規則,如果不使用分區(Partition),則描述的是對整個表的資料的劃分規則。分桶列可以是多列,Aggregate和Unique模型分桶必須為Key列,Duplicate模型可以是Key列和Value列。分桶列可以和Partition列相同或不同。
分桶列的選擇,是在查詢吞吐和查詢並發之間的一種權衡:
如果選擇多個分桶列,則資料分布更均勻。如果一個查詢條件不包含所有分桶列的等值條件,那麼該查詢會觸發所有分桶同時掃描,這樣查詢的吞吐會增加,單個查詢的延遲隨之降低。這個方式適合大吞吐低並發的查詢情境。
如果選擇一個或少數分桶列,則對應的點查詢可以僅觸發一個分桶掃描。此時,當多個點查詢並發時,這些查詢有較大的機率分別觸發不同的分桶掃描,各個查詢之間的IO影響較小(尤其當不同桶分布在不同磁碟上時),所以這種方式適合高並發的點查詢情境。
分桶的數量理論上沒有上限。
最佳實務
分區(Partition)和桶(Bucket)的配置建議
一個表的Tablet總數量等於(Partition num * Bucket num)。
一個表一個分區的Tablet數量,在不考慮擴容的情況下,推薦略多於整個叢集的磁碟數量。
單個Tablet的資料量理論上沒有上下界,但建議在1~10 GB的範圍內。如果單個Tablet資料量過小,則資料的彙總效果不佳,且中繼資料管理壓力大。如果資料量過大,則不利於副本的遷移、補齊,會增加Schema變更或者ROLLUP操作失敗重試的代價(這些操作失敗重試的粒度是Tablet)。
當Tablet的資料量原則和數量原則衝突時,建議優先考慮資料量原則。
在建表時,每個分區的Bucket數量統一指定。但是在動態增加分區時(
ADD PARTITION),可以單獨指定新分區的Bucket數量。可以利用這個功能方便的應對資料縮小或膨脹。一個分區(Partition)的桶(Bucket)數量一旦指定,不可更改。所以在確定桶數量時,需要預先考慮叢集擴容的情況。例如當前只有3台HOST,每台HOST有1塊盤。如果桶的數量只設定為3或更小,那麼後期即使再增加機器,也不能提高並發度。
例如:有10台BE,每台BE一塊磁碟的情況下。
表總大小 | 500MB | 5GB | 50GB | 500GB | 5TB |
分區數 | 可不分區 | 可不分區 | 可不分區 | 分區大小在50GB | 分區大小在50GB |
分區數 | 4-8個 | 8-16個 | 32個 | 每個分區16-32個分區 | 每個分區16-32個分區 |
表的資料量可以通過SHOW DATA;命令查看。
Random Distribution的配置及使用
對於不需要彙總更新的明細類資料,可以採用Duplicate資料模型並採用Random Distribution方式,樣本如下。
CREATE TABLE IF NOT EXISTS test.example_tbl
(
`timestamp` DATETIME NOT NULL COMMENT "日誌時間",
`type` INT NOT NULL COMMENT "日誌類型",
`error_code` INT COMMENT "錯誤碼",
`error_msg` VARCHAR(1024) COMMENT "錯誤詳細資料",
`op_id` BIGINT COMMENT "負責人id",
`op_time` DATETIME COMMENT "處理時間"
)
DUPLICATE KEY(`timestamp`, `type`, `error_code`)
DISTRIBUTED BY RANDOM BUCKETS 16;如果Duplicate表沒有更新類型的欄位,將表的資料分桶模式設定為RANDOM,則可以避免嚴重的資料扭曲(資料在匯入表對應的分區時,單次匯入作業的資料將隨機播放一個Tablet進行寫入)。
當表的分桶模式被設定為RANDOM時,因為沒有分桶列,無法根據分桶列的值僅對幾個分桶查詢,對錶進行查詢的時將對命中分區的全部分桶同時掃描,該設定適合對錶資料整體的彙總查詢分析而不適合高並發的點查詢。
如果Duplicate表是Random Distribution的資料分布,那麼在資料匯入的時候可以設定單分區匯入模式(將
load_to_single_tablet設定為true,預設為false)。此時在巨量資料量的匯入的時,一個任務在將資料寫入對應的分區時只需要寫入一個分區。因此可以提高資料匯入的並發度和輸送量,減少資料匯入和 Compaction導致的寫放大問題,保障叢集的穩定性。
分區分桶同時使用的情境
有時間維度或帶有類似有序值維度場合,可以將這類維度列作為分區列。分區粒度可以根據匯入頻次、每個分區的資料量等進行評估。
如果有刪除歷史資料的需求(比如僅保留最近N天的資料),可以使用複合分區,通過刪除歷史分區來達到目的,也可以通過在指定分區內發送DELETE語句進行資料刪除。
解決資料扭曲問題,每個分區可以單獨指定分桶數量。例如在按天分區的情境下,當每天的資料量差異較大時,可以通過指定分區的分桶數,合理劃分不同分區的資料,分桶列建議選擇區分度大、資料可以被均勻劃分的列。