ClickHouse - 多卷存儲擴大存儲容量(生產環境必備)

筆者最近工作有點忙,加上培訓較多,近期文章更新慢了一拍。不過,今天爲 ClickHouse 的愛好者帶來一篇非常不錯的文章,部分內容來自 Altinity,以及筆者補充和整理而成。

長期使用 ClickHouse 的用戶都知道,每個 ClickHouse 服務都是單個進程,可訪問位於單個存儲設備上的數據。該設計提供了操作簡便性,這是一個很大的優點,但將用戶使用的所有數據限制在單一存儲類中。缺點是難以選擇成本/性能,特別是對於大型集羣。

在 2019 年期間,Altinity 和 ClickHouse 社區一直在努力使 ClickHouse 表能夠將存儲劃分爲包含多個設備的卷,並在它們之間自動移動數據。從 19.15 版本開始,ClickHouse 開始實現多卷存儲的功能。它具有多種用途,其中最重要的用途是將熱數據和冷數據存儲在不同類型的存儲中。這種配置被稱爲分層存儲,正確使用它可以極大地提高 ClickHouse 服務的經濟性。

在接下來的文章中,筆者將介紹 ClickHouse 的多卷存儲並展示其具體的工作方式。爲了提供生產環境實戰的參考,筆者將演示如何實現分層存儲,並說明其他用途。

多卷存儲的目的

假設有一個 ClickHouse 集羣來處理保留30天數據的服務日誌。用戶從當天開始對數據進行 95% 的交互式查詢。其餘 5% 是用於趨勢分析和機器學習的長期運行的批查詢。因此,這裏的選擇是:使用快速的 SSD 存儲在 3% 的數據上快速運行常見查詢,或者使用便宜的 HDD 來滿足其他 97% 的數據。快速的或便宜的,而不是兩者兼而有之。

幸運的是,ClickHouse 提供了一個更好的方案:分層存儲。

該想法是將新數據存儲在 NVMe SSD 等快速存儲中,然後再將其移動到速度較慢的存儲(例如 HDD)中。這不僅節省了存儲空間,而且通常可以減少服務器的總數而不犧牲性能,這是效率的雙贏,可以將硬件成本降低多達80%。毫不奇怪,多年來分層存儲一直是最重要的功能要求。

在多卷存儲功能實現之前,用戶藉助 Merge 引擎和部分手動操作將一個邏輯表的數據存儲在多個物理磁盤上。這種方法允許用戶解決多層存儲的問題,但這是一個非常不方便的解決方法。

ClickHouse 多卷存儲使分層存儲成爲可能,甚至非常方便。我們可以將表數據存儲在多個設備上,分爲多個卷,甚至可以自動跨卷移動數據。多卷存儲也解決了其他問題,例如,多卷存儲還提供了一種簡單的機制,可以通過附加新設備來擴展服務器容量。

ClickHouse 多卷存儲架構

在進入示例之前,筆者先介紹一下 ClickHouse 中多卷存儲的工作原理。一圖勝千言,如圖所示。 

該方案看似複雜,但實際上非常簡單。每個 MergeTree 表都與一個存儲策略( storage_policy)相關聯。策略只是用於編寫 MergeTree 表數據的規則,將磁盤分爲一個或多個卷。它們還定義了每個卷中磁盤的寫順序,以及有多個卷時數據在卷之間的移動方式。

存儲策略與舊錶向後兼容。ClickHouse 始終有一個名爲 default 的磁盤,該磁盤指向 config.xml 中的數據目錄路徑。還有一個相應的策略稱爲 default。如果 MergeTree 表沒有存儲策略,則 ClickHouse 將使用默認策略並寫入默認磁盤。

接下來,筆者將通過示例來進行說明。

服務器設置

接下來,我們將多卷安裝在具有多個塊設備的 ClickHouse 服務器上。

首先,需要一臺具有多個硬盤驅動器的服務器。對於此特定測試,我在 Ubuntu 18.04 (筆者生產環境爲 CentOS)上連接了三個附加存儲卷:

  • 2 x SSD 400GB

  • 1 x HDD 1000GB

可以使用方便的 lsblk 命令查看額外的塊設備。

$ lsblk
NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
...
nvme0n1     259:0    0  400G  0 disk
nvme2n1     259:1    0   10G  0 disk
└─nvme2n1p1 259:3    0   10G  0 part /
nvme1n1     259:2    0 1000G  0 disk
nvme3n1     259:4    0  400G  0 disk

筆者生產環境磁盤數超過4塊,官方建議使用 RAID-6 或 RAID-50,但是綜合考慮實際情況筆者使用 RAID5。

使用 ext4 文件系統格式化磁盤並掛載它們。

sudo mkfs -t ext4 /dev/nvme0n1
sudo mkfs -t ext4 /dev/nvme1n1
sudo mkfs -t ext4 /dev/nvme3n1
sudo mkdir /mnt/ebs_gp2_1
sudo mkdir /mnt/ebs_gp2_2
sudo mkdir /mnt/ebs_sc1_1
sudo mount -o noatime,nobarrier /dev/nvme0n1 /mnt/ebs_gp2_1
sudo mount -o noatime,nobarrier /dev/nvme3n1 /mnt/ebs_gp2_2
sudo mount -o noatime,nobarrier /dev/nvme1n1 /mnt/ebs_sc1_1

爲了使本示例簡單起見,我們將跳過將卷添加到 fstab 的操作,因此重新啓動後將不會自動掛載它們。

現在,我們來安裝 ClickHouse。當前,筆者建議您使用最新的 19.17 穩定版本進行測試。(也可以使用更高版本,介於原作者實驗,本文中的示例基於版本 19.16.3.6)

sudo apt-get install dirmngr
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv E0C56BD4
echo "deb http://repo.yandex.ru/clickhouse/deb/stable/ main/" | sudo tee /etc/apt/sources.list.d/clickhouse.list
sudo apt-get update
sudo apt-get install -y clickhouse-server clickhouse-client
sudo systemctl start clickhouse-server
sudo systemctl status clickhouse-server
clickhouse-client --query='SELECT version()'
19.16.3.6

配置 ClickHouse 存儲磁盤

安裝完成後,ClickHouse 會使用默認配置,並將數據默認存儲在 /var/lib/clickhouse/data 文件夾的根分區中。爲了驗證默認存儲位置,我們可以使用 system.disks 表,該表顯示 ClickHouse 使用的磁盤:

SELECT
    name,
    path,
    formatReadableSize(free_space) AS free,
    formatReadableSize(total_space) AS total,
    formatReadableSize(keep_free_space) AS reserved
FROM system.disks
┌─name────┬─path─────────────────┬─free─────┬─total────┬─reserved─┐
│ default │ /var/lib/clickhouse/ │ 7.49 GiB │ 9.63 GiB │ 0.00 B   │
└─────────┴──────────────────────┴──────────┴──────────┴──────────┘

由於我們有可用的額外磁盤,因此現在配置 ClickHouse 並使用它們。我們將以下配置添加到 /etc/clickhouse-server/config.d/storage.xml

注意:

將存儲配置放在 config.d 中,這可以防止它在升級時被刪除。

<yandex>
  <storage_configuration>
    <disks>
      <!--
          default disk is special, it always
          exists even if not explicitly
          configured here, but you can't change
          it's path here (you should use <path>
          on top level config instead)
      -->
      <default>
         <!--
             You can reserve some amount of free space
             on any disk (including default) by adding
             keep_free_space_bytes tag
         -->
         <keep_free_space_bytes>1024</keep_free_space_bytes>
      </default>
      <ebs_gp2_1>
         <!--
         disk path must end with a slash,
         folder should be writable for clickhouse user
         -->
         <path>/mnt/ebs_gp2_1/</path>
      </ebs_gp2_1>
      <ebs_gp2_2>
          <path>/mnt/ebs_gp2_2/</path>
      </ebs_gp2_2>
      <ebs_sc1_1>
          <path>/mnt/ebs_sc1_1/</path>
      </ebs_sc1_1>
    </disks>
  </storage_configuration>
</yandex>

要應用更改,必須重新啓動 ClickHouse。每次我們更改存儲配置時都需要這樣做。

$ sudo systemctl restart clickhouse-server
$ sudo systemctl status clickhouse-server
● clickhouse-server.service - ClickHouse Server (analytic DBMS for big data)
   Loaded: loaded (/etc/systemd/system/clickhouse-server.service; enabled; vendor preset: enabled)
   Active: activating (auto-restart) (Result: exit-code) since Mon 2019-11-04 15:02:06 UTC; 4s ago
  Process: 3146 ExecStart=/usr/bin/clickhouse-server --config=/etc/clickhouse-server/config.xml --pid-file=/run/clickhouse-server/clickhouse-server.pid (code=exited, status=70)
 Main PID: 3146 (code=exited, status=70)

糟糕,ClickHouse 無法啓動,不過不要緊張,讓我們檢查一下日誌:

sudo tail -n 10 /var/log/clickhouse-server/clickhouse-server.err.log
...
<Error> Application: DB::Exception: There is no RW access to disk ebs_gp2_1

錯誤提示很明顯,就是忘記了將存儲文件夾的權限授予 ClickHouse 用戶。修復一下權限問題:

sudo chown clickhouse:clickhouse -R /mnt/ebs_gp2_1 /mnt/ebs_gp2_2 /mnt/ebs_sc1_1
sudo systemctl restart clickhouse-server
sudo systemctl status clickhouse-server

再次重啓後,查看 ClickHouse 服務狀態。

sudo systemctl status clickhouse-server
● clickhouse-server.service - ClickHouse Server (analytic DBMS for big data)
   Loaded: loaded (/etc/systemd/system/clickhouse-server.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2019-11-04 15:10:55 UTC; 2s ago
 Main PID: 3714 (clickhouse-serv)
    Tasks: 40 (limit: 4633)
   CGroup: /system.slice/clickhouse-server.service
           └─3714 /usr/bin/clickhouse-server --config=/etc/clickhouse-server/config.xml --pid-file=/run/clickhouse-server/clickhouse-server.pid
... systemd[1]: Started ClickHouse Server (analytic DBMS for big data).

好的,現在 ClickHouse 正在運行,我們可以看到所有的存儲磁盤:

SELECT
    name,
    path,
    formatReadableSize(free_space) AS free,
    formatReadableSize(total_space) AS total,
    formatReadableSize(keep_free_space) AS reserved
FROM system.disks
┌─name──────┬─path─────────────────┬─free───────┬─total──────┬─reserved─┐
│ default   │ /var/lib/clickhouse/ │ 7.49 GiB   │ 9.63 GiB   │ 1.00 KiB │
│ ebs_gp2_1 │ /mnt/ebs_gp2_1/      │ 366.06 GiB │ 392.72 GiB │ 0.00 B   │
│ ebs_gp2_2 │ /mnt/ebs_gp2_2/      │ 372.64 GiB │ 392.72 GiB │ 0.00 B   │
│ ebs_sc1_1 │ /mnt/ebs_sc1_1/      │ 933.21 GiB │ 983.30 GiB │ 0.00 B   │
└───────────┴──────────────────────┴────────────┴────────────┴──────────┘

此時,我們有4個可用磁盤用於存儲數據。

接下來筆者將正式介紹 ClickHouse 存儲策略,並從簡單配置擴展到複雜配置,這將幫助我們達到配置分層存儲的目標。

存儲策略

上面,筆者已經講解了如何配置多個磁盤。但是,爲了使用多卷存儲還有更多工作要做。此時,表 MergeTree 數據仍存儲在默認磁盤上(即 /var/lib/clickhouse/)。可以從 system.tables 查詢信息:

CREATE TABLE sample1 (id UInt64) Engine=MergeTree ORDER BY id;
INSERT INTO sample1 SELECT * FROM numbers(1000000);
SELECT name, data_paths FROM system.tables WHERE name = 'sample1'\G
Row 1:
──────
name:       sample1
data_paths: ['/var/lib/clickhouse/data/default/sample1/']
SELECT name, disk_name, path FROM system.parts
   WHERE (table = 'sample1') AND active\G
Row 1:
──────
name:      all_1_1_0
disk_name: default
path:      /var/lib/clickhouse/data/default/sample1/all_1_1_0/

在不同磁盤上存儲數據的規則由存儲策略設置。在新安裝或升級的 ClickHouse 服務中,有一個存儲策略,稱爲 default,表示所有數據都應放置在默認磁盤上。此策略可確保對現有表的向後兼容性。我們可以通過從 system.storage_policies 表中查詢來查看定義,該表用於幫助管理分層存儲。

SELECT policy_name, volume_name, disks
FROM system.storage_policies
┌─policy_name────┬─volume_name─────────┬─disks─────────────────────┐
│ default        │ default             │ ['default']               │
└────────────────┴─────────────────────┴───────────────────────────┘

單磁盤策略

筆者將帶大家從一個非常簡單的示例開始,我們將引入名稱爲 ebs_gp2_1_only 的存儲策略,該策略將所有數據存儲在包含 ebs_gp2_1 磁盤的 ebs_gp2_1_volume 上。

我們已經所有與存儲相關的配置放在文件 /etc/clickhouse-server/config.d/storage.xml 中。

以下示例顯示了之前創建的磁盤配置:

<yandex>
  <storage_configuration>
    <disks>
      <!--
          default disk is special, it always
          exists even if not explicitly
          configured here, but you can't change
          it's path here (you should use <path>
          on top level config instead)
      -->
      <default>
         <!--
             You can reserve some amount of free space
             on any disk (including default) by adding
             keep_free_space_bytes tag
         -->
         <keep_free_space_bytes>1024</keep_free_space_bytes>
      </default>
      <ebs_gp2_1>
         <!--
         disk path must end with a slash,
         folder should be writable for clickhouse user
         -->
         <path>/mnt/ebs_gp2_1/</path>
      </ebs_gp2_1>
      <ebs_gp2_2>
          <path>/mnt/ebs_gp2_2/</path>
      </ebs_gp2_2>
      <ebs_sc1_1>
          <path>/mnt/ebs_sc1_1/</path>
      </ebs_sc1_1>
    </disks>
    <policies>
      <ebs_gp2_1_only> <!-- name for new storage policy -->
        <volumes>  
          <ebs_gp2_1_volume> <!-- name of volume -->
            <!--
                we have only one disk in that volume
                and we reference here the name of disk
                as configured above in <disks> p
            -->
            <disk>ebs_gp2_1</disk>
          </ebs_gp2_1_volume>
        </volumes>
      </ebs_gp2_1_only>
    </policies>
  </storage_configuration>
</yandex>

重新啓動 ClickHouse 以應用配置更改並檢查配置的新策略是否可見:

SELECT policy_name, volume_name, disks
FROM system.storage_policies
┌─policy_name────┬─volume_name─────────┬─disks─────────────────────┐
│ default        │ default             │ ['default']               │
│ ebs_gp2_1_only │ ebs_gp2_1_volume    │ ['ebs_gp2_1']             │
└────────────────┴─────────────────────┴───────────────────────────┘

太酷了,那麼我們現在如何使用它們呢?

其實針對用戶來說,使用起來很簡單,只需在新表上添加即可:

SETTINGS storage_policy = 'ebs_gp2_1_only'

創建表的完整示例如下:

CREATE TABLE sample2 (id UInt64) Engine=MergeTree 
ORDER BY id SETTINGS storage_policy = 'ebs_gp2_1_only';
INSERT INTO sample2 SELECT * FROM numbers(1000000);

現在我們可以看到該表具有存儲策略 ebs_gp2_1_only,並且所有 parts 都存儲在 /mnt/ebs_gp2_1 上:

SELECT name, data_paths, metadata_path, storage_policy
FROM system.tables
WHERE name LIKE 'sample%'
Row 1:
──────
name:           sample1
data_paths:     ['/var/lib/clickhouse/data/default/sample1/']
metadata_path:     /var/lib/clickhouse/metadata/default/sample1.sql
storage_policy:     default
Row 2:
──────
name:           sample2
data_paths:     ['/mnt/ebs_gp2_1/data/default/sample2/']
metadata_path:     /var/lib/clickhouse/metadata/default/sample2.sql
Storage_policy:    ebs_gp2_1_only

根據查詢 system.tables 顯示的結果,可以看到兩個表具有不同的數據路徑和不同的存儲策略。

請注意,表元數據保留在默認磁盤上。

我們還可以檢查每個 part 的存儲位置:

SELECT table, disk_name, path
FROM system.parts
WHERE table LIKE 'sample%'
Row 1:
──────
table:     sample1
disk_name: default
path:      /var/lib/clickhouse/data/default/sample1/all_1_1_0/
Row 2:
──────
table:     sample2
disk_name: ebs_gp2_1
path:      /mnt/ebs_gp2_1/data/default/sample2/all_1_1_0/

到此,我們知道了如何在另一個磁盤上存儲表。

當然,無論源表和目標表位於何處,都可以使用常規的 INSERT ... SELECT 將數據從一個表複製到另一表。

JBOD:具有多個磁盤的單層卷

在前面的示例中,我們將數據存儲在一個磁盤上。那麼我們如何將數據存儲在多個磁盤上?

我們可以使用存儲策略在一個卷中將兩個或多個磁盤分組。當我們這樣做時,數據將以輪循(round-robin)方式在磁盤之間分配:

每次 insert(或 merge)都會在卷中的下一個磁盤上創建 part。parts 的一半存儲在一個磁盤上,其餘部分存儲在另一個磁盤上。這個概念通常稱爲 JBOD,它是 “Just a Bunch of Disks” 的縮寫。

JBOD 卷組織提供了以下好處:

1. 通過附加磁盤來擴展存儲的簡便方法,這比遷移到 RAID 簡單得多。

2. 在某些情況下(例如,當多個線程並行使用不同的磁盤時),讀/寫速度會提高。

3. 由於每個磁盤上的 parts 數較少,因此表加載速度更快。

請注意,當使用 JBOD 時,一個磁盤的故障將導致數據丟失。添加更多磁盤會增加丟失至少一些數據的機會。當要求系統有容錯能力時,請始終使用複製方式。

現在,讓我們嘗試將兩個 SSD 磁盤連接到一個 JBOD 卷中。我們將以下存儲策略添加到 storage.xml 文件中:

<ebs_gp2_jbod> > <!-- name for new storage policy -->
  <volumes>
    <ebs_gp2_jbod_volume> <!-- name of volume -->
       <!--
          the order of listing disks inside
          volume defines round-robin sequence
          -->
          <disk>ebs_gp2_1</disk>
          <disk>ebs_gp2_2</disk>
    </ebs_gp2_jbod_volume>
  </volumes>
</ebs_gp2_jbod>

我們重新啓動 ClickHouse 並檢查 system.storage_policies 中的存儲策略。

SELECT policy_name, volume_name, disks
FROM system.storage_policies
┌─policy_name────┬─volume_name─────────┬─disks─────────────────────┐
│ default        │ default             │ ['default']               │
│ ebs_gp2_1_only │ ebs_gp2_1_volume    │ ['ebs_gp2_1']             │
│ ebs_gp2_jbod   │ ebs_gp2_jbod_volume │ ['ebs_gp2_1','ebs_gp2_2'] │
└────────────────┴─────────────────────┴───────────────────────────┘

讓我們使用新的策略創建另外一張表:

CREATE TABLE sample3 (id UInt64) Engine=MergeTree ORDER BY id SETTINGS storage_policy = 'ebs_gp2_jbod';
SELECT
    name,
    data_paths,
    metadata_path,
    storage_policy
FROM system.tables
WHERE name = 'sample3'
Row 1:
──────
name:           sample3
data_paths:     ['/mnt/ebs_gp2_1/data/default/sample3/','/mnt/ebs_gp2_2/data/default/sample3/']
metadata_path:  /var/lib/clickhouse/metadata/default/sample3.sql
storage_policy: ebs_gp2_jbod

接着,我們可以添加數據並檢查 parts 存儲的位置:

insert into sample3 select * from numbers(1000000);
insert into sample3 select * from numbers(1000000);
insert into sample3 select * from numbers(1000000);
insert into sample3 select * from numbers(1000000);
select name, disk_name, path from system.parts where table = 'sample3';
┌─name──────┬─disk_name─┬─path───────────────────────────────────────────┐
│ all_1_1_0 │ ebs_gp2_1 │ /mnt/ebs_gp2_1/data/default/sample3/all_1_1_0/ │
│ all_2_2_0 │ ebs_gp2_2 │ /mnt/ebs_gp2_2/data/default/sample3/all_2_2_0/ │
│ all_3_3_0 │ ebs_gp2_1 │ /mnt/ebs_gp2_1/data/default/sample3/all_3_3_0/ │
│ all_4_4_0 │ ebs_gp2_2 │ /mnt/ebs_gp2_2/data/default/sample3/all_4_4_0/ │
└───────────┴───────────┴────────────────────────────────────────────────┘

後臺合並可以從不同磁盤上的 parts 收集數據,並將合併完成的新的較大 part 放在該卷的其中一個磁盤上(根據輪詢算法)。我們可以通過運行 OPTIMIZE TABLE 強制合併來查看此示例中的行爲。

OPTIMIZE TABLE sample3
Ok.
0 rows in set. Elapsed: 0.240 sec.
SELECT
    name,
    disk_name,
    path
FROM system.parts
WHERE (table = 'sample3') AND active
┌─name──────┬─disk_name─┬─path───────────────────────────────────────────┐
│ all_1_4_1 │ ebs_gp2_1 │ /mnt/ebs_gp2_1/data/default/sample3/all_1_4_1/ │
└───────────┴───────────┴────────────────────────────────────────────────┘

後臺合併往往會隨着時間的流逝創建越來越大的 parts,從而將每個生成的 part 移至其中一個磁盤。因此,我們的存儲策略不能保證數據將均勻地分佈在磁盤上,它也不能保證 JBOD 上的 I/O 吞吐量要比最慢的磁盤上的 I/O 吞吐量更好。爲了獲得這樣的保證,應該改用 RAID。

使用 JBOD 存儲策略的最明顯原因是通過添加其他存儲而不移動現有數據來增加 ClickHouse 服務的容量。

多層存儲:具有不同優先級的卷

現在,我們準備研究多卷功能最有趣的用例,即分層存儲的配置。

讓我們回到本篇文章開始討論的問題。我們有新插入的數據,這些數據被認爲是熱數據,並且經常訪問,這需要快速但昂貴的存儲。接下來,我們有冷數據。在批處理查詢之外很少訪問它,並且查詢性能不是一個很大的考慮因素。冷數據可以存儲在速度更慢、價格更低的存儲上。

要在存儲策略中實施此操作,我們需要定義以下內容:

  • 在初始插入時新數據應該存儲在哪裏

  • 何時將它移到較慢的存儲空間

ClickHouse 19.15 版本使用基於 part 大小的啓發式方法來確定何時在卷之間移動 part。在 ClickHouse 中,part 大小和 part 壽命通常是緊密相關的。MergeTree 引擎一直在進行後臺合併,將新插入的數據和小的 part 隨時間合併爲越來越大的 part。這意味着大的 part 會在幾次合併後出現,因此通常情況下,part 越大就越老。

注意:

我們還在研究另一種 age-based 的方法來跨層移動數據。它擴展了 TTL 機制,該機制目前用於刪除 MergeTree 中的舊錶行,例如30天之後。當該功能完成時,我們將能夠使用 TTL 表達式在卷之間移動數據。

現在,使用以下存儲策略配置分層存儲。與之前操作一樣,我們需要重新啓動才能生效。

<ebs_hot_and_cold>
  <volumes>
    <hot_volume>
       <disk>ebs_gp2_2</disk>
        <!--
           that volume allowed to store only parts which
           size is less or equal 200Mb
        -->
        <max_data_part_size_bytes>200000000</max_data_part_size_bytes>
    </hot_volume>
    <cold_volume>
        <disk>ebs_sc1_1</disk>
        <!--
          that volume will be used only when the first
          has no space of if part size doesn't satisfy
          the max_data_part_size_bytes requirement of the
          first volume, i.e. if part size is greater
          than 200Mb
        -->
    </cold_volume>
  </volumes>
</ebs_hot_and_cold>

上述策略中卷的順序非常重要。將新的 part 存儲在磁盤上時,ClickHouse 首先嚐試將其放置在第一個卷中,然後放置在第二個卷中,依此類推。

SELECT *
FROM system.storage_policies
WHERE policy_name = 'ebs_hot_and_cold'
Row 1:
──────
policy_name:        ebs_hot_and_cold
volume_name:        hot_volume
volume_priority:    1
disks:              ['ebs_gp2_2']
max_data_part_size:     200000000
move_factor:        0.1
Row 2:
──────
policy_name:        ebs_hot_and_cold
volume_name:        cold_volume
volume_priority:    2
disks:              ['ebs_sc1_1']
Max_data_part_size:    0
move_factor:        0.1

爲了驗證效果,創建一個使用新的分層存儲配置的表。

CREATE TABLE sample4 (id UInt64) Engine=MergeTree ORDER BY id SETTINGS storage_policy = 'ebs_hot_and_cold';
INSERT INTO sample4 SELECT rand() FROM numbers(10000000); 
-- repeat 10 times
SELECT
    disk_name,
    formatReadableSize(bytes_on_disk) AS size
FROM system.parts
WHERE (table = 'sample4') AND active
┌─disk_name─┬─size───────┐
│ ebs_gp2_2 │ 121.30 MiB │
│ ebs_gp2_2 │ 123.53 MiB │
│ ebs_gp2_2 │ 131.84 MiB │
│ ebs_gp2_2 │ 5.04 MiB   │
│ ebs_gp2_2 │ 2.74 MiB   │
└───────────┴────────────┘

目前,所有數據都是熱數據,並存儲在 SSD 上。

-- 重複執行該 INSERT 操作至少 8 次
INSERT INTO sample4 SELECT rand() FROM numbers(10000000); 
SELECT
    disk_name,
    formatReadableSize(bytes_on_disk) AS size
FROM system.parts
WHERE (table = 'sample4') AND active
┌─disk_name─┬─size───────┐
│ ebs_sc1_1 │ 570.56 MiB │
│ ebs_gp2_2 │ 26.90 MiB  │
│ ebs_gp2_2 │ 29.08 MiB  │
│ ebs_gp2_2 │ 26.90 MiB  │
│ ebs_gp2_2 │ 5.04 MiB   │
│ ebs_gp2_2 │ 5.04 MiB   │
│ ebs_gp2_2 │ 5.04 MiB   │
│ ebs_gp2_2 │ 5.04 MiB   │
│ ebs_gp2_2 │ 2.74 MiB   │
└───────────┴────────────┘

可以看到合併時創建了一個很大的 part,該 part 被放置在冷的存儲中。

根據新 part 大小的估計來確定放置新 part 的位置,可能與實際 part 大小不同。對於 insert 操作,使用未壓縮的方式估算 part 大小。對於 merge 操作,ClickHouse 使用合併 parts 的壓縮大小之和 + 10%。該估計值是近似值,可能並非在所有情況下都完全準確。我們可能會看到某些 parts 比慢速存儲上的限制小一些,或者有些 parts 在快速存儲上大一些。

手動移動 parts

除了後臺合併過程中發生的自動移動之外,還可以使用新的 ALTER 命令語法手動移動 parts 和 partitions。

ALTER TABLE sample4 MOVE PART 'all_570_570_0' TO VOLUME 'cold_volume'
ALTER TABLE sample4 MOVE PART 'all_570_570_0' TO DISK 'ebs_gp2_1'
ALTER TABLE sample4 MOVE PARTITION tuple() TO VOLUME 'cold_volume'

同樣,當一個卷的可用空間不足 10% 時,ClickHouse 在後臺嘗試通過將不足空間中的 part 移動到下一個捲來釋放空間。我們可以通過更改 move_factor 來調整它,默認情況下爲 0.1 = 10%。我們還可以通過使用 move_factor 爲 0 來完全禁用自動後臺移動。

後臺移動以與後臺合併相同的方式執行。當前,我們看不到正在運行某個移動,但是可以在 system.part_log 中看到移動的日誌。

讓我們看看它是如何工作的。

我們將啓用 part_log 並設置一個很高的 move_factor 來查看會發生什麼。

切記:

請勿在生產中這樣做!

<ebs_hot_and_cold_movefactor99>
  <volumes>
    <hot_volume>
        <disk>ebs_gp2_2</disk>
        <max_data_part_size_bytes>200000000</max_data_part_size_bytes>
    </hot_volume>
    <cold_volume>
        <disk>ebs_sc1_1</disk>
    </cold_volume>
  </volumes>
  <move_factor>0.9999</move_factor>
</ebs_hot_and_cold_movefactor99>

如果想知道如何啓用 part_log 表,請將以下配置添加到 /etc/clickhouse-server/config.d/part_log.xml 中:

<yandex>
  <part_log>
    <database>system</database>
    <table>part_log</table>
    <flush_interval_milliseconds>7500</flush_interval_milliseconds>
  </part_log>
</yandex>

現在創建一個表並加載一些數據。

CREATE TABLE sample5 (id UInt64) Engine=MergeTree ORDER BY id SETTINGS storage_policy = 'ebs_hot_and_cold_movefactor99';
INSERT INTO sample5 SELECT rand() FROM numbers(100);
SELECT event_type, path_on_disk
FROM system.part_log
┌─event_type─┬─path_on_disk───────────────────────────────────┐
│ NewPart    │ /mnt/ebs_gp2_2/data/default/sample5/all_1_1_0/ │
│ MovePart   │ /mnt/ebs_sc1_1/data/default/sample5/all_1_1_0/ │
└────────────┴────────────────────────────────────────────────┘

根據上面輸出結果可以可以看到,insert 內容將新 part 放置在第1個磁盤上,但是後臺進程看到,根據移動因子將 part 移動並將其移動到第2個磁盤上。

結合 JBOD 和分層存儲

到目前爲止,我們已經看到了如何使用一組磁盤(JBOD)創建卷以及如何在單個磁盤之間創建分層存儲。在最後一個示例中,我們可以將這些概念組合起來以創建多個分層卷,每個分層卷都包含多個磁盤。

<three_tier>
  <volumes>
    <hot_volume>
      <disk>superfast_ssd1</disk>
      <disk>superfast_ssd2</disk>
      <max_data_part_size_bytes>200000000</max_data_part_size_bytes>
    </hot_volume>
    <jbod_volume>
      <disk>normal_hdd1</disk>
      <disk>normal_hdd2</disk>
      <disk>normal_hdd3</disk>
      <max_data_part_size_bytes>80000000000</max_data_part_size_bytes>
    </jbod_volume>
    <archive_volume>
      <disk>slow_and_huge</disk>
    </archive_volume>
  </volumes>
</three_tier>

總結

本文介紹瞭如何構建存儲策略並應用它們以不同方式分發 MergeTree 表數據。筆者建議最有用的配置是啓用分層存儲。另外,正如我們所看到的,也可以使用存儲策略來解決其他問題。

後續筆者將討論多卷操作管理以及一些在多卷配置中可能遇到的特殊情況和問題。

參考

  • https://clickhouse.yandex/docs/en/single/#table_engine-mergetree-multiple-volumes

  • https://www.altinity.com/blog/2019/11/27/amplifying-clickhouse-capacity-with-multi-volume-storage-part-1?rq=multi%20user

  • https://www.altinity.com/blog/2019/11/29/amplifying-clickhouse-capacity-with-multi-volume-storage-part-2

發佈了272 篇原創文章 · 獲贊 99 · 訪問量 105萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章