ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ClickHouse] Mutation 알아보기
    Database/Clickhouse 2024. 4. 13. 07:29
    728x90
    반응형

    - 목차

     

    들어가며.

    Mutation 은 테이블의 데이터를 수정하거나 삭제하는 작업을 의미합니다.

    이는 ALTER TABLE ... DELETE 또는 ALTER TABLE ... UPDATE 와 같은 명령어로 실행되며, 백그라운드에서 비동기적으로 처리됩니다.

    MutationMergeTree 엔진을 사용하는 테이블에서 지원되며, 데이터 파트를 새로운 버전으로 재작성하여 변경사항을 적용합니다.

     

    ClickHouse 의 Mutation 은 일반적인 관계형 데이터베이스 관리 시스템(RDBMS)의 DELETEUPDATE 연산과 유사한 동작으로 생각하시면 됩니다.

    하지만 차이점들이 존재합니다.

     

    1. MutationALTER TABLE 과 같이 DDL 형식으로 실행됩니다.
    2. MySQL 과 같은 DB 에서 DELETE, UPDATE 등의 쿼리가 동기적으로 실행되는 반면, Mutation 은 백그라운드에서 실행됩니다.
    3. MutationWHERE 절에 의해서 지정된 모든 Part 파일들에 대해 적용됩니다. 그래서 상당 수의 Disk IO 가 발생할 수 있습니다.

    우선 간단히 위와 같이 정리를 해보았는데요.

    이어지는 내용에서 자세한 설명과 예시 및 실습을 진행해보도록 하겠습니다.

     

    Mutation 이란 ?

    ClickHouse 에서 Mutation은 테이블의 기존 데이터를 수정하거나 삭제하는 작업을 의미합니다.

    ALTER TABLE ... DELETE 또는 ALTER TABLE ... UPDATE 명령어를 통해 수행되며, 이러한 명령어는 테이블의 데이터를 변경하는 데 사용됩니다.

    또한 Mutation 명령어의 진행 상황은 system.mutations 시스템 테이블에서 확인할 수 있습니다.

     

    우선 간단한 MergeTree Table 을 생성하고, Mutation 을 수행해보도록 하겠습니다.

    아래의 Docker 명령어를 통해서 ClickHouse Container 를 실행합니다.

    docker run -d --platform linux/amd64 --name clickhouse clickhouse:24.9.3
    docker exec -it clickhouse bash

     

     

    그리고 아래의 SQL 쿼리들은 Mutation 테스트를 수행할 간단한 예시 쿼리입니다.

    user_events 라는 이름의 MergeTree 엔진의 테이블을 생성합니다.

    toYYYYMM(event_time) 을 기준으로 PARTITION KEY 를 설정하였구요.

    따라서 event_time 의 Month 가 다른 Row 마다 Part 파일이 생성될 겁니다.

    CREATE TABLE user_events (
        event_id UInt32,
        event_time DateTime,
        user_id UInt32,
        event_type String
    ) ENGINE = MergeTree()
    PARTITION BY toYYYYMM(event_time)
    ORDER BY (user_id, event_time);
    INSERT INTO user_events (event_id, event_time, user_id, event_type) VALUES
    (1, '2024-01-11 10:00:00', 101, 'login'),
    (2, '2024-02-11 10:05:00', 102, 'purchase'),
    (3, '2024-03-11 10:10:00', 101, 'logout'),
    (4, '2024-04-11 10:15:00', 103, 'login'),
    (5, '2024-05-11 10:20:00', 104, 'purchase');

     

    위의 Table Create DDL 과 Insert DML 이 모두 실행되면, 생성된 Part File 들은 아래와 같이 5개가 생성됩니다.

    그 이유는 PARTITION BY 로 설정된 event_time 칼럼이 YYYYMM 을 기준으로 파티셔닝되기 때문입니다.

     

    ls -a1 /var/lib/clickhouse/data/default/user_events/

     

    아래의 Query 는 2024년 01월에 해당하는 모든 Row 들을 삭제하는 쿼리입니다.

    ALTER TABLE user_events DELETE 
    WHERE event_time > '2024-01-01 00:00:00' AND event_time <= '2024-01-31 23:59:59';

     

    Mutation 을 실행하기 이전에 system.mutations 의 상태를 조회해보았습니다.

    아래 이미지와 같이 현재는 비어있는 상황입니다.

     

     

    하지만 Delete Mutation 실행 이후에 user_events 의 테이블 상황과 Parts 들의 상태는 아래와 같습니다.

    아래의 이미지처럼 event_time 이 2024-01 에 속하는 Row 는 삭제됩니다.

     

     

    그리고 Parts 들 또한 아래와 같이 변경됩니다.

    아래의 이미지와 같이 _6 Suffix 가 붙은 새로운 Part 들이 생성됩니다.

     

     

    Part 의 Naming Convention 에서 맨 마지막의 숫자는 Data Version 을 의미합니다.

    사실 저의 추측은 아래와 같습니다.

    저는 Partition Key 로 사용된 event_time 칼럼이 2024-01 인 Row 만을 제거하였습니다.

    따라서 202401 에 해당하는 Part 만이 변경되기를 기대하였는데,

    실상은 위 이미지와 같이 모든 Part 에 대해서 새로운 Version 의 Part 들이 생성되었습니다.

     

    많은 양의 Part 와 적은 범위의 Mutataion 실험.

    Part 들이 매우 많고, 극히 일부의 데이터를 삭제하는 Mutation 을 수행합니다.

     

    아래의 Insert Query 는 1억개의 Row 들을 생성하는 Query 입니다.

    INSERT INTO user_events (event_id, event_time, user_id, event_type)
    SELECT
        number AS event_id,
        addSeconds('2024-01-01 00:00:00', rand() % 31536000) AS event_time,
        rand() % 1000 + 1 AS user_id,
        ['login', 'purchase', 'logout'][rand() % 3 + 1] AS event_type
    FROM numbers(100000000);
    ALTER TABLE user_events DELETE 
    WHERE event_time > '2024-01-01 00:00:00' AND event_time <= '2024-01-31 23:59:59';

     

    위 쿼리를 실행하면 아래와 같이 많은 양의 Part 들이 생성됩니다.

     

     

    아래의 이미지는 Delete Mutation 을 실행한 이후의 Part 들의 상태입니다.

    _1081 이라는 Suffix 가 붙은 새로운 Part 들이 생성됩니다.

    Mutation 의 적용 범위와 무관하게 전역적으로 Mutation 이 수행되는 것으로 관찰됩니다.

    ( 뭔가 삭제할 파일을 찾는 최적화가 되지 않는 느낌 ? )

     

     

    아래의 결과는 system.mutations 의 조회 내용입니다.

    select database, table, parts_to_do, parts_to_do_names, block_numbers.number, block_numbers.partition_id 
    from system.mutations format vertical;

     

    • database 와 table : Mutation 이 적용되는 범위
    • parts_to_do: Mutation 이 수행되는 Part 의 갯수를 의미합니다.
      • 위의 케이스에서는 36 개의 Part 가 그 대상이 되며, 새로운 Part 가 만들어집니다.
    • parts_to_do_names: 삭제되어야할 Part 와 새롭게 만들어지는 Part 의 이름들입니다.
      • 아래의 이미지와 같이 Mutation 의 대상이 되는 Part 들은 _1081 와 같이 Data Version 이 붙은 새로운 Part 들로 만들어집니다.
       

     

     

    Update Mutation 알아보기.

     

    이번에는 아래의 Update Mutation 을 통해서 Row 를 업데이트하고, 이에 따른 변경 사항들을 알아보도록 하겠습니다.

    ALTER TABLE user_events
    UPDATE event_type = 'purchase'
    WHERE user_id = 101;

     

    Update Mutation 수행 후, 아래와 같이 user_id: 101 에 해당하는 Row 의 event_type 값이 변경됩니다.

     

     

     

    system.mutations 테이블에서 조회되는 Mutation 기록과 변경된 Part 상태는 아래와 같습니다.

    전반적으로 Delete & Update 의 종류와 관계없니 Mutation 는 동일하게 동작하는 것으로 관찰됩니다.

     

    select database, table, parts_to_do, parts_to_do_names, block_numbers.number, block_numbers.partition_id 
    from system.mutations format vertical;

     

     

    정리하며.

    1. ClickHouse Mutation 은 비동기적으로 실행됩니다.
      1. ALTER TABLE ... 와 같은 DDL 방식으로 Mutation 이 수행됩니다.
      2. 이는 Background 에서 수행되기에 실행 결과를 확인하려면 system.mutations 테이블을 활용해야합니다.
    2. ClickHouse Mutation 은 많은 양의 Part 파일들을 생성하게 됩니다.
      1. 실험의 결과 Mutation 의 Where 문장과 Part 의 범위가 최적의 대응이 되지 않습니다.
      2. 아마도 Background 에서 Optimize Table 과정이 수행되지 않나 추측해봅니다.
      3. 결론적으로 적은 범위의 Mutation 이더라도 많은 양의 Part 들이 수정되기에 퍼포먼스 이슈를 고려해야합니다.

     

     

    반응형
Designed by Tistory.