10년의 릴리스 중 가장 큰 숫자
2026년 7월, ClickHouse가 10주년을 맞아 26.6을 공개했다. 56개 새 기능, 79개 성능 최적화, 366개 버그 수정이 담겼다. 버전 번호가 연도.월 방식이라 26.6은 2026년 6월 릴리스를 뜻한다. 숫자 자체보다 중요한 것은 이번 릴리스가 운영자가 오랫동안 원하던 기능 세 가지를 함께 처음 내놓았다는 점이다.
- 인덱스를 실제로 만들기 전에 효과를 미리 측정하는 가상 skip 인덱스
- 독립 스케줄로 뒤처지던 다단계 Materialized View를 종속 갱신으로 연결
- 중첩 쿼리 분석 비용을 줄여 동일 쿼리를 약 3× 빠르게 실행
세 기능은 공통 배경을 공유한다. ClickHouse를 실제 운영하면서 만나는 반복적인 병목이다. 인덱스가 효과가 있는지 사전에 알 방법이 없었고, RMV(Refreshable Materialized View) 체인이 한 단계 늦으면 다음 단계가 낡은 데이터를 읽었으며, 복잡한 쿼리를 던지면 분석 시간이 길어졌다.
이 장은 각 기능의 동작 원리, 운영 경계, 그리고 도입 시 확인해야 할 사항을 다룬다.
이 장은 ClickHouse 26.6 공식 릴리스 블로그, changelog, 관련 기술 자료를 기준으로 한다. 실험적(Experimental) 표시가 붙은 기능은 별도 표기한다.
가상 skip 인덱스: 만들기 전에 먼저 측정한다
ClickHouse의 skip 인덱스(data skipping index)는 특정 컬럼 표현식에 대한 집계를 granule 단위로 저장해, 쿼리가 조건과 일치할 수 없는 granule을 통째로 건너뛰게 한다. bloom filter, minmax, set, ngrambf_v1 등 여러 타입이 있다.
문제는 효과가 있는지 알려면 인덱스를 실제로 만들어봐야 했다는 것이다. 수백 GB 테이블에 bloom filter를 추가하면 materialization에 시간과 공간이 필요하다. 인덱스를 만든 뒤 EXPLAIN indexes=1로 확인했더니 granule 건너뜀이 없다면, 시간과 공간을 낭비하고 되돌려야 한다.
EXPLAIN WHATIF와 가상 인덱스
26.6은 다음 흐름을 가능하게 한다.
-- 1. 세션 범위 가상 인덱스 정의 (실제 테이블에 기록되지 않음)
CREATE HYPOTHETICAL INDEX idx_bloom
ON events (action) TYPE bloom_filter(0.01);
-- 2. 해당 인덱스가 있다고 가정하고 쿼리 분석
EXPLAIN WHATIF
SELECT count() FROM events WHERE action = 'purchase';
-- 3. 가상 인덱스 목록 확인
SELECT * FROM system.hypothetical_indexes;EXPLAIN WHATIF는 실제로 쿼리를 실행하지 않는다. 가상 인덱스가 존재한다고 가정한 실행 계획을 보여주고, 예상 skip ratio와 비용 절감을 함께 제공한다. 테이블에 아무 변경도 없다.
가상 인덱스는 세션 범위다. 연결이 끊어지면 사라진다. 여러 타입과 표현식을 반복해 측정하고, 결과가 납득될 때만 ALTER TABLE ... ADD INDEX로 실체화한다.
skip 인덱스 타입 선택 기준
| 타입 | 적합한 컬럼 | 주의점 |
|---|---|---|
bloom_filter(p) | 고 카디널리티 문자열, UUID | FPR p가 작을수록 인덱스 크기 증가 |
minmax | 날짜, 숫자, 정렬 경향이 있는 값 | 범위가 넓은 granule에 효과 없음 |
set(max_values) | 낮은 카디널리티 enum·상태값 | max_values 초과 시 항상 포함으로 처리 |
ngrambf_v1 | LIKE 검색 텍스트 | 짧은 문자열은 효과 제한적 |
tokenbf_v1 | 단어 단위 텍스트 검색 | 언어 패턴에 따라 달라짐 |
26.6 이전에는 이 선택을 실험 없이 해야 했다. 이제는 여러 타입을 CREATE HYPOTHETICAL INDEX로 반복해 EXPLAIN WHATIF로 skip ratio를 비교한 뒤 결정할 수 있다.
종속 Refreshable Materialized View: 체인이 시계처럼 돌아가게
RMV(Refreshable Materialized View)는 ClickHouse의 매우 유용한 기능이다. REFRESH EVERY 1 HOUR처럼 주기를 정하면 ClickHouse가 알아서 쿼리를 재실행하고 결과를 갱신한다.
문제는 다단계 집계 체인에서 드러난다.
raw_events (실시간 데이터)
→ stage1_rmv (REFRESH EVERY 1 HOUR: 시간별 집계)
→ stage2_rmv (REFRESH EVERY 1 HOUR: 일별 요약)
→ stage3_rmv (REFRESH EVERY 1 HOUR: 주별 리포트)세 RMV가 각자 독립적인 시간 기반 스케줄을 갖는다. stage1_rmv가 00:05에 완료됐는데 stage2_rmv가 00:02에 이미 실행되면, stage2는 이전 주기의 낡은 데이터를 읽는다. 스케줄이 조금만 어긋나도 1시간 데이터 지연이 생길 수 있다.
REFRESH DEPENDS ON
26.6은 시간 기반 스케줄을 다른 RMV의 갱신 이벤트로 대체한다.
-- stage1: 원본 데이터를 시간별로 집계
CREATE MATERIALIZED VIEW stage1_rmv
REFRESH EVERY 1 HOUR
AS SELECT toStartOfHour(ts) AS hour, sum(value) AS total
FROM raw_events GROUP BY hour;
-- stage2: stage1이 갱신된 직후에만 실행
CREATE MATERIALIZED VIEW stage2_rmv
REFRESH EVERY 1 HOUR DEPENDS ON stage1_rmv
AS SELECT toDate(hour) AS day, sum(total) AS day_total
FROM stage1_rmv GROUP BY day;
-- stage3: stage2가 갱신된 직후에만 실행
CREATE MATERIALIZED VIEW stage3_rmv
REFRESH EVERY 1 HOUR DEPENDS ON stage2_rmv
AS SELECT toStartOfWeek(day) AS week, sum(day_total) AS week_total
FROM stage2_rmv GROUP BY week;DEPENDS ON은 두 가지를 보장한다.
stage1_rmv가 완료된 뒤에만stage2_rmv가 실행된다.stage2_rmv가 완료된 뒤에만stage3_rmv가 실행된다.
시간 트리거를 유지하면서 실행 순서를 강제하는 구조다. REFRESH EVERY 1 HOUR는 여전히 실행 주기의 하한을 정하지만, 실제 실행은 dependency가 완료된 뒤다.
이전과의 차이
| 항목 | 26.5 이하 | 26.6 DEPENDS ON |
|---|---|---|
| 실행 트리거 | 각자 독립 시간 타이머 | upstream RMV 갱신 완료 |
| 데이터 신선도 | upstream 지연 + 타이머 오차 | upstream 완료 직후 실행 |
| 스케줄 드리프트 | 장기 운영 시 오차 누적 가능 | 구조적으로 방지 |
| 실패 전파 | upstream 실패와 무관하게 진행 | upstream 실패 시 downstream도 대기 |
마지막 항목은 주의가 필요하다. stage1_rmv가 실패하면 stage2_rmv는 실행을 기다린다. 자동 재시도 설정이 없으면 체인 전체가 멈출 수 있다. 장기 대기 상태를 모니터링하고 알림을 연결해야 한다.
중첩 쿼리 3× 지연 개선
26.6에서 측정된 수치다.
동일 쿼리:
- ClickHouse 26.5: 최적 98ms
- ClickHouse 26.6: 최적 30ms
약 3.3× 개선대상은 깊이 중첩된 서브쿼리다. ClickHouse 26.5까지는 복잡한 중첩 구조를 분석할 때 쿼리 분석기(analyzer) 내부 비용이 실행 비용과 비슷하게 커지는 경우가 있었다. 26.6은 이 분석 경로를 더 효율적으로 만들었다.
어떤 쿼리가 해당하는가?
-- 전형적인 중첩 집계 패턴
SELECT user_id, total_value
FROM (
SELECT user_id, sum(day_value) AS total_value
FROM (
SELECT user_id, toDate(ts) AS day, sum(amount) AS day_value
FROM (
SELECT user_id, ts, amount
FROM events
WHERE status = 'completed'
) GROUP BY user_id, day
) GROUP BY user_id
) WHERE total_value > 1000;CTE, 다단계 집계, 여러 단계의 필터링이 결합된 분석 쿼리에서 이 개선이 유효하다. 단순한 SELECT나 한 단계 JOIN은 이 최적화의 주요 대상이 아니다.
실제 워크로드에 얼마나 적용되는지는 EXPLAIN으로 중첩 깊이를 확인하고 26.5와 26.6에서 실행 시간을 비교해 측정해야 한다. 내 쿼리가 26.6 최적화 경로에 해당하는지는 직접 측정 없이 알 수 없다.
system.documentation: 서버에서 직접 문서를 쿼리한다
ClickHouse 26.6은 system.documentation 테이블을 추가했다. 이 테이블은 ClickHouse의 레퍼런스 매뉴얼을 서버 내부에 저장하고, SQL로 검색할 수 있게 한다.
-- 특정 함수 문서 검색
SELECT name, description, example
FROM system.documentation
WHERE name LIKE '%bloom%'
ORDER BY name;
-- built-in CLI help도 같은 내용을 참조
\h bloom_filter실용적인 용도는 두 가지다. 첫째, ClickHouse 버전별로 지원 함수와 설정 키가 다를 때 클라이언트 연결만으로 현재 서버 기준 문서를 확인할 수 있다. 둘째, WHERE name = 'current_feature'로 찾은 결과를 다른 쿼리와 JOIN하거나, BI 도구에서 함수 목록을 자동으로 가져오는 데 활용할 수 있다.
실험적 기능: Continuous Query
26.6에는 실험적(Experimental) 표시가 붙은 continuous query가 포함됐다. 일반 쿼리는 한 번 실행되고 끝나지만, continuous query는 지정된 Kafka·MaterializeMySQL 같은 스트리밍 소스를 읽으면서 지속 실행된다.
-- 실험적 기능 활성화 필요
SET allow_experimental_continuous_query = 1;
CREATE CONTINUOUS QUERY cq_sensor_agg
TO sensor_hourly
AS SELECT sensor_id, toStartOfHour(ts) AS hour, avg(value) AS avg_val
FROM sensor_stream
GROUP BY sensor_id, hour;이 기능은 Kafka 소비와 집계를 하나의 선언으로 표현한다. 이전까지는 MaterializeMySQL이나 Kafka 테이블 엔진 + Materialized View 조합으로 비슷한 효과를 얻었는데, 해당 구조의 명시적 대안이 생긴다.
주의: 실험적 기능이므로 API와 동작이 마이너 릴리스에서 바뀔 수 있다. 프로덕션 사용 전 해당 patch release의 release note와 known issue를 반드시 확인한다.
26.6 업그레이드 체크리스트
CREATE HYPOTHETICAL INDEX로 후보 인덱스를 세션에 정의하고 EXPLAIN WHATIF로 skip ratio를 측정한다.system.view_refreshes에서 확인한다.REFRESH EVERY ... DEPENDS ON으로 선언하고 테스트 환경에서 실행 순서를 확인한다.system.view_refreshes의 last_refresh_result를 모니터링한다.EXPLAIN으로 확인한다. 옵티마이저 변화로 기존과 다른 조인 순서나 집계 경로가 선택될 수 있다.모니터링 쿼리 모음
업그레이드 후 확인해야 할 핵심 지표를 쿼리로 표현한다.
-- RMV 갱신 상태 확인 (DEPENDS ON 체인 포함)
SELECT view_name, last_refresh_time, last_refresh_result,
next_refresh_time, exception
FROM system.view_refreshes
ORDER BY last_refresh_time DESC;
-- 가상 인덱스 목록 (세션 내)
SELECT * FROM system.hypothetical_indexes;
-- skip 인덱스 granule 건너뜀 통계
SELECT table, index_name, skipped_granules, total_granules,
round(skipped_granules / total_granules * 100, 1) AS skip_pct
FROM system.parts_columns
WHERE index_name != '';
-- 중첩 쿼리 실행 시간 분포 (query_log 기반)
SELECT
query_duration_ms,
read_rows,
memory_usage
FROM system.query_log
WHERE query LIKE '%FROM (%FROM (%'
AND event_date = today()
ORDER BY query_duration_ms DESC
LIMIT 20;정리
ClickHouse 26.6은 세 가지 문제를 동시에 해결한다.
가상 skip 인덱스는 "인덱스가 효과가 있는지 만들어보기 전에 알 수 없다"는 한계를 없앤다. skip ratio를 세션 내에서 측정하고 납득된 뒤에만 실체화한다. 잘못된 인덱스를 만들고 되돌리는 반복 비용이 사라진다.
DEPENDS ON RMV 체인은 독립 타이머로 인한 스케줄 드리프트를 구조적으로 막는다. upstream이 완료된 직후 downstream이 실행되므로 다단계 집계의 신선도가 유지된다. 단, upstream 실패 알림을 빠뜨리면 체인 전체가 조용히 멈출 수 있다.
중첩 쿼리 최적화는 특정 패턴에서 약 3× 지연 개선을 가져온다. 내 쿼리가 해당하는지는 26.5와 비교 측정이 필요하다. 일반화해서 "모든 쿼리가 빨라진다"고 말하지 않는다.
운영자가 26.6을 적용할 때 가장 먼저 할 일은 테스트 환경에서 대표 쿼리와 RMV 체인을 재현하는 것이다. 366개 버그 수정이 기대한 쪽뿐 아니라 예상하지 못한 동작을 바꿀 수도 있다.
References
- ClickHouse Release 26.6 — Official Blog
- ClickHouse 26.6 X/Twitter Announcement: 56 new features, 79 perf optimizations
- ClickHouse Changelog 2026
- ClickHouse End of Life Dates — 26.6.1.1193
- This Week in Databases: ClickHouse 25.6 Delivers Consistency
- ClickHouse® Black Magic: Skipping Indices — Altinity
- How ClickHouse became fast at joins — ClickHouse Engineering Blog