이는 PostgreSQL에서 ClickHouse로 마이그레이션하는 가이드의 3부입니다. 이 문서에서는 실용적인 예시를 통해 PostgreSQL에서 마이그레이션할 때 ClickHouse에서 데이터를 어떻게 모델링하는지 설명합니다.Postgres에서 마이그레이션하는 사용자는 ClickHouse에서 데이터 모델링하는 방법에 대한 가이드를 읽어보시기를 권장합니다. 이 가이드에서는 동일한 Stack Overflow 데이터셋을 사용하여 ClickHouse 기능을 활용한 여러 접근 방식을 살펴봅니다.
OLTP 데이터베이스를 사용해 온 사용자는 ClickHouse에서 이에 해당하는 개념을 찾는 경우가 많습니다. ClickHouse가
PRIMARY KEY 구문을 지원하는 것을 보면, 원본 OLTP 데이터베이스와 동일한 키로 테이블 스키마를 정의하고 싶을 수 있습니다. 하지만 이는 적절하지 않습니다.
ClickHouse 프라이머리 키는 어떻게 다를까요?
- Postgres 프라이머리 키는 정의상 각 행마다 고유합니다. B-트리 구조를 사용하면 이 키를 통해 단일 행을 효율적으로 조회할 수 있습니다. ClickHouse 역시 단일 행 값 조회에 맞게 최적화할 수는 있지만, 분석 워크로드에서는 일반적으로 소수의 컬럼에 대해 많은 수의 행을 읽어야 합니다. 또한 filter는 대개 집계가 수행될 일부 행 집합을 식별해야 합니다.
- 메모리와 디스크 효율성은 ClickHouse가 주로 사용되는 규모에서 매우 중요합니다. 데이터는 ClickHouse 테이블에 파트(parts)라는 청크 단위로 기록되며, 백그라운드에서는 이 파트들을 머지하는 규칙이 적용됩니다. ClickHouse에서는 각 파트가 자체 프라이머리 인덱스를 가집니다. 파트가 머지되면 병합된 파트의 프라이머리 인덱스도 함께 머지됩니다. Postgres와 달리 이러한 인덱스는 각 행마다 생성되지 않습니다. 대신 각 파트의 프라이머리 인덱스에는 여러 행으로 이루어진 각 그룹마다 하나의 인덱스 엔트리가 있습니다. 이 기법을 희소 인덱싱이라고 합니다.
- 희소 인덱싱이 가능한 이유는 ClickHouse가 지정된 키를 기준으로 정렬된 상태로 각 파트의 행을 디스크에 저장하기 때문입니다. 희소 프라이머리 인덱스는 B-트리 기반 인덱스처럼 단일 행을 직접 찾는 대신, 인덱스 엔트리에 대한 이진 검색을 통해 쿼리와 일치할 가능성이 있는 행 그룹을 빠르게 식별할 수 있게 해줍니다. 그런 다음 일치 가능성이 있는 행 그룹을 병렬로 ClickHouse engine에 스트리밍하여 실제로 일치하는 행을 찾습니다. 이러한 인덱스 설계 덕분에 프라이머리 인덱스를 작게 유지할 수 있으며(전체가 main memory에 완전히 적재됨), 동시에 특히 데이터 분석 사용 사례에서 흔한 범위 쿼리의 실행 시간을 크게 줄일 수 있습니다.
테이블의 모든 컬럼은 키 자체에 포함되어 있는지 여부와 관계없이, 지정된 정렬 키 값을 기준으로 정렬됩니다. 예를 들어CreationDate를 키로 사용하면 다른 모든 컬럼의 값 순서도CreationDate컬럼의 값 순서를 따르게 됩니다. 여러 개의 정렬 키를 지정할 수도 있으며, 이 경우SELECT쿼리의ORDER BY절과 동일한 의미로 정렬됩니다.
순서 지정 키 선택
파티션
PARTITION BY 절을 통해 파티셔닝을 지정합니다. 이 절에는 임의의 컬럼에 대한 SQL 표현식을 포함할 수 있으며, 그 결과에 따라 행이 어떤 파티션으로 전송될지가 결정됩니다.
데이터 파트는 디스크에서 각 파티션과 논리적으로 연결되며, 개별적으로 쿼리할 수 있습니다. 아래 예시에서는 toYear(CreationDate) 표현식을 사용해 posts 테이블을 연도별로 파티셔닝합니다. 행이 ClickHouse에 삽입되면 이 표현식이 각 행에 대해 평가되고, 그 결과에 해당하는 파티션으로 전달됩니다(해당 연도의 첫 번째 행이면 파티션이 생성됩니다).
파티션의 활용
- 데이터 관리 - ClickHouse에서는 파티셔닝을 쿼리 최적화 기법이 아니라 데이터 관리 기능으로 우선적으로 고려해야 합니다. 키를 기준으로 데이터를 논리적으로 분리하면 각 파티션을 독립적으로 처리할 수 있습니다. 예를 들어 삭제할 수 있습니다. 이를 통해 시점을 기준으로 파티션, 즉 데이터의 하위 집합을 스토리지 계층 간에 효율적으로 이동하거나 데이터 만료/클러스터에서 효율적으로 삭제할 수 있습니다. 아래 예시에서는 2008년의 게시물을 제거합니다.
- 쿼리 최적화 - 파티션은 쿼리 성능 향상에 도움이 될 수 있지만, 그 효과는 액세스 패턴에 크게 좌우됩니다. 쿼리가 소수의 파티션만 대상으로 하는 경우(이상적으로는 1개) 성능이 향상될 수 있습니다. 일반적으로 이는 파티셔닝 키가 프라이머리 키에 포함되어 있지 않고, 해당 키로 필터링할 때만 유용합니다. 반대로 많은 파티션을 대상으로 해야 하는 쿼리는 파티셔닝을 사용하지 않을 때보다 성능이 더 나빠질 수 있습니다(파티셔닝으로 인해 파트 수가 더 많아질 수 있기 때문입니다). 또한 파티셔닝 키가 이미 프라이머리 키의 앞부분에 있다면, 단일 파티션만 대상으로 할 때의 이점은 매우 작거나 사실상 없을 수 있습니다. 각 파티션 내 값이 고유하다면 파티셔닝을 GROUP BY 쿼리 최적화에 사용할 수도 있습니다. 그러나 일반적으로는 먼저 프라이머리 키가 최적화되어 있는지 확인하고, 파티셔닝은 액세스 패턴이 예측 가능한 특정 시점의 일부 데이터에만 접근하는 예외적인 경우에 한해 쿼리 최적화 기법으로 고려해야 합니다. 예를 들어 일(day) 단위로 파티셔닝하고 대부분의 쿼리가 최근 하루의 데이터를 대상으로 하는 경우가 여기에 해당합니다.
파티션에 대한 권장 사항
내부적으로 ClickHouse는 삽입된 데이터에 대해 파트를 생성합니다. 더 많은 데이터가 삽입될수록 파트 수는 증가합니다. 지나치게 많은 파트로 인해 쿼리 성능이 저하되는 것(읽어야 할 파일 수 증가)을 방지하기 위해, 파트는 백그라운드 비동기 프로세스에서 머지됩니다. 파트 수가 미리 설정된 한도를 초과하면 ClickHouse는 삽입 시 “too many parts” 오류와 함께 예외를 발생시킵니다. 이는 정상적인 운영에서는 발생하지 않아야 하며, ClickHouse가 잘못 구성되었거나 잘못 사용된 경우(예: 작은 삽입을 많이 수행하는 경우)에만 발생합니다.
파트는 각 파티션마다 독립적으로 생성되므로, 파티션 수가 늘어나면 파트 수도 증가합니다. 즉, 파티션 수의 배수로 늘어납니다. 따라서 카디널리티가 높은 파티셔닝 키는 이 오류를 유발할 수 있으므로 피해야 합니다.
materialized views와 프로젝션 비교
ORDER BY 절을 지정할 수 있게 해줍니다.
ClickHouse의 데이터 모델링 문서에서는 ClickHouse에서 materialized view를 사용해 집계를 사전 계산하고, 행을 변환하며, 다양한 액세스 패턴에 맞게 쿼리를 최적화하는 방법을 살펴봅니다.
이 중 마지막 경우에 대해서는 materialized view가 원래 삽입을 받는 테이블과 다른 순서 지정 키를 가진 대상 테이블로 행을 보내는 예시를 소개했습니다.
예를 들어, 다음 쿼리를 살펴보겠습니다.
UserId가 정렬 키가 아니므로 9,000만 개의 모든 행을 스캔해야 합니다(다행히 매우 빠르게 처리되기는 합니다).
이전에는 PostId에 대한 lookup 역할을 하는 materialized view를 사용해 이 문제를 해결했습니다. 같은 문제는
프로젝션으로도 해결할 수 있습니다. 아래 명령은
ORDER BY user_id에 대한 프로젝션을 추가합니다.
ALTER로 생성한 경우, MATERIALIZE PROJECTION 명령을 실행하면 생성 작업은 비동기적으로 진행됩니다. 다음 쿼리로 이 작업의 진행 상황을 확인하고 is_done=1이 될 때까지 기다릴 수 있습니다.
EXPLAIN 명령으로 이 쿼리가 프로젝션을 사용해 처리되는지도 확인할 수 있습니다:
프로젝션을 사용해야 하는 경우
- 데이터의 완전한 재정렬이 필요한 경우입니다. 프로젝션의 표현식은
이론적으로
GROUP BY,를 사용할 수 있지만, 집계를 유지하는 데에는 materialized view가 더 효과적입니다. 또한 쿼리 최적화기는 단순한 재정렬을 사용하는 프로젝션, 즉SELECT * ORDER BY x를 활용할 가능성이 더 높습니다. 저장 공간 사용량을 줄이기 위해 이 표현식에서 일부 컬럼만 선택할 수 있습니다. - 저장 공간 사용량 증가와 데이터를 두 번 기록하는 오버헤드를 감수할 수 있는 경우입니다. 삽입 속도에 미치는 영향을 테스트하고 저장 공간 오버헤드를 평가하십시오.
버전 25.5부터 ClickHouse는
프로젝션에서 가상 컬럼
_part_offset을 지원합니다. 이를 통해 프로젝션을 더 공간 효율적으로 저장할 수 있습니다.자세한 내용은 “프로젝션”을 참조하십시오.