테이블 데이터 파트

테이블 데이터 파트

MergeTree 엔진 패밀리 테이블의 데이터는 디스크에서 불변(immutable) 데이터 파트(data part) 들의 집합으로 조직돼 있어요. 행 집합이 테이블에 삽입될 때마다 파트 하나가 생깁니다. 파트는 자기 완결적이라 중앙 카탈로그 없이도 내용을 해석할 수 있는 메타데이터를 모두 담고 있죠.

삽입이 파트를 만든다

영국 부동산 가격 데이터를 담는 uk_price_paid_simple 예시 테이블을 봐 볼게요.

CREATE TABLE uk.uk_price_paid_simple
(
    date Date,
    town LowCardinality(String),
    street LowCardinality(String),
    price UInt32
)
ENGINE = MergeTree
ORDER BY (town, street);

4개 행을 담는 INSERT를 처리한다고 하면, 클릭하우스는 몇 단계를 거칩니다.

  1. 정렬 — 행을 테이블의 정렬 키 (town, street)로 정렬하고, 정렬된 행에 대한 스파스 프라이머리 인덱스를 생성해요.
  2. 컬럼 분리 — 정렬된 데이터를 컬럼별로 나눠요.
  3. 압축 — 각 컬럼을 압축합니다.
  4. 디스크 기록 — 압축된 컬럼을 바이너리 컬럼 파일로 새 디렉토리에 저장해요. 스파스 프라이머리 인덱스도 함께 압축·저장됩니다.

테이블의 특정 엔진에 따라 정렬과 함께 추가 변환이 일어날 수도 있어요.

데이터 파트는 자기 완결적이어서, 스파스 프라이머리 인덱스 외에도 보조 데이터 스킵 인덱스, 컬럼 통계, 체크섬, 미니맥스 인덱스(파티셔닝을 쓸 때) 같은 메타데이터를 더 담습니다.

파트 병합

테이블당 파트 개수를 관리하기 위해 백그라운드 병합 작업이 주기적으로 작은 파트들을 큰 파트로 합쳐요. 설정 가능한 압축 크기(보통 약 150GB)에 도달할 때까지 병합이 계속되고, 병합된 파트는 비활성으로 표시된 뒤 설정 가능한 시간 후 삭제됩니다. 시간이 지나며 병합된 파트들의 계층 구조가 만들어지는데, 그래서 이 테이블을 MergeTree 테이블이라고 부르는 거예요.

초기 파트 수와 병합 오버헤드를 줄이기 위해 클라이언트는 데이터를 묶어서 삽입하는 게 권장돼요. 예를 들어 한 번에 2만 행씩 삽입하거나, 비동기 삽입 모드를 쓰는 방식이 있어요. 비동기 삽입은 클릭하우스가 여러 INSERT로 들어온 행을 같은 테이블에 버퍼링하다가, 버퍼 크기가 설정 임계치를 넘거나 타임아웃이 지나면 새 파트 하나를 만듭니다.

파트 모니터링

현재 존재하는 활성 파트의 목록은 가상 컬럼 _part로 조회할 수 있어요.

SELECT _part
FROM uk.uk_price_paid_simple
GROUP BY _part
ORDER BY _part ASC;

결과의 각 행은 디스크의 디렉토리 이름을 나타내며, 각 디렉토리가 테이블의 활성 데이터 파트 하나를 의미합니다.

또는 클릭하우스가 모든 테이블의 모든 파트 정보를 system.parts 시스템 테이블에 기록하니, 아래 쿼리로 활성 파트의 이름·병합 레벨·행 수를 확인할 수 있어요. 파트의 병합 레벨은 병합이 한 번 더 일어날 때마다 1씩 올라가요. 레벨 0은 아직 병합된 적 없는 새 파트를 뜻합니다.

SELECT
    name,
    level,
    rows
FROM system.parts
WHERE (database = 'uk') AND (`table` = 'uk_price_paid_simple') AND active
ORDER BY name ASC;

출처: Table parts

더 알아보기

  • 파트 병합 — 백그라운드 파트 병합이 구체적으로 어떻게 일어나는지
  • 스파스 프라이머리 인덱스 — 각 파트에 붙는 인덱스가 어떻게 쿼리를 가속화하는지
  • 쿼리 병렬 실행 — 파트 데이터가 어떻게 병렬로 처리되는지