ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ClickHouse] Parts & Partition 알아보기
    Database/Clickhouse 2024. 2. 29. 07:04
    728x90
    반응형

     

    - 목차

     

    들어가며.

    이번 글에서는 ClickHouse 의 MergeTree 엔진에서 사용되는 Parts 와 Partition 에 대해서 알아보려고 합니다.

    Partition 은 MergeTree Table 에 생성되는 데이터의 물리적인 단위입니다.

    Table 은 Partition 을 기준으로 물리적인 저장 위치가 나뉘어집니다.

    그래서 시계열 데이터의 경우에, Partition 을 시간 기준으로 나눌 수 있고, Table 의 데이터를 연월일시에 따라 개별적인 파일로써 관리됩니다.

     

    Parts 는 하나의 온전한 Partition 을 만들기 위해서 저장되는 임시 단위입니다.

    만약 시간 단위로 Partition 이 나뉘어진다고 가정하겠습니다.

    그리고 1분마다 100개의 데이터가 삽입됩니다.

    이때 1시간 동안 총 60의 Insert Query 가 발생하게 되는데, 그렇게 되면 60개의 Parts 가 생성되게 됩니다.

    그리고 60개의 Parts 는 최종적으로 1개의 Part 로 Merge 되어 하나의 Partition 을 구성합니다.

     

    간략하게 Parts 와 Partition 에 대해서 작성해보았는데요.

    이어지는 내용에서 상세하게 알아보도록 하겠습니다.

    ClickHouse 세팅하기.

    ClickHouse 의 간단한 생성을 위해서 Docker 를 활용합니다.

    아래 페이지는 Docker 를 기반으로 ClickHouse 컨테이너를 실행하는 간단한 설명을 포함합니다.

    아래 페이지의 가이드에 따라 ClickHouse 컨테이너를 실행하실 수 있습니다.

    https://westlife0615.tistory.com/694

     

    [Clickhouse] Docker 로 Clickhouse 구현하기

    - 목차 들어가며. 이번 글에서는 Docker 를 활용해서 Clickhouse 를 구축하는 글을 작성하려고 합니다. 사용할 Docker Image 는 bitnami/clickhouse 이미지입니다. 간단한 MergeTree 엔진의 Table 을 생성하고 데이

    westlife0615.tistory.com

     

    MergeTree 테이블 생성.

     

    default 데이터베이스 내부에 test_parts 테이블을 생성합니다.

    datetime64 데이터 타입의 칼럼인 `date` 이 Partition 의 기준으로 사용됩니다. 

    CREATE TABLE default.test_parts
    (
        `id` UInt32,
        `data` String,
        `date` DATETIME64
    )
    ENGINE MergeTree()
    PARTITION BY toYYYYMM(date)
    ORDER BY tuple(date)
    SETTINGS index_granularity = 8192,
    max_parts_in_total = 60000;

     

    그리고 아래의 INSERT 쿼리문을 실행합니다.

    아래의 문장들은 5개의 Rows 를 생성하며, 하나의 문장이 하나의 Row 를 생성합니다.

    insert into default.test_parts (id, data, date)
    values (1, 'test1', '2023-01-01');
    insert into default.test_parts (id, data, date)
    values (2, 'test2', '2023-02-01');
    insert into default.test_parts (id, data, date)
    values (3, 'test4', '2023-03-01');
    insert into default.test_parts (id, data, date)
    values (4, 'test4', '2023-04-01');
    insert into default.test_parts (id, data, date)
    values (5, 'test5', '2023-05-01');

     

    그 결과로써 생성되는 Parts 들은 아래와 같습니다.

    총 5개의 Parts 가 생성되구요.

    각 Part 의 특징을 알아보겠습니다.

    SELECT partition, name, part_type, rows, path FROM system.parts
    WHERE table = 'test_parts'
    AND active = 1;

     

    +---------+-------------+---------+----+
    |partition|name         |part_type|rows|
    +---------+-------------+---------+----+
    |202301   |202301_1_1_0 |Compact  |1   |
    |202302   |202302_2_2_0 |Compact  |1   |
    |202303   |202303_3_3_0 |Compact  |1   |
    |202304   |202304_4_4_0 |Compact  |1   |
    |202305   |202305_5_5_0 |Compact  |1   |
    |202306   |202306_6_8_1 |Compact  |6   |
    |202401   |202401_9_10_1|Compact  |2   |
    +---------+-------------+---------+----+

     

    동일한 Partition 의 데이터 생성하기.

    추가적으로 6개의 Rows 를 생성합니다.

    새롭게 추가되는 6개의 Rows 는 "202306" 이라는 동일한 Partition Key 를 가집니다.

    insert into default.test_parts (id, data, date)
    values (6, 'test6', '2023-06-01'), (7, 'test7', '2023-06-01');
    insert into default.test_parts (id, data, date)
    values (8, 'test8', '2023-06-02'), (9, 'test9', '2023-06-03');
    insert into default.test_parts (id, data, date)
    values (10, 'test10', '2023-06-02'), (11, 'test11', '2023-06-03');

     

    그리고 system.parts 를 조회하게 되면 아래의 결과를 얻게 됩니다.

    "202306" 에 해당하는 3개의 Parts 가 생성됩니다.

    저는 2개의 Rows 를 BatchInsert 하는 방식으로 Row 를 생성하였습니다.

    그 결과로써 rows 갯수가 2인 Parts 3개가 생성이 되죠. 

    +---------+------------+---------+----+
    |partition|name        |part_type|rows|
    +---------+------------+---------+----+
    |202301   |202301_1_1_0|Compact  |1   |
    |202302   |202302_2_2_0|Compact  |1   |
    |202303   |202303_3_3_0|Compact  |1   |
    |202304   |202304_4_4_0|Compact  |1   |
    |202305   |202305_5_5_0|Compact  |1   |
    |202306   |202306_6_6_0|Compact  |2   |
    |202306   |202306_7_7_0|Compact  |2   |
    |202306   |202306_8_8_0|Compact  |2   |
    +---------+------------+---------+----+

     

    하지만 이 3개의 Parts 는 서로 저장된 물리적인 파일 위치가 다릅니다.

    각각 서로 다른 위치에 저장됩니다.

    이 Parts 는 MergeTree 의 Compaction 에 의해서 하나로 통합됩니다.

    이는 LSM Tree 의 Merge 동작과 유사합니다.

    /bitnami/clickhouse/data/store/25f/25f18906-cd3e-4c56-a34d-7c719aa8d498/202306_6_6_0/
    /bitnami/clickhouse/data/store/25f/25f18906-cd3e-4c56-a34d-7c719aa8d498/202306_7_7_0/
    /bitnami/clickhouse/data/store/25f/25f18906-cd3e-4c56-a34d-7c719aa8d498/202306_8_8_0/

     

     

    Merge Operation .

    많은 양의 Insert Query 로 인해서 하나의 Partition 이 여러 Parts 로 분리되어 있을 경우에 Merge Operation 이 동작합니다.

    Merge Operation 을 강제로 트리거시키는 OPTIMIZE TABLE 이라는 쿼리문이 존재하는데요.

    아래 명령어를 통해서 Merge Operation 을 실행합니다.

    OPTIMIZE TABLE default.test_parts;

     

    실행 결과는 아래와 같습니다.

    "202306_6_6_0", "202306_7_7_0", "202306_8_8_0"  Parts 는 "202306_6_8_1" 로 머지됩니다.

    "202306_6_8_1" 는 Merge 되었기 때문에 rows 는 6개를 가지게 되구요.

    Part 이름 또한 6_8_1 로 변경됩니다.

    "6_8" 은 6 ~ 8 에 해당하는 Parts 가 머지되었음을 뜻하며, "_1" 는 Merge 횟수를 의미합니다.

    +---------+------------+---------+----+-------------------------------------------------------------------------------------+
    |partition|name        |part_type|rows|path                                                                                 |
    +---------+------------+---------+----+-------------------------------------------------------------------------------------+
    |202301   |202301_1_1_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202301_1_1_0/|
    |202302   |202302_2_2_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202302_2_2_0/|
    |202303   |202303_3_3_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202303_3_3_0/|
    |202304   |202304_4_4_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202304_4_4_0/|
    |202305   |202305_5_5_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202305_5_5_0/|
    |202306   |202306_6_8_1|Compact  |6   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202306_6_8_1/|
    +---------+------------+---------+----+-------------------------------------------------------------------------------------+

     

    MergeTree Part 에 대해서.

    하나의 Part 는 크게 partition, name, part_type, rows, path 라는 정보를 포함합니다.

    그 외에서 많은 정보가 존재하지만 의미있는 몇가지 정보들만 알아보려고 합니다.

     

    partition.

    MergeTree Table 에서 설정한 Partition Key 가 Partition 으로 사용됩니다.

    ClickHouse 의 MergeTree Table 은 Partition Key 를 기준으로 물리적인 파일을 관리합니다.

    예를 들어보겠습니다.

    아래와 같은 데이터가 존재하고, Partition Key 는 "YYYYMM 에 해당하는 연월" 입니다.

    1 ~ 5월은 각 하나의 Row 를 가지구요.

    6월은 6개의 Row 들을 가집니다.

    +--+------+-----------------------+
    |id|data  |date                   |
    +--+------+-----------------------+
    |11|test11|2023-06-03 00:00:00.000|
    |9 |test9 |2023-06-03 00:00:00.000|
    |10|test10|2023-06-02 00:00:00.000|
    |8 |test8 |2023-06-02 00:00:00.000|
    |7 |test7 |2023-06-01 00:00:00.000|
    |6 |test6 |2023-06-01 00:00:00.000|
    |5 |test5 |2023-05-01 00:00:00.000|
    |4 |test4 |2023-04-01 00:00:00.000|
    |3 |test4 |2023-03-01 00:00:00.000|
    |2 |test2 |2023-02-01 00:00:00.000|
    |1 |test1 |2023-01-01 00:00:00.000|
    +--+------+-----------------------+

     

    이 Row 들의 Parts 는 아래와 같이 총 6개로 구성됩니다.

    "202306" Part 는 6개의 Rows 를 가집니다.

    +---------+------------+---------+----+-------------------------------------------------------------------------------------+
    |partition|name        |part_type|rows|path                                                                                 |
    +---------+------------+---------+----+-------------------------------------------------------------------------------------+
    |202301   |202301_1_1_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202301_1_1_0/|
    |202302   |202302_2_2_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202302_2_2_0/|
    |202303   |202303_3_3_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202303_3_3_0/|
    |202304   |202304_4_4_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202304_4_4_0/|
    |202305   |202305_5_5_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202305_5_5_0/|
    |202306   |202306_6_8_1|Compact  |6   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202306_6_8_1/|
    +---------+------------+---------+----+-------------------------------------------------------------------------------------+

     

    그래서 모든 Merge Operation 이 마무리된 Merge Table 은 Partition 의 갯수만큼 물리적인 파일들이 생성됩니다.

    name.

    name 은 Part 의 이름입니다.

    Part 의 이름은 Part 의 특징을 나타내는데요.

    "PartitionName - minimum data block - maximum data block - Merge Level" 의 포맷을 취합니다.

    포맷의 표현이 어렵기 때문에 몇가지 예시를 작성해보겠습니다.

    PartitionName 은 202301 과 같은 Partition 기준값을 뜻합니다.

    그리고 data block 은 각 Part 가 구성하는 Block 의 넘버를 뜻하는데요.

    Insert Query 로 인해서 하나의 Part 가 생성될 때에 Part 는 자신의 Block Number 를 가집니다.

     

    아래의 2개의 Insert Query 를 실행합니다.

    insert into default.test_parts (id, data, date)
    values (100, 'test100', '2024-01-01');
    insert into default.test_parts (id, data, date)
    values (101, 'test101', '2024-01-01');

     

    그리고 system.parts 를 확인해보면 "202401_9_9_0", "202401_10_10_0" Parts 가 생성됩니다.

    이때에 9와 10 은 이 Part 가 가지는 Block 의 Numbering 에 해당합니다.

    즉 9번 Block 과 10번 Block 이 생성되었다는 뜻입니다.

    +---------+--------------+---------+----+---------------------------------------------------------------------------------------+
    |partition|name          |part_type|rows|path                                                                                   |
    +---------+--------------+---------+----+---------------------------------------------------------------------------------------+
    |202301   |202301_1_1_0  |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202301_1_1_0/  |
    |202302   |202302_2_2_0  |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202302_2_2_0/  |
    |202303   |202303_3_3_0  |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202303_3_3_0/  |
    |202304   |202304_4_4_0  |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202304_4_4_0/  |
    |202305   |202305_5_5_0  |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202305_5_5_0/  |
    |202306   |202306_6_8_1  |Compact  |6   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202306_6_8_1/  |
    |202401   |202401_9_9_0  |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202401_9_9_0/  |
    |202401   |202401_10_10_0|Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202401_10_10_0/|
    +---------+--------------+---------+----+---------------------------------------------------------------------------------------+

     

    그리고 이들을 Merge 되면 아래와 같이 202401 Partition2024-9-10-1 과 같은 이름의 Part 가 됩니다.

    OPTIMIZE TABLE default.test_parts PARTITION 202401;
    +---------+-------------+---------+----+--------------------------------------------------------------------------------------+
    |partition|name         |part_type|rows|path                                                                                  |
    +---------+-------------+---------+----+--------------------------------------------------------------------------------------+
    |202301   |202301_1_1_0 |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202301_1_1_0/ |
    |202302   |202302_2_2_0 |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202302_2_2_0/ |
    |202303   |202303_3_3_0 |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202303_3_3_0/ |
    |202304   |202304_4_4_0 |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202304_4_4_0/ |
    |202305   |202305_5_5_0 |Compact  |1   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202305_5_5_0/ |
    |202306   |202306_6_8_1 |Compact  |6   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202306_6_8_1/ |
    |202401   |202401_9_10_1|Compact  |2   |/bitnami/clickhouse/data/store/215/215ce9aa-f181-4c1c-8a99-3baf3e5117c2/202401_9_10_1/|
    +---------+-------------+---------+----+--------------------------------------------------------------------------------------+

     

     

    index_granularity.

    클릭하우스의 MergeTree Table 은 Primary Index 를 가집니다.

    클릭하우스의 Primary Index 는 여타 데이터베이스와 달리 개별적인 Row 에 대해서 인덱싱을 수행하지 않습니다.

    대신 모든 Row 를 일일이 인덱싱하지 않고 Granule 이라는 단위별로 Index 를 적용하게 되죠.

    Granule 은 보통 8192 개의 Row 들을 묶는 단위인데요.

    그래서 클릭하우스의 Primary Index 는 8192 개의 Row 로 구성된 Granule 에게 인덱싱을 적용합니다.

    이는 Row 들을 듬성듬성하게 인덱싱한다고 하여 Sparse Index 라고도 부릅니다.

     

    index_granularity 는 Granule 이 몇개의 Row 를 하나의 단위로 설정할지에 대한 설정입니다.

    일반적으로 index_granularity 는 8192 를 기본적으로 권장합니다.

    하지만 이는 변경 가능한 설정이기도 합니다.

    MergeTree Table 을 정의할 때에 index_granularity 를 설정할 수 있죠.

    예시는 아래와 같습니다.

    CREATE TABLE default.test_table
    (
        `id`   UInt32,
        `date` DATETIME
    )
    ENGINE MergeTree()
    PARTITION BY toYYYYMM(date)
    ORDER BY tuple(date)
    SETTINGS index_granularity = 8192

     

    index_granularity_bytes.

    index_granularity 설정은 하나의 Granule 에 속할 수 있는 Row 의 갯수 제한입니다.

    반면 index_granularity_bytes 는 하나의 Granule 의 bytes 크기를 제한할 수 있는 설정입니다.

    만약 index_granularity 로 설정된 8192 보다 적은 갯수의 Row 일지언정 index_granularity_bytes 사이즈를 넘게된다면

    추가적인 Row 들이 하나의 Granule 에 포함되는 것이 제한됩니다.

    index_granularity_bytes 설정은 아래와 같이 설정될 수 있으며, 0 으로 설정하게 되면 제한을 적용하지 않음을 뜻합니다.

    설정하지 않게된다면 기본값은 10Mb 로 적용됩니다.

    CREATE TABLE default.test_table
    (
        `id`   UInt32,
        `date` DATETIME
    )
    ENGINE MergeTree()
    PARTITION BY toYYYYMM(date)
    ORDER BY tuple(date)
    SETTINGS index_granularity = 8192,
    index_granularity_bytes = 0

     

    반응형
Designed by Tistory.