Ken
ClickHouse에서 "Catalog"는 단순히 테이블 목록을 관리하는 것 이상의 의미를 가집니다. 분산 시스템에서 메타데이터를 어떻게 저장하고, 동기화하고, 조회하는지에 따라 전체 시스템의 확장성과 안정성이 결정됩니다. 이 글에서는 ClickHouse의 카탈로그 시스템이 어떻게 구성되어 있고, Replicated/Distributed 테이블과 Keeper가 어떻게 협력하는지, 그리고 ClickHouse Cloud의 Shared Catalog가 가져온 혁신적인 변화를 깊이 있게 살펴보겠습니다.
- ClickHouse Catalog의 구성
- 로컬 메타데이터 저장소
- System 테이블을 통한 메타데이터 조회
- MergeTree 파트 메타데이터
- Replicated, Distributed와 Keeper
- ReplicatedMergeTree와 Keeper
- Distributed 테이블의 메타데이터
- Replicated 데이터베이스 엔진
- ClickHouse Cloud의 Shared Catalog
- 기존 아키텍처의 한계
- SharedMergeTree: 클라우드 네이티브 테이블 엔진
- Shared Catalog: 완전한 Stateless 컴퓨트
- 메타데이터 라이프사이클 관리
- Shared Catalog가 가져온 개선점
- 성능 벤치마크 결과
- 결론
ClickHouse Catalog의 구성
ClickHouse의 카탈로그는 크게 세 가지 레이어로 구성됩니다: 로컬 메타데이터 파일, System 테이블, 그리고 분산 환경에서의 Keeper 기반 메타데이터입니다.
로컬 메타데이터 저장소
전통적인 ClickHouse에서 메타데이터는 로컬 파일 시스템에 SQL 텍스트 파일 형태로 저장됩니다. 데이터베이스 정의는 /var/lib/clickhouse/metadata/ 디렉토리에 저장되며, 각 데이터베이스는 UUID를 가진 디렉토리로 구성됩니다. 테이블 정의는 해당 데이터베이스 디렉토리 아래에 .sql 파일로 저장됩니다. 삭제된 테이블의 메타데이터는 metadata_dropped/ 디렉토리에 임시 보관됩니다.
/var/lib/clickhouse/
├── metadata/
│ ├── default/
│ │ ├── events.sql
│ │ ├── users.sql
│ │ └── ...
│ ├── analytics/
│ │ └── ...
│ └── system -> /var/lib/clickhouse/metadata/00000000-0000-0000-0000-000000000000
├── metadata_dropped/
│ └── ...
└── data/
└── ...
이 구조의 장점은 단순성입니다. ClickHouse 서버가 시작할 때 이 SQL 파일들을 읽어서 메모리에 테이블 정의를 로드합니다. 그러나 분산 환경에서는 각 노드가 독립적인 메타데이터를 가지므로 동기화 문제가 발생합니다.
System 테이블을 통한 메타데이터 조회
ClickHouse는 system 데이터베이스에 다양한 시스템 테이블을 제공하여 메타데이터를 SQL로 조회할 수 있게 합니다. 주요 시스템 테이블은 다음과 같습니다.
스키마 관련 테이블은 데이터베이스와 테이블의 구조를 담고 있습니다. system.databases는 모든 데이터베이스 목록과 엔진 정보를 제공하고, system.tables는 테이블 정의, 엔진, 파티션 키 등 상세 정보를 담고 있습니다. system.columns는 모든 테이블의 컬럼 정보를 제공합니다.
-- 데이터베이스 목록 조회
SELECT name, engine, data_path
FROM system.databases;
-- 특정 데이터베이스의 테이블 목록
SELECT name, engine, total_rows, total_bytes
FROM system.tables
WHERE database = 'default';
-- 테이블 스키마 조회
SELECT name, type, default_kind, default_expression
FROM system.columns
WHERE database = 'default' AND table = 'events';
저장소 관련 테이블은 실제 데이터 저장 상태를 보여줍니다. system.parts는 MergeTree 테이블의 파트 정보(크기, 행 수, 파티션 등)를 제공하고, system.parts_columns는 파트별 컬럼 정보를 담고 있습니다. system.disks는 설정된 디스크 정보를 보여주며, system.storage_policies는 스토리지 정책을 확인할 수 있게 합니다.
-- 테이블의 파트 현황 조회
SELECT
partition,
count() as parts,
sum(rows) as total_rows,
formatReadableSize(sum(bytes_on_disk)) as size
FROM system.parts
WHERE database = 'default' AND table = 'events' AND active
GROUP BY partition
ORDER BY partition;
복제 관련 테이블은 분산 환경에서의 복제 상태를 모니터링합니다. system.replicas는 복제 테이블의 상태를 제공하고, system.replication_queue는 복제 대기 중인 작업을 보여줍니다. system.replicated_fetches는 다른 복제본에서 가져오는 중인 파트 정보를 담고 있습니다.
-- 복제 상태 확인
SELECT
database, table,
is_leader, is_readonly,
queue_size, inserts_in_queue, merges_in_queue,
log_max_index, log_pointer
FROM system.replicas
WHERE database = 'default';
MergeTree 파트 메타데이터
MergeTree 계열 테이블에서 가장 핵심적인 메타데이터는 파트(Part) 정보입니다. 각 파트는 불변(immutable)이며, UUID, 컬럼 정보, 체크섬, 파티션 정보, TTL 정보, 행 수 등을 포함합니다.
Replicated, Distributed와 Keeper
분산 ClickHouse 클러스터에서 메타데이터 관리의 핵심은 Keeper(또는 ZooKeeper)입니다. Replicated 테이블과 Distributed 테이블은 서로 다른 방식으로 Keeper와 상호작용합니다.
ReplicatedMergeTree와 Keeper
ReplicatedMergeTree 테이블은 Keeper에 복제에 필요한 모든 메타데이터를 저장합니다.
CREATE TABLE events_replicated ON CLUSTER 'my_cluster'
(
event_time DateTime,
user_id UInt64,
event_type String
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_time, user_id);
위 DDL에서 /clickhouse/tables/{shard}/events는 Keeper 내의 경로입니다. 이 경로 아래에 다양한 메타데이터 노드가 생성됩니다.
Keeper의 ReplicatedMergeTree 메타데이터 구조는 여러 노드로 구성됩니다. /metadata는 테이블 스키마(CREATE TABLE 문)를 저장합니다. /columns는 컬럼 정의를 담고, /log는 복제 로그(INSERT, MERGE 등 작업)를 기록합니다. /blocks는 중복 방지를 위한 블록 해시를 저장하며, /replicas는 각 복제본의 정보를 관리합니다. /replicas/{replica}/queue는 해당 복제본의 작업 큐를 담고 있습니다.
데이터 복제 프로세스는 다음과 같이 진행됩니다. Server-1이 INSERT를 받으면, 데이터를 로컬 파트로 저장하고 /log에 항목을 추가합니다. 다른 서버들은 이 로그를 감시하다가 새 파트를 발견하면 Server-1에서 해당 파트를 가져옵니다(fetch). Keeper는 실제 데이터를 저장하지 않으며, 오직 "어떤 파트가 존재하는지"에 대한 메타데이터만 관리합니다.
Distributed 테이블의 메타데이터
Distributed 테이블은 자체적으로 데이터를 저장하지 않으며, 샤드에 분산된 로컬 테이블에 대한 "뷰" 역할을 합니다.
CREATE TABLE events_distributed ON CLUSTER 'my_cluster'
(
event_time DateTime,
user_id UInt64,
event_type String
)
ENGINE = Distributed('my_cluster', 'default', 'events_replicated', rand());
Distributed 테이블의 메타데이터는 주로 클러스터 설정에 의존합니다. /etc/clickhouse-server/config.d/remote_servers.xml 파일에 샤드와 복제본 정보가 정의되며, Distributed 테이블은 이 설정을 참조하여 쿼리를 분산합니다.
쿼리 실행 흐름에서 클라이언트가 Distributed 테이블에 쿼리를 보내면, 코디네이터 노드가 쿼리를 받아 각 샤드에 전달합니다. 각 샤드가 로컬에서 쿼리를 실행하고, 코디네이터가 결과를 수집하여 병합한 후 클라이언트에 반환합니다.
Replicated 데이터베이스 엔진
ClickHouse 21.3에서 도입된 Replicated 데이터베이스 엔진은 테이블 수준이 아닌 데이터베이스 수준에서 메타데이터 복제를 수행합니다.
CREATE DATABASE my_db ON CLUSTER 'my_cluster'
ENGINE = Replicated('/clickhouse/databases/my_db', '{shard}', '{replica}');
Replicated 데이터베이스 엔진은 DDL 변경 사항(CREATE, ALTER, DROP 등)을 자동으로 모든 노드에 복제합니다. ON CLUSTER 구문 없이도 DDL이 자동 복제되며, 새 노드가 추가되면 Keeper에서 메타데이터를 자동으로 가져와 부트스트랩합니다.
Atomic 엔진과의 차이점을 살펴보면, Atomic 데이터베이스 엔진은 기본 엔진으로 로컬 메타데이터만 관리합니다. 반면 Replicated 엔진은 Atomic을 기반으로 하되, Keeper의 DDL 로그를 통해 메타데이터 변경을 복제합니다.
ClickHouse Cloud의 Shared Catalog
ClickHouse Cloud는 기존 아키텍처의 한계를 극복하기 위해 완전히 새로운 메타데이터 관리 시스템을 도입했습니다. 이것이 바로 Shared Catalog와 Shared 데이터베이스 엔진입니다.
기존 아키텍처의 한계
Shared-Nothing 아키텍처의 문제점에서 기존 ClickHouse 클러스터는 각 노드가 독립적인 로컬 스토리지를 가지는 Shared-Nothing 아키텍처였습니다. 첫째, 메타데이터가 로컬 디스크에 저장되어 노드 추가/제거 시 복잡한 작업이 필요했습니다. 둘째, ReplicatedMergeTree는 소수의 복제본을 위해 설계되어, 노드 수가 많아지면 복제 로그 경합과 락 오버헤드가 증가했습니다. 셋째, DROP 작업이 모든 노드가 온라인 상태여야 완료되었고, 일부 노드가 오프라인이면 삭제가 지연되었습니다. 넷째, 원자적 INSERT ... SELECT나 크로스 데이터베이스 RENAME이 구현하기 어려웠습니다.
SharedMergeTree: 클라우드 네이티브 테이블 엔진
ClickHouse Cloud는 SharedMergeTree라는 새로운 테이블 엔진을 도입했습니다. ReplicatedMergeTree의 클라우드 네이티브 대체품으로, 스토리지와 컴퓨트를 완전히 분리합니다.
SharedMergeTree의 핵심 특징은 다음과 같습니다. 첫째, 모든 데이터는 오브젝트 스토리지(S3, GCS 등)에 저장됩니다. 둘째, 메타데이터만 Keeper에 복제되며, 실제 데이터는 복제되지 않습니다. 셋째, 노드 간 직접 통신이 불필요하고, 리더리스(Leaderless) 비동기 복제를 지원합니다. 넷째, 샤딩 없이 수백 개의 복제본을 지원합니다.
-- ClickHouse Cloud에서는 MergeTree가 자동으로 SharedMergeTree로 변환
CREATE TABLE events (
event_time DateTime,
user_id UInt64,
event_type String
)
ENGINE = MergeTree
ORDER BY (event_time, user_id);
-- 실제 사용되는 엔진 확인
SELECT engine FROM system.tables WHERE name = 'events';
-- 결과: SharedMergeTree
데이터 쓰기 프로세스를 보면, Server-1이 INSERT를 받아 파트를 오브젝트 스토리지에 직접 저장하고, 파트 메타데이터(위치, 크기 등)를 Keeper에 기록합니다. 다른 서버들은 Keeper에서 새 파트의 존재를 알게 되며, 필요할 때 오브젝트 스토리지에서 직접 읽습니다. 데이터 복제가 필요 없으므로 확장이 훨씬 빠르고 효율적입니다.
Shared Catalog: 완전한 Stateless 컴퓨트
Shared Catalog는 SharedMergeTree의 다음 단계 진화입니다. 데이터베이스와 테이블 정의를 로컬 디스크가 아닌 중앙화된 Keeper에 저장합니다.
Shared 데이터베이스 엔진의 특징으로는 먼저 순수 인메모리 엔진이 있습니다. 로컬 디스크에 메타데이터를 전혀 저장하지 않으며, 컴퓨트 노드는 CPU와 메모리만 필요합니다(Diskless). 또한 버전 관리된 메타데이터를 제공합니다. Keeper에 단일 진실 소스(Single Source of Truth)를 유지하고, 노드 시작 시 최신 상태를 가져와 diff만 적용합니다.
자동 메타데이터 복제를 통해 데이터베이스 정의가 모든 서버에 자동 복제되며, 새 인스턴스 추가 시 수동 설정이 불필요합니다. 마지막으로 개선된 컴퓨트-컴퓨트 분리를 지원합니다. DROP 작업이 일부 복제본이 오프라인이어도 완료되며, 백그라운드 삭제 스레드가 독립적으로 정리 작업을 수행합니다.
-- ClickHouse Cloud에서 자동으로 Shared 엔진 사용
CREATE DATABASE analytics;
-- 테이블 생성 시 자동으로 Shared Catalog의 이점을 활용
CREATE TABLE analytics.events (
event_time DateTime,
user_id UInt64,
event_type String
)
ENGINE = MergeTree
ORDER BY (event_time, user_id);
메타데이터 라이프사이클 관리
Shared Catalog는 메타데이터 객체의 라이프사이클을 단계별로 관리합니다.
INTENTION 단계에서는 DDL 실행 시 먼저 이 단계로 등록됩니다. 노드 장애 시 이 단계에서 멈추면 백그라운드 정리 스레드가 자동 삭제합니다. CREATED 단계는 생성 성공 후 정상 활성 상태입니다. 테이블/데이터베이스가 사용 가능한 상태가 됩니다. DROP_SCHEDULED 단계에서는 삭제 요청되었지만 아직 완료되지 않은 상태입니다. UNDROP 명령으로 복구 가능합니다.
DROP_IN_PROGRESS 단계는 백그라운드 삭제 스레드가 실제 삭제를 수행 중인 상태입니다. 오브젝트 스토리지의 데이터 삭제 후 Keeper 메타데이터를 삭제합니다.
Shared Catalog가 가져온 개선점
새로운 DDL 기능들이 도입되었습니다. 원자적 CREATE TABLE AS SELECT에서 SELECT가 실패하면 테이블 생성도 롤백됩니다. 부분적으로 생성된 테이블이 남지 않습니다. 크로스 데이터베이스 RENAME이 가능해졌습니다. 기존에는 메타데이터가 데이터베이스별 로그에 저장되어 어려웠지만, Shared Catalog의 단일 소스 메타데이터로 해결되었습니다. UNDROP 기능을 통해 DROP_SCHEDULED 상태의 테이블을 복구할 수 있으며, 메타데이터와 데이터가 아직 존재하는 동안 안전하게 복구 가능합니다.
탄력적 확장성도 크게 개선되었습니다. 웜업 없는 프로비저닝으로 새 노드가 Keeper에서 메타데이터를 즉시 가져와 빠르게 시작합니다. 수평/수직 스케일링이 더 빨라져서 컴퓨트 노드 추가/변경 시 메타데이터 동기화 오버헤드가 최소화되었습니다.
오픈 포맷 지원으로 Iceberg, Delta Lake 등 오픈 테이블 포맷에 대해서도 Stateless 컴퓨트를 적용할 수 있습니다.
-- DataLakeCatalog 엔진으로 외부 카탈로그 연결
CREATE DATABASE glue
ENGINE = DataLakeCatalog
SETTINGS
catalog_type = 'glue',
region = 'us-east-2',
aws_access_key_id = '...',
aws_secret_access_key = '...';
-- 외부 카탈로그의 테이블을 네이티브처럼 조회
SELECT * FROM glue.my_iceberg_table;
성능 벤치마크 결과
ClickHouse Cloud의 SharedMergeTree와 Shared Catalog 도입으로 인한 성능 개선은 다음과 같습니다.
INSERT 처리량에서 Poizon社의 사례에 따르면, 초당 2,000만 행의 쓰기 속도를 달성했습니다. 메타데이터가 복제될 필요 없이 Keeper에만 기록되므로 확장 시에도 처리량이 선형적으로 증가합니다.
수평 확장 속도에서 기존 ReplicatedMergeTree는 새 노드 추가 시 데이터 복제가 필요했으나, SharedMergeTree는 메타데이터 동기화만으로 분 단위 확장이 가능합니다.
리소스 효율성에서 Shared Catalog의 Diskless 컴퓨트로 노드 시작 시간이 대폭 단축되었습니다. Poizon社는 인프라 비용 60% 절감을 달성했습니다.
결론
ClickHouse의 Catalog 시스템은 단순한 메타데이터 저장소에서 클라우드 네이티브 분산 시스템의 핵심 컴포넌트로 진화해왔습니다.
전통적인 아키텍처에서는 로컬 파일 시스템 기반 메타데이터, ReplicatedMergeTree의 Keeper 기반 복제 로그, Distributed 테이블의 클러스터 설정 의존 방식이 사용되었습니다.
ClickHouse Cloud 아키텍처에서는 SharedMergeTree로 스토리지-컴퓨트 분리, Shared Catalog로 완전한 Stateless 컴퓨트, 버전 관리된 중앙화 메타데이터, 탄력적 확장과 새로운 DDL 기능이 도입되었습니다.
이러한 진화는 ClickHouse가 단순한 분석 데이터베이스에서 진정한 클라우드 네이티브 데이터 플랫폼으로 발전하고 있음을 보여줍니다. 앞으로 Multi-group Raft와 같은 기술 발전이 이어지면, 더욱 강력하고 유연한 분산 메타데이터 관리가 가능해질 것입니다.
참고 자료
- Shared catalog and shared database engine | ClickHouse Docs
- No more disks: the architecture behind stateless compute in ClickHouse Cloud
- ClickHouse Cloud boosts performance with SharedMergeTree and Lightweight Updates
- Replicated table engines | ClickHouse Docs
- Data catalog | ClickHouse Engineering Resources