全部產品
Search
文件中心

ApsaraDB for SelectDB:分區分桶

更新時間:Jul 06, 2024

為了協助您深入理解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 (...)語句進行分區的增刪操作時,分區範圍的變化情況。樣本如下。

  1. 建立樣本表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)
  2. 通過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)
  3. 通過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後,p201702p201705的分區範圍沒有發生變化,而這兩個分區之間,出現了一個空缺:[2017-03-01,2017-04-01)。即如果匯入的資料範圍在這個空缺範圍內,是無法匯入的且已經存在在空缺範圍內的資料也會被刪除。

  4. 繼續刪除分區p201702,則分區結果如下。

    p201701: [MIN_VALUE, 2017-02-01)
    p201705: [2017-04-01, 2017-06-01)

    空缺範圍變為:[2017-02-01,2017-04-01)。

  5. 繼續增加一個分區 `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)。

  6. 繼續刪除分區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 (...)語句進行分區的增刪操作時,分區的變化,樣本如下。

  1. 建立樣本表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")
  2. 增加一個分區`p_uk` VALUES IN ("London"),則分區結果如下。

    p_cn: ("Beijing", "Shanghai", "Hong Kong")
    p_usa: ("New York", "San Francisco")
    p_jp: ("Tokyo")
    p_uk: ("London")
  3. 刪除分區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語句進行資料刪除。

  • 解決資料扭曲問題,每個分區可以單獨指定分桶數量。例如在按天分區的情境下,當每天的資料量差異較大時,可以通過指定分區的分桶數,合理劃分不同分區的資料,分桶列建議選擇區分度大、資料可以被均勻劃分的列。