오브젝트 스토리지와 데이터 레이크 (s3 함수, Iceberg, Delta Lake, 카탈로그)
S3, GCS, Azure Blob 의 파일을 테이블처럼 읽고 쓰는 s3 계열 함수와 엔진, S3Queue 스트리밍 적재, Iceberg 와 Delta Lake 테이블을 카탈로그째로 붙이는 DataLakeCatalog, 그리고 비용과 성능을 좌우하는 설정을 정리합니다.
오브젝트 스토리지를 다루는 네 가지 방식
ClickHouse 는 오브젝트 스토리지를 네 가지 역할로 씁니다. 역할이 다르면 쓰는 문법도 다르니 먼저 구분해 둡니다.
| 역할 | 문법 | 설명 |
|---|---|---|
| 외부 파일을 쿼리 | s3(), gcs(), azureBlobStorage(), url(), hdfs() 테이블 함수 | 적재 없이 파일을 바로 읽음. 탐색, 일회성 적재, 내보내기 |
| 파일 묶음을 테이블로 고정 | S3, AzureBlobStorage 테이블 엔진 | 경로와 포맷을 테이블 정의로 저장. 읽기와 쓰기 모두 |
| 버킷에 떨어지는 파일을 계속 적재 | S3Queue, AzureQueue 엔진 + 머티리얼라이즈드 뷰 | 새 파일을 감지해 한 번씩 처리하는 스트리밍 적재 |
| MergeTree 데이터를 오브젝트 스토리지에 저장 | 스토리지 정책의 s3 디스크, ClickHouse Cloud 의 SharedMergeTree | 테이블 자체의 저장 매체. TTL, 디스크 티어링 과 ClickHouse Cloud 개요 에서 다룸 |
이 문서는 앞의 세 가지와 레이크 포맷(Iceberg, Delta Lake) 을 다룹니다.
s3 테이블 함수
SELECT count(), min(ts), max(ts)
FROM s3('https://my-bucket.s3.ap-northeast-2.amazonaws.com/events/2026/10/*.parquet', Parquet);
-- 자격 증명 직접, 구조 지정, 압축 명시
SELECT * FROM s3('https://my-bucket.s3.amazonaws.com/raw/day={01..31}/*.json.gz',
'AKIA...', 'secret', JSONEachRow,
'ts DateTime, user_id UInt64, event String', 'gzip')
LIMIT 10;- 경로 패턴
*(한 단계 임의 문자열),**(하위 디렉터리 재귀),?,{a,b},{1..31}을 씁니다. 숫자 범위는 자릿수 패딩도 맞춰 줍니다({01..31}). - 가상 컬럼
_path,_file,_size,_time,_etag로 어느 파일에서 온 행인지 알 수 있습니다. 파일 단위 멱등 적재의 핵심 재료입니다. - Hive 파티션 경로
year=2026/month=10/같은 경로는use_hive_partitioning = 1이면year,month가 가상 컬럼으로 잡히고, 그 컬럼의 WHERE 조건은 파일 목록 단계에서 걸러집니다. - 포맷과 압축 생략 하면 확장자로 추론합니다. 구조도 생략하면 스키마 추론 이 동작합니다.
- 클러스터 병렬
s3Cluster('cluster', ...)는 파일 목록을 노드에 나눠 읽습니다. 25.3 부터는 병렬 복제본(enable_parallel_replicas) 이 켜진 클러스터에서 그냥s3()만 써도 자동으로 분산되며, 끄려면parallel_replicas_for_cluster_engines = 0을 줍니다.
자격 증명은 named collection 으로
키를 쿼리에 박으면 system.query_log 에 그대로 남습니다. named collection 에 담고 이름으로 부르세요.
CREATE NAMED COLLECTION lake_prod AS
url = 'https://my-bucket.s3.ap-northeast-2.amazonaws.com/',
access_key_id = 'AKIA...', secret_access_key = '...';
SELECT count() FROM s3(lake_prod, filename = 'events/2026/10/*.parquet', format = 'Parquet');EC2 나 EKS 위에서는 키 없이 인스턴스 역할을 쓸 수 있습니다(서버 설정 <s3><use_environment_credentials>true</use_environment_credentials></s3>). 25.8 부터는 extra_credentials(role_arn = 'arn:aws:iam::...:role/reader') 로 쿼리마다 다른 IAM 역할을 맡을 수 있고, GRANT READ ON S3('https://my-bucket/path/*') 처럼 URL 패턴 단위로 권한을 줄 수 있습니다. ClickHouse Cloud 는 서비스마다 IAM 역할 ARN 을 발급해 주므로 버킷 정책에 그 ARN 만 허용하면 됩니다.
쓰기와 내보내기
-- 쿼리 결과를 파일 하나로
INSERT INTO FUNCTION s3(lake_prod, filename = 'export/top_users.parquet', format = 'Parquet')
SELECT user_id, count() AS c FROM events GROUP BY user_id ORDER BY c DESC LIMIT 1000000;
-- Hive 스타일 파티션 디렉터리로 (25.8 부터 S3 엔진의 partition_strategy = 'hive')
CREATE TABLE events_export (ts DateTime, user_id UInt64, event String, dt Date)
ENGINE = S3(lake_prod, filename = 'events_export/', format = 'Parquet', partition_strategy = 'hive')
PARTITION BY dt;
INSERT INTO events_export SELECT ts, user_id, event, toDate(ts) FROM events;같은 파일 이름에 두 번 INSERT 하면 기본으로는 오류가 납니다. s3_truncate_on_insert = 1 이면 덮어쓰고, s3_create_new_file_on_insert = 1 이면 뒤에 번호를 붙여 새 파일을 만듭니다.
S3Queue: 버킷에 떨어지는 파일을 스트리밍으로
업스트림이 파일을 S3 에 계속 떨어뜨리는 구조(배치 ETL 결과, 로그 수집기, 파트너 전송) 라면 S3Queue 엔진이 "새 파일 감지, 한 번만 처리" 를 맡습니다. 처리한 파일 목록은 Keeper 에 남아 서버가 재시작해도 이어집니다.
CREATE TABLE raw_files_queue (ts DateTime, user_id UInt64, event String)
ENGINE = S3Queue(lake_prod, filename = 'incoming/*.json.gz', format = 'JSONEachRow')
SETTINGS mode = 'unordered',
keeper_path = '/clickhouse/s3queue/raw_files',
s3queue_processing_threads_num = 4,
s3queue_tracked_files_limit = 100000,
s3queue_polling_min_timeout_ms = 10000;
CREATE MATERIALIZED VIEW raw_files_mv TO events AS
SELECT ts, user_id, event FROM raw_files_queue;mode = 'ordered'는 파일 이름이 사전순으로 증가한다는 가정 아래 마지막 처리 파일만 기억해 Keeper 부담이 적고,unordered는 파일마다 기록해 임의 순서를 허용합니다.- 처리 결과를
system.s3queue_log에서 보고, 실패한 파일은s3queue_loading_retries만큼 재시도합니다. - 25.12 부터는 처리가 끝난 파일을 다른 접두어로 옮기거나 태그를 붙이는 후처리 옵션이 들어와, "처리됨" 폴더 운영이 ClickHouse 안에서 끝납니다.
- Azure 는
AzureQueue엔진이 같은 역할입니다. ClickHouse Cloud 에서는 같은 일을 ClickPipes 의 S3, GCS 커넥터(계속 수집 모드) 가 관리형으로 해 줍니다.
팁 S3Queue 는 "파일" 단위 at-least-once 입니다. 처리 도중 서버가 죽으면 파일을 다시 읽을 수 있으니, 저장 테이블 쪽 블록 중복 제거나
_file기반 중복 제거 설계를 함께 두세요.
Iceberg
Iceberg 테이블은 테이블 함수로 바로 읽을 수도 있고, 카탈로그를 데이터베이스로 붙여 여러 테이블을 한 번에 볼 수도 있습니다.
-- 메타데이터 위치만 알면 바로
SELECT count() FROM iceberg('https://bucket.s3.amazonaws.com/warehouse/db/orders/', lake_prod);
-- 과거 스냅샷 (25.4 부터 time travel)
SELECT count() FROM iceberg(...) SETTINGS iceberg_timestamp_ms = 1759276800000;
SELECT count() FROM iceberg(...) SETTINGS iceberg_snapshot_id = 5839234231;
-- 스냅샷 이력
SELECT * FROM system.iceberg_history WHERE table = 'orders';DataLakeCatalog 데이터베이스 엔진 은 카탈로그에 등록된 테이블을 ClickHouse 데이터베이스처럼 보여 줍니다. 25.3 에 AWS Glue 와 Unity Catalog, 25.5 에 Hive Metastore 가 들어왔고, 25.8 에 REST, Glue, Unity, Hive Metastore 카탈로그가 Beta 로 올라갔습니다.
CREATE DATABASE lake ENGINE = DataLakeCatalog('https://glue.ap-northeast-2.amazonaws.com')
SETTINGS catalog_type = 'glue', region = 'ap-northeast-2',
aws_access_key_id = '...', aws_secret_access_key = '...';
SHOW TABLES FROM lake;
SELECT * FROM lake.`sales.orders` LIMIT 10; -- 네임스페이스.테이블 을 백틱으로- 읽기는 파티션 가지치기, 매니페스트 기반 파일 건너뛰기, positional 과 equality delete 파일 적용, 스키마 진화를 지원합니다. 메타데이터 캐시(
use_iceberg_metadata_files_cache) 가 반복 쿼리를 빠르게 합니다. - 쓰기(
INSERT,DELETE,UPDATE, 스키마 변경) 는 25.8 에IcebergS3엔진으로 들어왔지만 Exp 입니다. REST 와 Glue 카탈로그를 통한 쓰기도 같은 단계입니다. 운영 적재의 기준 복사본을 Iceberg 로 쓸 계획이라면 버전별 상태를 꼭 확인하세요. - 2026년 들어 S3 Tables 와 Horizon 같은 새 카탈로그가 추가됐습니다. 2026년 주요 변화 에 정리했습니다.
Delta Lake 와 Hudi
SELECT count() FROM deltaLake('https://bucket.s3.amazonaws.com/warehouse/db/orders/', lake_prod);- 25.2 에 Databricks 의 Rust 커널(delta-kernel-rs) 기반 새 구현이 Exp 로 들어왔고(
allow_experimental_delta_kernel_rs), 이후 기본 경로가 됐습니다. Deletion vector, 컬럼 매핑 같은 최신 프로토콜 기능을 이 커널이 처리합니다. - 25.8 부터 Delta Lake 쓰기와 time travel, 25.12 부터 변경 데이터 피드(CDF) 읽기가 들어왔습니다. Unity Catalog 를 DataLakeCatalog 로 붙이면 Delta 와 Iceberg 테이블을 함께 봅니다.
- Hudi 는
hudi()함수로 읽기만 지원합니다.
레이크 쿼리를 빠르게
| 설정 또는 방법 | 효과 |
|---|---|
| Parquet 필터 푸시다운, Bloom 필터, 리더 v3 | 읽을 행 그룹과 페이지를 줄임. 입출력 포맷 참고 |
use_hive_partitioning, Iceberg 파티션 가지치기 | 파일 목록 단계에서 제외 |
병렬 복제본 또는 s3Cluster | 파일을 노드에 나눠 읽음 |
max_download_threads, max_download_buffer_size | 파일 하나를 조각내 병렬 다운로드 |
enable_filesystem_cache(캐시 디스크 구성 필요) | 자주 읽는 원격 파일 조각을 로컬에 캐시 |
schema_inference_use_cache_for_s3 | 반복 쿼리의 스키마 추론 비용 제거 |
| 자주 쓰는 데이터는 MergeTree 로 복사 | 레이크 직접 쿼리는 탐색용, 대시보드는 적재 후 |
레이크 직접 쿼리는 편하지만 MergeTree 보다 몇 배에서 수십 배 느립니다. 반복해서 보는 데이터는 INSERT INTO ... SELECT FROM s3(...) 로 적재하고, 레이크는 원본 보관과 다른 엔진과의 공유에 쓰는 구성이 가장 흔합니다.
비용에서 놓치기 쉬운 것
- LIST 요청 넓은 글롭(
**) 은 파일 목록을 전부 받아 옵니다. 파일이 수백만 개면 목록만으로 수십 초와 요청 비용이 듭니다. 경로를 날짜까지 좁히세요. - GET 요청 파일이 작을수록 행당 요청 수가 늘어납니다. 레이크 쪽에서 파일을 100 MB 안팎으로 합치는 것이 ClickHouse 설정보다 효과가 큽니다.
- 리전 간 전송 버킷과 ClickHouse 가 다른 리전이면 전송 요금이 붙습니다. ClickHouse Cloud 서비스는 버킷과 같은 리전에 만드세요.
- 스키마 추론 재실행 캐시가 꺼져 있으면 같은 경로를 쿼리할 때마다 파일 앞부분을 다시 읽습니다.