메인 콘텐츠로 건너뛰기
업데이트를 처리하는 방식은 기본 설계 철학과 대상 사용 사례가 다르기 때문에 분석용 데이터베이스와 트랜잭션 데이터베이스마다 서로 다릅니다. ClickHouse는 컬럼 지향 데이터베이스로, 읽기 중심 분석과 고처리량 append-only 작업에 최적화되어 있습니다. 실제로는 삭제와 업데이트를 append 작업으로 전환할 수 있도록 테이블을 재구성하는 경우가 많으며, 이러한 작업은 비동기적으로 처리되거나 읽기 시점에 처리됩니다. 이를 통해 고처리량 데이터 수집에서 ClickHouse의 강점을 활용할 수 있습니다. ClickHouse는 강력한 업데이트 및 삭제 작업도 지원합니다. 이 가이드는 ClickHouse에서 사용할 수 있는 업데이트 메서드를 개괄적으로 설명하고, 워크로드에 적합한 업데이트 전략을 선택하는 데 도움을 줍니다.

업데이트 전략 선택

ClickHouse에서 데이터를 업데이트하는 기본 접근 방식은 두 가지입니다.
  1. 삽입을 통해 업데이트를 처리하는 특수 테이블 엔진 사용
  2. UPDATE ... SET 또는 ALTER TABLE ... UPDATE SQL 문과 같은 선언적 업데이트 사용
위 두 범주 안에는 데이터를 업데이트하는 여러 방법이 있습니다. 각 방법마다 장점과 성능 특성이 다르므로, 데이터 모델과 업데이트하려는 데이터 양에 따라 적절한 방법을 선택해야 합니다.

특수한 테이블 엔진을 사용해야 하는 경우

대량의 업데이트가 있거나, 행 수준 변경이 자주 발생하거나, 업데이트 및 삭제 이벤트의 지속적인 스트림을 처리해야 하는 경우에는 특수 테이블 엔진이 더 적합합니다. 일반적으로 자주 접하게 되는 엔진은 다음과 같습니다.
EngineSyntaxWhen to use
ReplacingMergeTreeENGINE = ReplacingMergeTree대량의 데이터를 업데이트할 때 사용합니다. 이 테이블 엔진은 머지 중 데이터 중복 제거에 최적화되어 있습니다.
CoalescingMergeTreeENGINE = CoalescingMergeTree데이터가 조각난 형태로 유입되고, 전체 행 교체가 아니라 컬럼 단위 coalescing이 필요한 경우 사용합니다.
CollapsingMergeTreeENGINE = CollapsingMergeTree(Sign)개별 행이 자주 업데이트되거나, 시간에 따라 변하는 객체의 최신 상태를 유지해야 하는 시나리오에 사용합니다. 예를 들어 사용자 활동이나 게시물 통계를 추적할 때 유용합니다.
MergeTree 계열의 테이블 엔진은 백그라운드에서 데이터 파트를 머지하므로 _최종 일관성(eventual consistency)_을 제공합니다. 따라서 테이블을 쿼리하는 동안 중간 단계에서도 올바르게 중복 제거되도록 하려면 FINAL 키워드를 사용해야 합니다. 다른 엔진 종류도 있지만, 여기 소개한 엔진이 가장 일반적으로 사용됩니다.

선언적 업데이트를 사용하는 경우

선언적 UPDATE SQL 문은 중복 제거 로직을 관리하는 복잡성 없이 단순한 업데이트 작업을 더 쉽게 수행할 수 있습니다. 다만 일반적으로는 특수 엔진에 비해, 더 적은 수의 행을 덜 자주 업데이트하는 경우에 더 적합합니다.
메서드구문사용 시점
경량 업데이트UPDATE [table] SET ... WHERE대부분의 시나리오에서는 이를 사용하십시오. 특히 애플리케이션이나 워크플로의 일부로 빈번하게 작은 규모의 UPDATE(테이블의 약 10% 이하)를 수행할 때 적합합니다. 예를 들어, 사용자가 자신의 이벤트 이력을 삭제하려고 하는데 이벤트가 많은 사용자가 공유하는 멀티테넌트 테이블 전반에 분산되어 있는 경우입니다. 이 방식은 전체 컬럼을 다시 쓰지 않고도 즉시 반영된 결과를 볼 수 있도록 패치 파트를 생성합니다. SELECT 쿼리에 오버헤드가 추가되지만 지연 시간은 예측 가능합니다.
Update mutationALTER TABLE [table] UPDATE더 큰 규모의 데이터 관리를 수행할 때 사용하십시오. 특히 업데이트가 테이블 파티셔닝과 잘 맞아떨어질 때 적합합니다. 예를 들어, 월 단위로 파티셔닝된 테이블에서 특정 월에 속한 모든 행의 컬럼 하나를 업데이트해야 하는 경우입니다.

특수 테이블 엔진을 활용한 업데이트

ReplacingMergeTree

ReplacingMergeTree는 백그라운드 머지 과정에서 동일한 정렬 키를 가진 행을 중복 제거하여 최신 버전만 유지합니다.
CREATE TABLE posts
(
    Id UInt32,
    Title String,
    ViewCount UInt32,
    Version UInt32
)
ENGINE = ReplacingMergeTree(Version)
ORDER BY Id
이 엔진은 안정적인 키로 식별되는 개별 행에 빈번하게 업데이트가 발생하는 경우에 적합합니다. 벤치마크에 따르면 단일 행 업데이트에서는 뮤테이션보다 최대 4,700배 더 빠를 수 있습니다. 행을 업데이트하려면 동일한 정렬 키 값과 더 높은 버전 번호를 가진 새 버전을 삽입하면 됩니다. 이전 버전은 백그라운드 머지 중에 제거됩니다. 중복 제거는 즉시 수행되지 않고 머지 중에만 발생하므로, 정확하게 중복 제거된 결과를 얻으려면 FINAL 수정자 또는 이에 상응하는 쿼리 로직을 사용해야 합니다. FINAL 수정자는 데이터에 따라 21~550%의 쿼리 오버헤드를 추가합니다. ReplacingMergeTree는 정렬 키 값을 업데이트할 수 없습니다. 또한 논리적 삭제를 위한 Deleted 컬럼도 지원합니다. 더 알아보기: ReplacingMergeTree 가이드 | ReplacingMergeTree 참고

CoalescingMergeTree

CoalescingMergeTree는 머지 과정에서 각 컬럼의 가장 최근 NULL이 아닌 값을 유지해 희소 레코드를 통합합니다. 이를 통해 전체 행을 교체하는 대신 컬럼 수준의 업서트를 지원합니다.
CREATE TABLE electric_vehicle_state
(
    vin String, -- 차량 식별 번호
    last_update DateTime64 Materialized now64(), -- 선택 사항 (argMax와 함께 사용)
    battery_level Nullable(UInt8), -- % 단위
    lat Nullable(Float64), -- 위도 (°)
    lon Nullable(Float64), -- 경도 (°)
    firmware_version Nullable(String),
    cabin_temperature Nullable(Float32), -- °C 단위
    speed_kmh Nullable(Float32) -- 센서 측정값
)
ENGINE = CoalescingMergeTree
ORDER BY vin;
이 엔진은 데이터가 여러 소스에서 부분적으로 들어오거나, 서로 다른 컬럼이 서로 다른 시점에 채워지는 시나리오를 위해 설계되었습니다. 대표적인 사용 사례로는 분산된 하위 시스템에서 수집되는 IoT 텔레메트리, 사용자 프로필 보강, 그리고 차원 데이터가 지연 도착하는 ETL 파이프라인이 있습니다. 동일한 정렬 키를 가진 행들이 머지되면, CoalescingMergeTree는 전체 행을 대체하는 대신 각 컬럼에서 가장 최신의 NULL이 아닌 값을 유지합니다. 이 기능이 의도대로 동작하려면 키가 아닌 컬럼은 Nullable이어야 합니다. ReplacingMergeTree와 마찬가지로, 올바르게 병합된 결과를 얻으려면 FINAL을 사용하십시오. 이 엔진은 ClickHouse 25.6부터 사용할 수 있습니다. 자세히 보기: CoalescingMergeTree

CollapsingMergeTree

업데이트는 비용이 많이 들지만 삽입을 활용해 업데이트를 수행할 수 있다는 아이디어를 바탕으로, CollapsingMergeTree는 머지 중 행을 어떻게 처리할지 ClickHouse에 알리기 위해 Sign 컬럼을 사용합니다. Sign 컬럼에 -1이 삽입되면 해당 행은 대응되는 +1 행과 짝을 이룰 때 축약되어 삭제됩니다. 업데이트할 행은 테이블 생성 시 ORDER BY 절에서 사용한 정렬 키를 기준으로 식별됩니다.
CREATE TABLE user_activity
(
    UserID UInt64,
    PageViews UInt8,
    Duration UInt8,
    Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID

-- 초기 상태
INSERT INTO user_activity VALUES (4324182021466249494, 5, 146, 1)

-- 이전 행 취소 및 새 상태 삽입
INSERT INTO user_activity VALUES (4324182021466249494, 5, 146, -1)
INSERT INTO user_activity VALUES (4324182021466249494, 6, 185, 1)

-- 올바른 집계를 사용한 쿼리
SELECT
    UserID,
    sum(PageViews * Sign) AS PageViews,
    sum(Duration * Sign) AS Duration
FROM user_activity
GROUP BY UserID
HAVING sum(Sign) > 0
┌──────────────UserID─┬─PageViews─┬─Duration─┐
│ 4324182021466249494 │         6 │      185 │
└─────────────────────┴───────────┴──────────┘
ReplacingMergeTree와 달리 CollapsingMergeTree는 정렬 키 값을 수정할 수 있습니다. 금융 거래나 게임 상태 추적처럼 취소 semantics가 있는 되돌릴 수 있는 작업에 적합합니다.
위의 업데이트 방식에서는 취소 행을 삽입하기 위해 애플리케이션이 클라이언트 측에서 상태를 유지해야 합니다. 이 방식은 ClickHouse 관점에서 가장 효율적이지만, 대규모 환경에서는 다루기가 복잡할 수 있습니다. 또한 올바른 결과를 얻으려면 쿼리에서도 부호 곱셈을 포함한 집계가 필요합니다.
자세히 보기: CollapsingMergeTree

선언적 업데이트

다음 방법은 MergeTree 엔진 계열 엔진을 사용하는 테이블에서 사용할 수 있습니다.
방법구문적합한 경우장단점
뮤테이션ALTER TABLE ... UPDATE드물게 수행하는 대량 업데이트, 특히 업데이트가 테이블 파티셔닝과 맞아떨어질 때 적합I/O 부담이 큼; 컬럼을 재작성
경량 업데이트UPDATE ... SET ... WHERE소규모 업데이트(행의 약 0.1~10%); 성능이 중요한 빈번한 업데이트SELECT 오버헤드가 추가됨; 패치 파트도 제한 수에 포함됨

뮤테이션

뮤테이션 (ALTER TABLE ... UPDATE)은 WHERE 표현식에 일치하는 행이 포함된 모든 파트를 재작성합니다.
ALTER TABLE posts UPDATE AnswerCount = AnswerCount + 1 WHERE AnswerCount = 0
뮤테이션은 WHERE 표현식과 일치하는 모든 파트를 다시 써야 하므로 I/O 부하가 큽니다. 이 과정에는 원자성이 없습니다. 파트는 준비되는 즉시 뮤테이션된 파트로 교체되므로, 뮤테이션이 진행되는 동안 실행을 시작한 SELECT 쿼리에서는 이미 뮤테이션된 파트의 데이터와 아직 뮤테이션되지 않은 파트의 데이터가 함께 조회될 수 있습니다. 진행 상태는 system.mutations 테이블에서 확인할 수 있습니다.
뮤테이션은 I/O 집약적이므로 클러스터의 SELECT 성능에 영향을 줄 수 있어 꼭 필요한 경우에만 사용해야 합니다. 뮤테이션이 처리되는 속도보다 더 빠르게 큐에 쌓이면 쿼리 성능이 저하됩니다. system.mutations를 통해 큐를 모니터링하세요.
자세히 보기: ALTER TABLE UPDATE

온더플라이 뮤테이션

ALTER TABLE ... UPDATE를 통한 뮤테이션은 변경된 값이 쿼리에 반영되기까지 백그라운드 프로세스에서 뮤테이션이 적용될 때까지 기다려야 할 수 있습니다. ClickHouse는 “온더플라이 뮤테이션”을 통해 이 동작을 바꿀 수 있는 방법을 제공합니다. 온더플라이 뮤테이션이 활성화되면 업데이트된 행은 즉시 업데이트된 것으로 표시되며, 이후 SELECT 쿼리는 변경된 값을 자동으로 반환합니다. 온더플라이 뮤테이션은 쿼리 수준 설정인 apply_mutations_on_fly를 활성화하면 MergeTree 계열 테이블에서 사용할 수 있습니다.
SET apply_mutations_on_fly = 1;
테이블을 생성하고 몇 가지 뮤테이션을 실행해 보겠습니다.
CREATE TABLE test_on_fly_mutations (id UInt64, v String)
ENGINE = MergeTree ORDER BY id;

-- 예시를 위해 뮤테이션의 백그라운드 머티리얼라이즈를 비활성화합니다
-- 온더플라이 뮤테이션이 활성화되지 않았을 때의 기본 동작을 보여줍니다
SYSTEM STOP MERGES test_on_fly_mutations;
SET mutations_sync = 0;

-- 새 테이블에 몇 개의 행을 삽입합니다
INSERT INTO test_on_fly_mutations VALUES (1, 'a'), (2, 'b'), (3, 'c');

-- 행 값을 업데이트합니다
ALTER TABLE test_on_fly_mutations UPDATE v = 'd' WHERE id = 1;
ALTER TABLE test_on_fly_mutations DELETE WHERE v = 'd';
ALTER TABLE test_on_fly_mutations UPDATE v = 'e' WHERE id = 2;
ALTER TABLE test_on_fly_mutations DELETE WHERE v = 'e';
SELECT 쿼리로 업데이트 결과를 확인해 보겠습니다.
-- 온더플라이 뮤테이션을 명시적으로 비활성화합니다
SET apply_mutations_on_fly = 0;

SELECT id, v FROM test_on_fly_mutations ORDER BY id;
새 테이블에 쿼리해도 아직은 행 값이 업데이트되지 않았음을 확인할 수 있습니다.
┌─id─┬─v─┐
│  1 │ a │
│  2 │ b │
│  3 │ c │
└────┴───┘
이제 온더플라이 뮤테이션을 활성화했을 때 어떻게 되는지 살펴보겠습니다.
-- 온더플라이 뮤테이션을 활성화합니다
SET apply_mutations_on_fly = 1;

SELECT id, v FROM test_on_fly_mutations ORDER BY id;
이제 SELECT 쿼리는 뮤테이션이 적용될 때까지 기다리지 않고도 즉시 올바른 결과를 반환합니다.
┌─id─┬─v─┐
│  3 │ c │
└────┴───┘
성능 영향
온더플라이 뮤테이션이 활성화되면 뮤테이션은 즉시 구체화되지 않고 SELECT 쿼리에서만 적용됩니다. 다만 뮤테이션은 여전히 백그라운드에서 비동기적으로 구체화되며, 이 과정은 부하가 큰 작업이라는 점에 유의하십시오. 일정 시간 동안 등록되는 뮤테이션 수가 백그라운드에서 처리되는 뮤테이션 수를 지속적으로 초과하면, 적용해야 할 미구체화 뮤테이션의 큐가 계속 증가합니다. 그 결과 결국 SELECT 쿼리 성능이 저하됩니다. 미구체화 뮤테이션이 무한정 증가하는 것을 제한하려면 apply_mutations_on_fly 설정을 number_of_mutations_to_throw, number_of_mutations_to_delay와 같은 다른 MergeTree 수준의 설정과 함께 활성화하는 것이 좋습니다.
서브쿼리 및 비결정적 함수 지원
온더플라이 뮤테이션은 서브쿼리 및 비결정적 함수에 대해 제한적으로만 지원합니다. 결과 크기가 적절한 스칼라 서브쿼리만 지원하며, 이는 설정 mutations_max_literal_size_to_replace로 제어됩니다. 비결정적 함수는 상수인 경우에만 지원됩니다(예: 함수 now()). 이 동작은 다음 설정으로 제어됩니다:
설정설명기본값
mutations_execute_nondeterministic_on_initiatortrue이면 비결정적 함수가 initiator 레플리카에서 실행되고 UPDATEDELETE 쿼리에서는 리터럴로 대체됩니다.false
mutations_execute_subqueries_on_initiatortrue이면 스칼라 서브쿼리가 initiator 레플리카에서 실행되고 UPDATEDELETE 쿼리에서는 리터럴로 대체됩니다.false
mutations_max_literal_size_to_replaceUPDATEDELETE 쿼리에서 대체할 직렬화된 리터럴의 최대 크기(바이트)입니다.16384 (16 KiB)

경량 업데이트

경량 업데이트는 기존 뮤테이션처럼 전체 컬럼을 재작성하는 대신, 업데이트된 컬럼과 행만 포함하는 특수한 데이터 파트인 “패치 파트”를 사용합니다.
UPDATE posts SET AnswerCount = AnswerCount + 1 WHERE Id = 404346
이 방식은 표준 UPDATE 구문을 사용하며, 머지를 기다리지 않고 즉시 패치 파트를 생성합니다. 업데이트된 값은 패치 적용을 통해 SELECT 쿼리에서 바로 확인할 수 있지만, 물리적으로는 이후 머지 과정에서만 구체화됩니다. 따라서 경량 업데이트는 예측 가능한 지연 시간으로 적은 비율의 행(테이블의 약 10% 이하)을 업데이트하는 경우에 적합합니다. 벤치마크 결과, 뮤테이션보다 최대 23배 더 빠를 수 있습니다. 반면, 패치를 적용할 때 SELECT 쿼리에 오버헤드가 발생하며, 패치 파트도 파트 제한 수에 포함됩니다. 약 10%의 임계값을 넘으면 읽기 시 패치 적용 오버헤드가 비례해 증가하므로, 더 큰 규모의 업데이트에는 동기식 뮤테이션이 더 효율적입니다. 자세히 보기: Lightweight UPDATE

온더플라이 뮤테이션

온더플라이 뮤테이션은 행을 업데이트할 때 백그라운드 처리가 끝날 때까지 기다리지 않아도, 이후 SELECT 쿼리에서 변경된 값을 자동으로 반환할 수 있게 해줍니다. 이는 일반 뮤테이션의 원자성 한계를 효과적으로 해결합니다.
SET apply_mutations_on_fly = 1;

SELECT ViewCount FROM posts WHERE Id = 404346
┌─ViewCount─┐
│     26762 │
└───────────┘
-- 카운트 증가
ALTER TABLE posts UPDATE ViewCount = ViewCount + 1 WHERE Id = 404346

-- 업데이트된 값이 즉시 반영됨
SELECT ViewCount FROM posts WHERE Id = 404346
┌─ViewCount─┐
│     26763 │
└───────────┘
뮤테이션과 그에 이어지는 SELECT 쿼리 모두에서 apply_mutations_on_fly = 1 설정이 활성화되어 있어야 합니다. 뮤테이션 조건은 ClickHouse Keeper에 저장되며, Keeper는 모든 것을 메모리에 유지하고 쿼리 실행 중 온더플라이로 이를 적용합니다. 뮤테이션은 여전히 데이터를 업데이트하는 데 사용되며, 단지 즉시 구체화되지 않을 뿐입니다. 뮤테이션은 비동기 프로세스로 백그라운드에서 계속 적용되며, 일반 뮤테이션과 마찬가지로 큰 오버헤드가 발생합니다. 또한 이 작업에 사용할 수 있는 표현식도 제한적입니다(자세한 내용 참조).
온더플라이 뮤테이션은 소수의 작업에만 사용해야 합니다. 많아도 수십 건 정도가 적절합니다. Keeper는 조건을 메모리에 저장하므로 과도하게 사용하면 클러스터 안정성에 영향을 줍니다. Keeper 부하가 높으면 관련 없는 테이블에도 영향을 미치는 세션 timeout이 발생할 수 있습니다.
자세히 보기: 온더플라이 뮤테이션

비교 요약

다음 표는 벤치마크를 바탕으로 쿼리 성능 오버헤드를 요약합니다. 뮤테이션은 완료 후 데이터가 물리적으로 다시 기록되면 쿼리가 최대 속도로 실행되므로 기준선으로 사용됩니다.
방법쿼리 속도 저하메모리 오버헤드참고 사항
뮤테이션기준선기준선완료 후 최대 속도; 데이터가 물리적으로 다시 기록됨
온더플라이 뮤테이션가변적가변적즉시 반영됨; 업데이트가 많이 누적되면 성능이 저하됨
경량 업데이트7–18% (평균 약 12%)+20–210%쿼리 측면에서 가장 효율적임; 테이블의 10% 이하를 업데이트할 때 가장 적합함
ReplacingMergeTree + FINAL21–550% (평균 약 280%)기준선 대비 20–200배모든 행 버전을 읽어야 함; 쿼리 오버헤드가 가장 큼
CoalescingMergeTree + FINALReplacingMergeTree와 유사ReplacingMergeTree와 유사컬럼 수준 coalescing으로 비슷한 수준의 오버헤드가 발생함
CollapsingMergeTree집계에 따라 달라짐집계에 따라 달라짐오버헤드는 쿼리 복잡도에 따라 달라짐

추가 자료

시간이 지나면서 ClickHouse의 업데이트 방식이 어떻게 발전해 왔는지와 벤치마크 분석을 자세히 살펴보고 싶다면 다음 자료를 참조하십시오.
마지막 수정일 2026년 6월 10일