ClickHouse 的 Parts 和 Partitions

abce發表於2024-10-16

在 ClickHouse 中,磁碟上儲存表的部分資料的物理檔案稱為 "part"。"partition "是使用分割槽鍵建立的表資料的邏輯劃分。分割槽可用於提高大型表的效能和可管理性,允許使用者更有效地儲存、查詢和運算元據子集。

Parts

如前所述,"Parts"是磁碟上的物理檔案。預設情況下,所有資料相關檔案都位於"/var/lib/clickhouse"目錄下。ClickHouse 中的每個合併樹表都有唯一的儲存目錄路徑來儲存 Parts。你可以從 "system.parts "表中獲取parts的實際位置、parts名稱、分割槽資訊(如果有的話)以及其他一些有價值的資訊。

以下是從 system.parts 表查詢結果的示例。

SELECT
    substr(table, 1, 22),
    partition AS prt,
    name,
    part_type,
    path
FROM system.parts
WHERE database = 'mytest'
ORDER BY
    table ASC,
    partition ASC,
    name ASC

Query id: e2f55694-fa55-48f3-99f7-74bca59aca48

┌─substring(table, 1, 22)─┬─prt──────┬─name─────────────────┬─part_type─┬─path──────────────────────────────────────────────────────────────────────────────────────┐
│ bucket_daily_stats_v2   │ tuple()  │ all_1_1389_6_695     │ Wide      │ /var/lib/clickhouse/store/7b0/7b0e6f30-114d-472d-8ff4-f4c9a4cf7cc1/all_1_1389_6_695/      │
│ corr_pair_value         │ tuple()  │ all_208611_209409_4  │ Wide      │ /var/lib/clickhouse/store/5d7/5d732078-0f2e-4d04-9848-d216a1839b32/all_208611_209409_4/   │
│ corr_pair_value         │ tuple()  │ all_209410_209634_3  │ Wide      │ /var/lib/clickhouse/store/5d7/5d732078-0f2e-4d04-9848-d216a1839b32/all_209410_209634_3/   │
│ corr_pair_value         │ tuple()  │ all_209635_210331_4  │ Wide      │ /var/lib/clickhouse/store/5d7/5d732078-0f2e-4d04-9848-d216a1839b32/all_209635_210331_4/   │
│ mytest_of_ti            │ 202408   │ 202408_4241_5001_5   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202408_4241_5001_5/    │
│ mytest_of_ti            │ 202408   │ 202408_5002_5008_1   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202408_5002_5008_1/    │
│ mytest_of_ti            │ 202408   │ 202408_5009_5045_2   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202408_5009_5045_2/    │
│ mytest_of_ti            │ 202408   │ 202408_5046_5046_0   │ Compact   │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202408_5046_5046_0/    │
│ mytest_of_ti            │ 202409   │ 202409_5013_5624_5   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202409_5013_5624_5/    │
│ mytest_of_ti            │ 202409   │ 202409_5625_5661_2   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202409_5625_5661_2/    │
│ mytest_of_ti            │ 202409   │ 202409_5662_5699_2   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202409_5662_5699_2/    │
│ mytest_of_ti            │ 202409   │ 202409_5700_5708_1   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202409_5700_5708_1/    │
│ mytest_of_ti            │ 202409   │ 202409_5710_5793_4   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202409_5710_5793_4/    │
│ mytest_of_ti            │ 202409   │ 202409_5794_5794_0   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202409_5794_5794_0/    │
│ mytest_of_ti            │ 202409   │ 202409_5795_5795_0   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202409_5795_5795_0/    │
│ mytest_of_ti            │ 202410   │ 202410_5703_5810_7   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202410_5703_5810_7/    │
│ mytest_of_ti            │ 202410   │ 202410_5811_5849_2   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202410_5811_5849_2/    │
│ mytest_of_ti            │ 202410   │ 202410_5850_5882_2   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202410_5850_5882_2/    │
│ mytest_of_ti            │ 202410   │ 202410_5883_5888_1   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202410_5883_5888_1/    │
│ mytest_of_ti            │ 202410   │ 202410_5889_5894_1   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202410_5889_5894_1/    │
│ mytest_of_ti            │ 202410   │ 202410_5895_5901_1   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202410_5895_5901_1/    │
│ mytest_of_ti            │ 202410   │ 202410_5902_5902_0   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202410_5902_5902_0/    │
│ mytest_of_ti            │ 202410   │ 202410_5903_5903_0   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202410_5903_5903_0/    │
│ mytest_of_ti            │ 202410   │ 202410_5904_5904_0   │ Wide      │ /var/lib/clickhouse/store/32f/32ff94bb-43af-47c3-99b1-5c85e912483c/202410_5904_5904_0/    │
└─────────────────────────┴──────────┴──────────────────────┴───────────┴───────────────────────────────────────────────────────────────────────────────────────────┘

在 "part_type" 列中,"Wide "表示每一列都儲存在檔案系統中的一個單獨檔案中,而 "Compact "表示所有列都儲存在檔案系統的一個檔案中。此外,"partition "列中的 "tuple() "表示該表未分割槽。

也可以在目錄“/var/lib/clickhouse/data/<DBNAME>/<TABLENAME>”中檢視錶的 parts,會發現該目錄下存放都是符號連結,透過連結可以檢視錶包含的parts。比如,進入表 mytest_of_ti 所在目錄檢視:

# ls -la
......
drwxr-x--- 2 clickhouse clickhouse 4096 Oct 10 15:10 202410_5811_5849_2
drwxr-x--- 2 clickhouse clickhouse 4096 Oct 11 15:10 202410_5850_5882_2
drwxr-x--- 2 clickhouse clickhouse 4096 Oct 11 15:10 202410_5883_5888_1
drwxr-x--- 2 clickhouse clickhouse 4096 Oct 11 15:10 202410_5889_5894_1
drwxr-x--- 2 clickhouse clickhouse 4096 Oct 13 15:10 202410_5895_5901_1
drwxr-x--- 2 clickhouse clickhouse 4096 Oct 13 15:10 202410_5902_5902_0
drwxr-x--- 2 clickhouse clickhouse 4096 Oct 13 15:10 202410_5903_5903_0
drwxr-x--- 2 clickhouse clickhouse 4096 Oct 13 15:10 202410_5904_5904_0
drwxr-x--- 2 clickhouse clickhouse   10 Nov 11  2023 detached
-rw-r----- 1 clickhouse clickhouse    1 Nov 11  2023 format_version.txt
-rw-r----- 1 clickhouse clickhouse  123 Dec 11  2023 mutation_252.txt
-rw-r----- 1 clickhouse clickhouse   96 Dec 11  2023 mutation_259.txt
-rw-r----- 1 clickhouse clickhouse  230 Dec 11  2023 mutation_264.txt

可以統計出表mytest_of_ti一共有個part:

# ls -la |grep 20 |wc -l
133

以列出的202410_5850_5882_2 該part為例,其中202410表示分割槽id;5850表示part中最小的塊號;5882表示最大的塊號;2表示塊的級別(chunk level)。如果沒有做分割槽,part 的名稱以all_開頭。

也可以從系統表 system.parts 中檢視該part 的資訊:

SELECT
    name,
    partition_id,
    min_block_number,
    max_block_number,
    level,
    data_version
FROM system.parts
WHERE (database = 'factor_data') AND (table = 'factor_value_of_trading') AND (name = '202410_5850_5882_2')

Query id: e6678b2e-31e2-4a9d-9c9b-16ff8bf9ecf0

┌─name───────────────┬─partition_id─┬─min_block_number─┬─max_block_number─┬─level─┬─data_version─┐
│ 202410_5850_5882_2 │ 202410       │             5850 │             5882 │     2 │         5850 │
└────────────────────┴──────────────┴──────────────────┴──────────────────┴───────┴──────────────┘

1 row in set. Elapsed: 0.006 sec. 

Partitions

與 parts 一樣,也可以從 “system.parts ”表中訪問合併樹表的分割槽資訊。不過,分割槽列現在不是用 “tuple() ”來表示了。要建立分割槽表,首先需要在建立表時使用 “PARTITION BY expr ”子句。例如,“PARTITION BY toYYYMMDD(start_time) ”子句建立了一個與 “start_time ”列相關的每日分割槽。在下面的示例中,可以看到Partitions名和parts名是不同的。分割槽類似於邏輯上的劃分,但 parts是作為物理檔案定位的。一個分割槽可以包含一個或多個 parts。

SELECT
    partition,
    name,
    active
FROM system.parts
WHERE (table = 'mytest_of_ti') AND (database = 'test')

Query id: 9c8c15bc-df95-4713-b001-39447010a1ab

┌─partition─┬─name─────────────────┬─active─┐
│ 201411    │ 201411_1_1_1_264     │      1 │
│ 201411    │ 201411_2_2_3_264     │      1 │
│ 201412    │ 201412_3_3_3_264     │      1 │
│ 201412    │ 201412_4_4_1_264     │      1 │
│ 201412    │ 201412_5_5_1_264     │      1 │
│ 202001    │ 202001_6_6_3_264     │      1 │
│ 202002    │ 202002_7_7_3_264     │      1 │
│ 202003    │ 202003_8_8_3_264     │      1 │
│ 202004    │ 202004_9_9_1_264     │      1 │
│ 202004    │ 202004_10_10_3_264   │      1 │
...
│ 202410    │ 202410_5934_5934_0   │      0 │
│ 202410    │ 202410_5935_5935_0   │      0 │
│ 202410    │ 202410_5936_5936_0   │      0 │
│ 202410    │ 202410_5937_5937_0   │      1 │
│ 202410    │ 202410_5938_5938_0   │      1 │
└───────────┴──────────────────────┴────────┘

167 rows in set. Elapsed: 0.005 sec. 

通常,分割槽是用來提升查詢效能。便於使用者靈活地管理資料子集。可以直接查詢分割槽、刪除分割槽等。

可以透過指定 where 子句或者使用隱藏列"_partition_id" 來檢視指定的分割槽。當然最好使用官方推薦的方式,在where子句中新增分割槽條件比較好。 有些特殊情況下,需要使用隱藏列"_partition_id"。

現在,讓我們看看分割槽表的查詢示例。假設我們的表(test.mytest_of_ti)對 “created_at ”列進行了分割槽。我們可以使用分割槽鍵列和隱藏的“_partition_id ”列訪問特定分割槽。此外,我們還可以使用“_partition_id ”查詢前 10 個分割槽。

SELECT count()
FROM recoDB.mytest_of_ti
WHERE toDate(created_at) = '2023-01-21'

┌─count()─┐
│    9731 │
└─────────┘
#####################################

SELECT count()
FROM test.mytest_of_ti
WHERE _partition_id = '20230121'

┌─count()─┐
│   9731  │
└─────────┘
SELECT
    _partition_id,
    count()
FROM test.mytest_of_ti
GROUP BY _partition_id
ORDER BY 2 DESC
LIMIT 10

解除安裝或附加Parts/Partitions

detach 操作可以將指定的 parts/partitions 移動到 detached 目錄。在重新附加之前,使用者無法訪問這些資料。預設情況下,detached 目錄位於"/var/lib/clickhouse/data/<DATABASE_NAME>/<TABLE_NAME>"中。

而 attach 操作可以將被解除安裝的parts/partitions 附加進來。語法如下所示:

#DETACH PART/PARTITION
ALTER TABLE <DATABASE_NAME>.<TABLE_NAME> [ON CLUSTER <CLUSTER_NAME>] DETACH PARTITION|PART <PARTITION_EXPRESSION>

#ATTACH PART/PARTITION
ALTER TABLE <DATABASE_NAME>.<TABLE_NAME> [ON CLUSTER <CLUSTER_NAME>] ATTACH PARTITION|PART <PARTITION_EXPRESSION>

這裡的 PARTITION_EXPRESSION 是分割槽名、分割槽id或表示式自身。

這裡,分割槽表示式應該是分割槽名稱、分割槽 id 或表示式本身。

假設我們要從 “test.mytest_of_ti ”表中解除安裝任何指定日期的分割槽。

首先,找到指定日期的分割槽和parts名稱。如圖所示,該日期位於分割槽 “20231013 ”中,該分割槽有兩個part。

SELECT
    partition,
    name,
    partition_id
FROM system.parts
WHERE (database = 'test') AND (table = 'mytest_of_ti') AND (partition_id IN (
    SELECT DISTINCT _partition_id
    FROM test.mytest_of_ti
    WHERE toDate(created_at) = '2023-10-13'
))


┌─partition─┬─name─────────────┬─partition_id─┐
│ 20231013  │ 20231013_62_62_0 │ 20231013     │
│ 20231013  │ 20231013_78_78_0 │ 20231013     │
└───────────┴──────────────────┴──────────────┘

開始解除安裝分割槽"20231013",然後再附加進來。

-- Count related date before detached
SELECT count()
FROM test.mytest_of_ti
WHERE toDate(created_at) = '2023-10-13'
┌─count()─┐
│   78536 │
└─────────┘

-- detach partition
ALTER TABLE test.mytest_of_ti DETACH PARTITION 20231013
Ok.

-- Count related date after detached
SELECT count()
FROM test.mytest_of_ti
WHERE toDate(created_at) = '2023-10-13'
┌─count()─┐
│       0 │
└─────────┘

然後再重新附加進來:

--attach partition
ALTER TABLE test.mytest_of_ti ATTACH PARTITION 20231013

Ok.
--Count related date after attach partition
SELECT count()
FROM test.mytest_of_ti
WHERE toDate(created_at) = '2023-10-13'
┌─count()─┐
│   78536 │
└─────────┘

同樣對單個part做如上的操作。

--find parts for given date
SELECT
    partition,
    name,
    partition_id
FROM system.parts
WHERE (database = 'test') AND (table = 'mytest_of_ti') AND (partition_id IN (
    SELECT DISTINCT _partition_id
    FROM test.mytest_of_ti
    WHERE toDate(created_at) = '2023-10-13'
))
┌─partition─┬─name───────────────┬─partition_id─┐
│ 20231013  │ 20231013_142_142_0 │ 20231013     │
│ 20231013  │ 20231013_143_143_0 │ 20231013     │
└───────────┴────────────────────┴──────────────┘

-- Count before detach part operation
SELECT count()
FROM test.mytest_of_ti
WHERE toDate(created_at) = '2023-10-13'
┌─count()─┐
│   78536 │
└─────────┘

-- detach any of the parts
ALTER TABLE test.mytest_of_ti DETACH PART '20231013_142_142_0'
Ok.

-- Count after detach part operation
SELECT count()
FROM test.mytest_of_ti
WHERE toDate(created_at) = '2023-10-13'
┌─count()─┐
│   78406 │
└─────────┘

-- Attach related part
ALTER TABLE test.mytest_of_ti ATTACH PART '20231013_142_142_0'

-- Count after attach the part
SELECT count()
FROM test.mytest_of_ti
WHERE toDate(created_at) = '2023-10-13'
┌─count()─┐
│   78536 │
└─────────┘

也可以將源表的分割槽附加到目標表上,如下例所示,附加後資料不會從源表或目標表被刪除:

ALTER TABLE <DESTINATION_TABLE> [ON CLUSTER <CLUSTER_NAME>] ATTACH PARTITION <PARTITION EXPRESSION> FROM <SOURCE_TABLE>

不過,該操作需要目標表和源表滿足以下條件:

·結構相同

·分割槽鍵、排序鍵和主鍵相同

·儲存策略相同

刪除Parts/Partitions

刪除的parts/Partitions 在系統表 system.parts 中被標記為不活躍的,且刪除操作完成後,對應的parts/Partitions 會在 "/var/lib/clickhouse/data/<database_name>/<table_name>/" 中保留十分鐘。

-- Drop from table itself
ALTER TABLE <DATABASE_NAME>.<TABLE_NAME> [ON CLUSTER <CLUSTER_NAME>] DROP PARTITION|PART 'PART/PARTITON EXPRESSION'

-- Remove specified part/partition from detached folder
ALTER TABLE <DATABASE_NAME>.<TABLE_NAME> [ON CLUSTER <CLUSTER_NAME>] DROP DETACHED PARTITION|PART 'PART/PARTITON EXPRESSION'

移動Parts/Partitions

可以將partitions移動到 Clickhouse 中的另一個表。在這種情況下,源表和目標表都具有相同的結構、分割槽鍵、按鍵排序、主鍵、儲存策略和引擎系列。

對於合併樹引擎表,移動的另一個選項是將parts或partitions移動到另一個磁碟或捲上。

-- Usage: ALTER TABLE <DATABASE_NAME.SOURCE_TABLE> [ON CLUSTER <CLUSTER_NAME>] MOVE PARTITION <PARTITION EXPRESSION> TO TABLE <DATABASE_NAME.DESTINATION_TABLE>

-- 1. Create empty mytest_of_ti_new table same as the mytest_of_ti

-- 2. Move 20231013 partition from mytest_of_ti to mytest_of_ti_new
ALTER TABLE test.mytest_of_ti
    MOVE PARTITION '20231013' TO TABLE test.mytest_of_ti_new
Ok.
--3. Take partition count for 20231013 in source table
SELECT count()
FROM test.mytest_of_ti
WHERE _partition_id = '20231013'
┌─count()─┐
│       0 │
└─────────┘

--4 Take partition count for 20231013 in destination table
SELECT count()
FROM test.mytest_of_ti_new
WHERE _partition_id = '20231013'

Query id: 49319c73-c84f-4fbf-838d-ab787971eaad

┌─count()─┐
│   78536 │
└─────────┘

移動 parts/partitions 到其它的磁碟或卷,需要設定一個儲存策略,並使用建立的策略建立一個新表。這裡不再講儲存策略。

-- Our table's(mytest_of_ti_with_storage_policy) policy is "vo1_to_vo2" 
-- and this volume contain 2 disks(vo1 and vo2)
SELECT *
FROM system.storage_policies
WHERE policy_name = (
    SELECT storage_policy
    FROM system.tables
    WHERE (database = 'test') AND (name = 'mytest_of_ti_with_storage_policy')

┌─policy_name─┬─volume_name─┬─volume_priority─┬─disks───┬─volume_type─┬─max_data_part_size─┬─move_factor─┬─prefer_not_to_merge─┐
│ vo1_to_vo2  │ vo1_volume  │               1 │ ['vo1'] │ JBOD        │                  0 │         0.1 │                   0 │
│ vo1_to_vo2  │ vo2_volume  │               2 │ ['vo2'] │ JBOD        │                  0 │         0.1 │                   0 │
└─────────────┴─────────────┴─────────────────┴─────────┴─────────────┴────────────────────┴─────────────┴─────────────────────┘

-- The 20231013 partition is stored under vo1 disk.
SELECT
    partition,
    name,
    path
FROM system.parts
WHERE (database = 'test') AND (table = 'mytest_of_ti_with_storage_policy') AND (partition = '20231013')


┌─partition─┬─name───────────────┬─path────────────────────────────────────────────────────────────────────┐
│ 20231013  │ 20231013_107_107_0 │ /vo1/store/346/3465ekb6-231d-493d-94bc-518234d79b45/20231013_107_107_0/ │
└───────────┴────────────────────┴─────────────────────────────────────────────────────────────────────────┘

-- Let's move it to the vo2 disk
ALTER TABLE test.mytest_of_ti_with_storage_policy
    MOVE PARTITION '20231013' TO DISK 'vo2'

-Check the new volume
SELECT
    partition,
    name,
    path
FROM system.parts
WHERE (database = 'test') AND (table = 'mytest_of_ti_with_storage_policy') AND (partition = '20231013')

┌─partition─┬─name───────────────┬─path────────────────────────────────────────────────────────────────────┐
│ 20231013  │ 20231013_107_107_0 │ /vo2/store/346/3465ekb6-231d-493d-94bc-518234d79b45/20231013_107_107_0/ │
└───────────┴────────────────────┴─────────────────────────────────────────────────────────────────────────┘

-- You can move parts and also you can move volume instead of disk 

相關文章