Skip to main content

설명

CollapsingMergeTree 엔진은 MergeTree를 상속하고, 머지 과정에서 행을 축약하는 로직을 추가합니다. CollapsingMergeTree 테이블 엔진은 비동기적으로 정렬 키(ORDER BY)의 모든 필드가 특수 필드 Sign을 제외하고 동일하며 Sign 값만 1 또는 -1로 서로 반대인 행 쌍을 삭제(축약)합니다. 반대 값의 Sign을 가진 짝이 없는 행은 유지됩니다. 자세한 내용은 문서의 축약 섹션을 참조하십시오.
이 엔진은 저장 공간 사용량을 크게 줄일 수 있으며, 그 결과 SELECT 쿼리의 효율을 높일 수 있습니다.

매개변수

Sign 매개변수를 제외한 이 테이블 엔진의 모든 매개변수는 MergeTree와 동일한 의미를 갖습니다.
  • Sign1은 “state” 행, -1은 “cancel” 행을 나타내는 행 유형용 컬럼 이름입니다. 유형: Int8.

테이블 생성

  • 쿼리 매개변수에 대한 설명은 쿼리 설명을 참조하십시오.
  • CollapsingMergeTree 테이블을 생성할 때는 MergeTree 테이블을 생성할 때와 동일한 쿼리 절이 필요합니다.

축약

데이터

주어진 객체에 대해 계속해서 변경되는 데이터를 저장해야 하는 상황을 생각해 보겠습니다. 객체마다 행 하나를 두고 변경이 있을 때마다 업데이트하는 방식이 논리적으로 보일 수 있지만, 업데이트 작업은 저장소의 데이터를 다시 써야 하므로 DBMS에서는 비용이 많이 들고 처리 속도도 느립니다. 데이터를 빠르게 기록해야 한다면 대량의 업데이트를 수행하는 방식은 적절하지 않지만, 객체의 변경 사항을 순차적으로 기록하는 것은 언제나 가능합니다. 이를 위해 특수한 컬럼 Sign을 사용합니다.
  • Sign = 1이면 해당 행은 “state” 행을 의미합니다: 현재 유효한 상태를 나타내는 필드를 포함한 행.
  • Sign = -1이면 해당 행은 “cancel” 행을 의미합니다: 같은 속성을 가진 객체의 상태를 취소하는 데 사용하는 행.
예를 들어, 어떤 웹사이트에서 사용자가 몇 개의 페이지를 조회했고 각 페이지에 얼마나 오래 머물렀는지 계산하려고 합니다. 특정 시점에 다음과 같이 사용자 활동 상태를 나타내는 행을 기록합니다:
이후 시점에 사용자 활동의 변화를 기록하고, 이를 다음 두 개의 행으로 기록합니다:
첫 번째 행은 객체의 이전 상태를 취소합니다(이 경우 사용자). Sign을 제외한 “취소된” 행의 모든 정렬 키(sorting key) 필드를 복사해야 합니다. 위의 두 번째 행에는 현재 상태가 들어 있습니다. 사용자 활동의 마지막 상태만 필요하므로, 아래와 같이 원래의 “state” 행과 삽입한 “cancel” 행을 삭제하여 객체의 유효하지 않은(이전) 상태를 축약할 수 있습니다:
CollapsingMergeTree는 데이터 파트가 머지되는 동안 바로 이러한 축약 동작을 수행합니다.
각 변경마다 왜 두 개의 행이 필요한지는 알고리즘 단락에서 더 자세히 설명합니다.
이러한 접근 방식의 특성
  1. 데이터를 쓰는 프로그램은 객체를 취소할 수 있도록 해당 객체의 상태를 기억해야 합니다. “cancel” 행에는 “state”의 정렬 키 필드 복사본과 반대 Sign 값이 포함되어야 합니다. 이렇게 하면 초기 저장 크기는 늘어나지만 데이터를 빠르게 쓸 수 있습니다.
  2. 컬럼에서 계속 길어지는 배열은 쓰기 부하를 증가시켜 엔진의 효율을 떨어뜨립니다. 데이터가 단순할수록 효율은 높아집니다.
  3. SELECT 결과는 객체 변경 이력의 일관성에 크게 좌우됩니다. 삽입할 데이터를 준비할 때는 주의해야 합니다. 데이터가 일관되지 않으면 예측할 수 없는 결과가 나올 수 있습니다. 예를 들어 session depth와 같이 음수가 될 수 없는 메트릭에 음수 값이 생길 수 있습니다.

알고리즘

ClickHouse가 데이터 파트를 머지할 때, 같은 정렬 키(ORDER BY)를 가진 연속된 각 행 그룹은 최대 2개의 행으로 축약됩니다. 즉, Sign = 1인 “state” 행과 Sign = -1인 “cancel” 행입니다. 다시 말해, ClickHouse에서는 항목이 축약됩니다. 각 결과 데이터 파트에 대해 ClickHouse는 다음을 저장합니다. 또한 “state” 행이 “cancel” 행보다 2개 이상 많거나, “cancel” 행이 “state” 행보다 2개 이상 많으면 머지가 계속 진행됩니다. 하지만 ClickHouse는 이 상황을 논리적 오류로 간주하고 서버 로그에 기록합니다. 이 오류는 동일한 데이터가 두 번 이상 삽입되면 발생할 수 있습니다. 따라서 축약은 통계 계산 결과를 바꾸지 않아야 합니다. 변경 사항은 점진적으로 축약되며, 결국 거의 모든 객체에 대해 마지막 상태만 남게 됩니다. 머지 알고리즘은 같은 정렬 키를 가진 모든 행이 동일한 결과 데이터 파트에, 나아가 동일한 물리적 서버에 있게 된다고 보장하지 않으므로 Sign 컬럼이 필요합니다. ClickHouse는 여러 스레드로 SELECT 쿼리를 처리하므로 결과에서 행의 순서를 예측할 수 없습니다. CollapsingMergeTree 테이블에서 완전히 “축약된” 데이터를 얻어야 한다면 집계가 필요합니다. 축약을 완료하려면 부호를 고려하는 GROUP BY 절과 집계 함수를 사용해 쿼리를 작성하십시오. 예를 들어 수량을 계산할 때는 count() 대신 sum(Sign)를 사용하십시오. 어떤 값의 합계를 계산할 때는 아래 예시와 같이 sum(x) 대신 HAVING sum(Sign) > 0와 함께 sum(Sign * x)를 사용하십시오. 집계 count, sum, avg는 이런 방식으로 계산할 수 있습니다. 객체에 축약되지 않은 상태가 하나 이상 있으면 집계 uniq도 계산할 수 있습니다. 하지만 집계 minmax는 계산할 수 없습니다. CollapsingMergeTree는 축약된 상태의 이력을 저장하지 않기 때문입니다.
집계 없이 데이터를 추출해야 하는 경우 (예를 들어, 최신 값이 특정 조건과 일치하는 행이 있는지 확인하는 경우), FROM 절에 FINAL 수정자를 사용할 수 있습니다. 이 수정자를 사용하면 결과를 반환하기 전에 데이터를 머지합니다. CollapsingMergeTree의 경우 각 키에 대해 가장 최신의 state 행만 반환됩니다.

예시

사용 예시

다음 예시 데이터가 있다고 가정합니다:
CollapsingMergeTree를 사용해 테이블 UAct를 생성해 보겠습니다:
다음으로 일부 데이터를 삽입하겠습니다:
서로 다른 2개의 데이터 파트를 만들기 위해 INSERT 쿼리 2개를 사용합니다.
데이터를 단일 쿼리로 삽입하면 ClickHouse는 데이터 파트를 1개만 생성하며, 이후에는 머지를 수행하지 않습니다.
다음과 같이 데이터를 조회할 수 있습니다:
위에서 반환된 데이터를 살펴보면서 축약이 발생했는지 확인해 보겠습니다… 두 번의 INSERT 쿼리로 두 개의 데이터 파트를 만들었습니다. SELECT 쿼리는 두 개의 스레드에서 수행되었으며, 그 결과 행이 무작위 순서로 반환되었습니다. 하지만 아직 데이터 파트가 머지되지 않았기 때문에 축약은 발생하지 않았습니다. 또한 ClickHouse는 예측할 수 없는 시점에 백그라운드에서 데이터 파트를 머지합니다. 따라서 집계가 필요합니다. 이는 sum 집계 함수와 HAVING 절을 사용해 수행합니다:
집계가 필요 없고 축약을 강제로 적용하려면 FROM 절에 FINAL 수정자를 사용할 수도 있습니다.
이와 같은 데이터 선택 방식은 효율이 떨어지므로, 스캔하는 데이터가 많은 경우(수백만 행)에는 사용하지 않는 것이 좋습니다.

또 다른 접근 방식의 예시

이 접근 방식의 핵심은 머지 작업이 키 필드만 고려한다는 점입니다. 따라서 “cancel” 행에서는 Sign 컬럼을 사용하지 않고 합계를 계산할 때 이전 버전의 행이 상쇄되도록 음수 값을 지정할 수 있습니다. 이 예시에서는 아래의 샘플 데이터를 사용합니다:
이 방식에서는 음수 값을 저장할 수 있도록 PageViewsDuration의 데이터 타입을 변경해야 합니다. 따라서 collapsingMergeTree를 사용해 테이블 UAct를 생성할 때 이 컬럼들의 데이터 타입을 UInt8에서 Int16으로 변경합니다.
테이블에 데이터를 삽입해 이 접근 방식을 테스트해 보겠습니다. 다만 예시나 작은 테이블에서는 이렇게 해도 괜찮습니다:
마지막 수정일 2026년 7월 23일