핵심 요약: 고카디널리티 컬럼(사용자 ID·로봇 ID·세션 ID 등)을 PARTITION BY에 직접 넣으면 파트 디렉터리가 수천~수만 개로 늘어나 메타데이터 처리 비용이 급증한다. 안전한 기본값은 시간 기반 파티션(일·월)과 고카디널리티 차원을 ORDER BY 정렬 키로 분리하는 구성이다. 2026년 기준 ClickHouse·Pinot 실측 사례 모두 이 분리 패턴에서 수십만~100만 행/초 수준의 수집 처리량을 보고한다.

파티션에 고카디널리티 컬럼을 쓰면 정확히 어디서 터지는가?

파티션 키가 고카디널리티면 파티션 수와 파트 디렉터리 수가 거의 1:1로 늘어나고, 쓰기 배치마다 새 파티션이 생겨 머지 대상 파트가 폭증한다. ClickHouse 관련 가이드는 파티션 수를 대략 10~100개 수준으로 유지하고 수천 개는 피하라고 권고한다. 이는 파티션이 데이터 수명주기(보존·삭제) 관리 단위이지 쿼리 필터 단위가 아니라는 설계 가정에서 나온 결론이다 ClickHouse 쿼리 최적화 가이드.

  • 파티션은 수명주기 관리 단위, 고카디널리티 필터는 ORDER BY로 처리한다
  • ClickHouse는 파티션 10~100개를 권장하고 수천 개 이상은 피하라고 명시한다
  • 활성 파트 총량은 수천 개보다 낮게 유지하는 편이 운영상 안전하다
  • 머티리얼라이즈드 뷰의 그룹화 차원은 대시보드가 실제로 쓰는 것으로 제한한다
  • Apache Pinot에서 userId는 Kafka 분산 키로 쓰이지, 저장 파티션 키로 쓰이지 않는다

이번 분석에서는 ClickHouse MergeTree 계열과 Apache Pinot 실시간 수집 경로에서 PARTITION BY·ORDER BY·머티리얼라이즈드 뷰(MV) 설계가 수집 처리량과 파트 수에 미치는 영향만 뜯어본다. 쿼리 옵티마이저 내부 구현이나 분산 조인 알고리즘은 이 글의 범위 밖이다.

파티션 키와 정렬 키는 저장 계층에서 왜 다른 일을 하는가?

PARTITION BY는 디렉터리 단위 분리, 즉 파트 생성 경계를 결정하고, ORDER BY는 파트 내부 물리 정렬과 primary index 역할을 한다. 이 둘을 혼동하면 고카디널리티 차원이 파티션으로 흘러들어가고, 반대로 분리하면 시간 파티션은 보존 정책 단위로, 정렬 키는 쿼리 가속 단위로 각자 일을 한다. 권장되는 기본 형태는 이벤트 테이블을 PARTITION BY toYYYYMM(event_time)처럼 월·일 단위로 나누고, ORDER BY (tenant_id, event_time)처럼 자주 조회하는 차원을 정렬 키 앞쪽에 배치하는 방식이다 Tinybird 대규모 데이터셋 가이드. 로봇 텔레메트리 실험에서도 PARTITION BY는 날짜, ORDER BY (robot_id, timestamp)는 식별자 중심으로 분리했고, 이 구성이 수백~수천 대 규모 시뮬레이션에서 기본값으로 유지됐다 deployed.cloud IoT 텔레메트리 벤치마크.

파트 디렉터리가 늘어나면 쿼리 계획 비용은 어떻게 커지는가?

ClickHouse는 파티션과 파트마다 별도 디렉터리를 생성하며, 파트 수가 늘수록 쿼리 계획 단계에서 처리해야 할 메타데이터 양도 함께 커진다. 고카디널리티 컬럼을 파티션 키로 쓰면 이 디렉터리 수가 데이터 양에 비례하지 않고 카디널리티에 비례해서 늘어난다는 점이 문제다. 운영 안정성을 위해서는 활성 파트 총량을 수천 개보다 낮게 유지하라는 실무 권고가 제시된다 Tinybird ClickHouse 대규모 데이터 처리. 파트가 많아지면 머지 스케줄러의 부하도 함께 늘어나는데, 이는 조회 지연뿐 아니라 백그라운드 머지가 쓰기 처리량을 깎아먹는 간접 비용으로 이어진다.

머티리얼라이즈드 뷰의 그룹화 차원을 늘리면 무슨 일이 일어나는가?

MV는 데이터가 도착하는 시점에 변환·집계를 수행해 별도 요약 테이블에 기록하는 구조라, 읽기 비용을 줄이는 대신 쓰기 경로에 추가 작업을 얹는다. 공식 문서는 여러 그룹화 차원을 추가하면 뷰 크기가 크게 증가하고 효율이 떨어질 수 있으며, 뷰 개수 자체에도 확장성 한계가 있다고 설명한다 ClickHouse MV 관리 문서. 원천 이벤트의 모든 사용자·세션·URL 조합을 MV 그룹 키로 만들면 요약 테이블이 원천 데이터와 비슷한 카디널리티를 갖게 되어, 집계 테이블을 둔 목적 자체가 희석된다. 그래서 공식 문서도 자주 쓰는 그룹화·필터링 컬럼만 뷰에 포함하라고 권고한다.

실제 수집 처리량은 스키마와 파이프라인에 따라 얼마나 갈리는가?

행 구조와 파이프라인 구성에 따라 처리량 차이가 크게 보고된다. 아래는 모두 작성자 공개 실험이거나 특정 사례 보고 수치이며, 재현 조건이 함께 명시된 경우만 인용한다.

조건 처리량 비고
단일 노드, 5,000만 건, 행당 250B, writer 8, batch 1만 30만~35만 행/초 ZSTD(3) 압축 2.0~2.5GB, 원시 12.5GB webscraper.site
Kafka→Buffer→MV→MergeTree, 3노드 약 22만 행/초 MV·복제 포함, 직접 INSERT와 단순 비교 불가 webscraper.site
구조화 JSON 3,000만 건, 800B, 단일 노드 batch 5,000 8만~11만 행/초 3노드에서는 6.5만~8만 행/초 webscraper.site
로봇 텔레메트리, 단일 노드 16vCPU/64GB, batch 1만 12만~20만 행/초 PARTITION BY 날짜, ORDER BY (robot_id, timestamp) deployed.cloud
로봇 텔레메트리, 5노드 32vCPU/128GB, 숫자형 전용 100만 행/초 이상 중첩 배열·문자열 중심 payload는 약 30% 낮춤 deployed.cloud

2026년 기준으로 봐도 열이 많거나 JSON·문자열 비중이 커지면 처리량이 수 배 떨어지는 패턴은 일관되게 관찰된다. 이는 고카디널리티 파티션 문제와는 별개로, 행 구조 자체가 수집 성능을 좌우한다는 점을 보여준다.

정렬 키와 MV로 옮기면 무엇을 얻고 무엇을 잃는가?

얻는 것은 파트 수 억제와 쿼리 계획 비용 감소, 그리고 primary index를 통한 고카디널리티 필터 가속이다. 한 사례는 5,000억 행 테이블에서 기본키 범위 조회가 4.3배, Bloom filter 조회가 7.7배 빨라졌다고 보고했으나, 하드웨어·스키마·버전 등 재현 조건이 충분히 공개되지 않아 독립 재현 가능한 수치로 인용하기엔 부족하다 Tinybird 빅데이터 사례.

잃는 것은 쓰기 경로 복잡도다. MV는 도착 시점에 변환·집계를 추가로 수행하므로, 그룹화 차원이 늘어날수록 쓰기 비용도 함께 늘어난다. 구조화 JSON·문자열 비중이 커진 스트림에서 처리량이 숫자형 전용 스트림 대비 30% 이상 낮아진다는 보고도 같은 맥락이다. 즉 읽기 비용을 정렬 키와 MV로 옮기면, 그 대가는 수집 파이프라인의 쓰기 지연과 운영 복잡도로 돌아온다.

이 패턴은 언제 쓰고 언제 피해야 하는가?

시간 기반 보존 정책이 있는 이벤트 스트림, 그리고 테넌트·디바이스 ID처럼 자주 필터링하는 차원이 있을 때 "시간 파티션 + 식별자 정렬 키" 조합이 기본값으로 적합하다. MV는 대시보드가 실제로 쓰는 차원으로 그룹화를 제한해야 효과가 유지된다. 한 사례는 1,000억 건 원천 이벤트를 테넌트·이벤트 종류별 연간 약 8,760행 규모 요약 테이블로 줄였지만, 원천 카디널리티·집계 주기가 충분히 공개되지 않아 일반 성능 보장으로 쓰기는 어렵다 Tinybird 대규모 데이터셋 가이드.

Apache Pinot에서는 userId 같은 고카디널리티 키를 Kafka 토픽 분산 키로 써서 소비자를 병렬화하는 설계가 제시되지만, 이는 저장 파티션을 사용자별로 쪼개는 것과는 다르다 Pinot 실시간 제품 분석 문서. LowLevel 소비 모드에서는 Kafka 파티션마다 독립 consumer가 생기고 복제 수에 맞춰 서버에 배치되므로, Kafka 파티션 수·복제 수·서버 수를 함께 설계해야 하며 고카디널리티 키의 불균형은 특정 consumer에 부하를 몰 수 있다 Pinot 데이터 수집 문서. 반대로 저장 파티션 자체를 고카디널리티 키로 나누려는 설계는 피하는 편이 안전하다.

핵심 정리

  • 고카디널리티 컬럼은 PARTITION BY가 아니라 ORDER BY(정렬 키)에서 처리한다
  • ClickHouse는 파티션 10~100개, 활성 파트 수천 개 미만을 권장 기준으로 제시한다
  • MV 그룹화 차원은 대시보드가 실제로 쓰는 차원으로 제한해야 뷰 크기 폭증을 피한다
  • 행 구조가 숫자형 위주면 100만 행/초 이상, JSON·문자열 비중이 크면 8만~11만 행/초 수준으로 처리량 차이가 크게 보고된다(재현 조건 상이)
  • Pinot에서 고카디널리티 키는 Kafka 분산 키로 쓰되 저장 파티션 키로는 쓰지 않는다

자주 묻는 질문

기존에 고카디널리티로 파티션된 테이블을 어떻게 옮겨야 하나?

새 테이블에 시간 파티션과 식별자 중심 ORDER BY를 적용해 생성한 뒤 배치 단위로 재적재하는 방식이 일반적으로 거론된다. 재적재 중 파트 머지 부하가 커질 수 있으므로 저트래픽 구간에 나눠 진행하는 편이 안전하다.

파티션 수가 이미 수천 개를 넘었는지는 어떻게 확인하나?

ClickHouse라면 시스템 테이블에서 파티션·파트 수를 직접 조회해 권장 범위(10~100개 수준)와 비교하는 것이 기본적인 점검 방법이다. 파트 수 자체가 수천 개를 넘는지도 별도로 확인해야 하는데, 이는 파티션 수와는 다른 지표다.

MV를 여러 개 두면 수집 처리량에 얼마나 영향을 주나?

구체적인 배수는 스키마와 그룹화 차원에 따라 달라져 일반화하기 어렵지만, 같은 실험에서 Kafka·MV·복제가 포함된 3노드 파이프라인은 약 22만 행/초로, MV 없는 직접 INSERT 대비 낮은 수치를 보였다. MV 개수와 그룹화 범위를 늘릴수록 쓰기 경로 비용이 누적된다고 보는 편이 안전하다.

Pinot과 ClickHouse 중 어느 쪽이 고카디널리티 처리에 유리한가?

두 엔진 모두 고카디널리티 키를 저장 파티션이 아니라 정렬 키(ClickHouse) 또는 분산 키(Pinot)로 처리하라는 동일한 원칙을 따른다. 엔진 선택보다는 이 원칙을 해당 엔진의 설계 단위에 맞게 적용했는지가 처리량 차이를 더 크게 좌우한다.

더 알아보기

실시간 분석 엔진: 아키텍처와 설계 판단 — 이 주제의 종합 가이드