ClickHouse의 파트 병합이란 무엇인가요?
ClickHouse는 스토리지 계층(storage layer)이 LSM 트리와 비슷하게 동작하기 때문에, 쿼리뿐 아니라 삽입도 빠릅니다: ① MergeTree 엔진 계열 테이블에 데이터를 삽입하면 정렬된 불변의 데이터 파트가 생성됩니다. ② 모든 데이터 처리는 백그라운드 파트 병합으로 넘겨집니다. 이 방식 덕분에 데이터 쓰기 작업의 부담이 적고 매우 효율적입니다. 테이블당 파트 수를 제어하고 위 ②를 구현하기 위해, ClickHouse는 더 작은 파트를 백그라운드에서 지속적으로 (파티션별로) 더 큰 파트로 병합하며, 압축된 크기가 약 ~150 GB에 이를 때까지 이를 계속합니다. 다음 다이어그램은 이 백그라운드 병합 과정을 개략적으로 보여줍니다:
파트의
merge level은 머지가 한 번 추가로 일어날 때마다 1씩 증가합니다. 레벨이 0이면 해당 파트는 새로 생성되었으며 아직 병합되지 않았다는 뜻입니다. 더 큰 파트로 병합된 파트는 비활성 상태로 표시되며, 설정 가능한 시간이 지난 뒤 최종적으로 삭제됩니다(기본값은 8분). 시간이 지나면서 이렇게 병합된 파트의 트리가 만들어집니다. 그래서 merge tree 테이블이라는 이름이 붙었습니다.
머지 모니터링
/merges HTTP handler를 통해 사용할 수 있으며, 이를 이용해 예시 테이블의 모든 파트 병합을 시각화할 수 있습니다:
위의 대시보드 녹화 화면은 초기 데이터 삽입부터 최종적으로 하나의 파트로 머지되기까지의 전체 과정을 보여줍니다: ① 활성 파트 수. ② 박스로 시각화한 파트 병합(상자 크기는 파트 크기를 반영). ③ 쓰기 증폭.
동시 파트 병합
각 병합 스레드는 다음과 같은 루프를 수행합니다: ① 다음에 병합할 파트를 결정하고, 해당 파트를 메모리에 로드합니다. ② 메모리에 로드된 파트들을 더 큰 파트로 병합합니다. ③ 병합된 파트를 디스크에 기록합니다. ①로 돌아갑니다 CPU 코어 수와 RAM 용량을 늘리면 백그라운드 병합 처리량을 높일 수 있습니다.
메모리 최적화된 머지
머지 메커니즘
파트 병합은 여러 단계에 걸쳐 수행됩니다: ① 압축 해제 및 로드: 머지할 파트의 압축된 바이너리 컬럼 파일을 압축 해제해 메모리에 로드합니다. ② 머지: 데이터를 더 큰 컬럼 파일로 머지합니다. ③ 인덱싱: 머지된 컬럼 파일에 대해 새로운 희소 프라이머리 인덱스를 생성합니다. ④ 압축 및 저장: 새 컬럼 파일과 인덱스를 압축한 뒤, 머지된 데이터 파트를 나타내는 새 디렉터리에 저장합니다. 보조 데이터 스키핑 인덱스, 컬럼 통계, 체크섬, MinMax 인덱스와 같은 데이터 파트의 추가 메타데이터도 머지된 컬럼 파일을 기반으로 다시 생성됩니다. 여기서는 단순화를 위해 이러한 세부 사항을 생략했습니다. ② 단계의 구체적인 동작 방식은 사용 중인 MergeTree 엔진에 따라 달라집니다. 엔진마다 머지를 처리하는 방식이 서로 다르기 때문입니다. 예를 들어, 행이 집계되거나 오래된 경우 대체될 수 있습니다. 앞서 언급했듯이, 이 접근 방식은 모든 데이터 처리를 백그라운드 머지로 오프로드하므로 쓰기 작업을 가볍고 효율적으로 유지할 수 있어 매우 빠른 삽입이 가능합니다. 다음으로, MergeTree 엔진 계열의 특정 엔진별 머지 메커니즘을 간략히 살펴보겠습니다.
표준 병합
위 다이어그램의 DDL 문은 정렬 키
(town, street)를 가진 MergeTree 테이블을 생성하며, 즉 디스크의 데이터는 이 컬럼을 기준으로 정렬되고 이에 따라 희소 프라이머리 인덱스가 생성됩니다.
① 압축이 해제된 사전 정렬 테이블 컬럼을 ② 테이블의 정렬 키로 정의된 전역 정렬 순서를 유지한 채 머지하고, ③ 새로운 희소 프라이머리 인덱스를 생성한 다음, ④ 머지된 컬럼 파일과 인덱스를 압축해 디스크에 새 데이터 파트로 저장합니다.
Replacing 머지
위 다이어그램의 DDL 문은 정렬 키가
(town, street, id)인 ReplacingMergeTree 테이블을 생성합니다. 즉, 디스크에 저장된 데이터는 이 컬럼들을 기준으로 정렬되며, 이에 따라 희소 프라이머리 인덱스(sparse primary index)도 생성됩니다.
② 머지 작업은 표준 MergeTree 테이블과 비슷하게 동작하며, 전역 정렬 순서를 유지하면서 압축 해제된 미리 정렬된 컬럼을 결합합니다.
하지만 ReplacingMergeTree는 동일한 정렬 키를 가진 중복 행을 제거하고, 해당 행이 포함된 파트의 생성 타임스탬프를 기준으로 가장 최근 행만 유지합니다.
합산 머지
위 다이어그램의 DDL 구문은
town을 정렬 키(sorting key)로 사용하는 SummingMergeTree 테이블을 정의합니다. 즉, 디스크의 데이터가 이 컬럼을 기준으로 정렬되며, 이에 따라 희소 프라이머리 인덱스가 생성됩니다.
② 머지 단계에서 ClickHouse는 동일한 정렬 키를 가진 모든 행을 하나의 행으로 합치고, 숫자 컬럼의 값을 합산합니다.
집계 머지
SummingMergeTree 테이블 예시는 AggregatingMergeTree 테이블의 특수한 변형으로, 파트 병합 중 90+개의 집계 함수를 적용해 자동 증분 데이터 변환을 수행할 수 있습니다:
위 다이어그램의 DDL 문은
town을 정렬 키로 사용하는 AggregatingMergeTree 테이블을 생성합니다. 이렇게 하면 디스크에서 데이터가 이 컬럼 기준으로 정렬되고, 이에 대응하는 희소 프라이머리 인덱스가 생성됩니다.
② 머지 중 ClickHouse는 동일한 정렬 키를 가진 모든 행을 부분 집계 상태를 저장하는 단일 행으로 대체합니다(예: avg()를 위한 sum과 count). 이러한 상태를 통해 증분 백그라운드 머지에서도 정확한 결과를 보장합니다.