Ken
ClickHouse는 대규모 데이터 분석을 위한 초고속 OLAP 데이터베이스로, 초기에는 JOIN 기능이 매우 제한적이었으나, 지속적인 발전을 통해 현재는 표준 SQL JOIN뿐만 아니라 초대형 데이터셋에 특화된 다양한 조인 알고리즘과 최적화 기능을 지원하고 있습니다. 이 페이지에서는 2016년 오픈소스 공개 이후 2025년 최신 버전까지 ClickHouse JOIN 기능의 발전사를 기술합니다.
2016–2018: 기본 JOIN 지원과 Join 엔진 도입
ClickHouse가 처음 오픈소스로 공개됐을 때 JOIN 기능은 매우 제한적이었습니다. 한 쿼리에서 하나의 JOIN만 사용할 수 있었고, 동등 조인(=)만 지원됐으며, INNER JOIN과 LEFT OUTER JOIN만 제공되었습니다. RIGHT JOIN과 FULL OUTER JOIN은 미지원이거나 불완전했습니다.
이 시기 ClickHouse만의 독특한 개념으로 ANY JOIN이 도입되었습니다. ANY JOIN은 우측 테이블에서 일치하는 첫 번째 행만 반환하여, 다대다 관계에서 결과 중복과 성능 저하를 방지했습니다. 반면, ALL JOIN은 표준 SQL과 같이 모든 일치 행을 반환합니다. 2018년 join_default_strictness 설정이 추가되어, 기본 조인 엄격성을 ALL로 변경해 SQL 표준 호환성을 높였습니다. 또한, join_use_nulls 옵션이 도입되어 OUTER JOIN에서 매칭되지 않는 컬럼에 NULL을 반환할 수 있게 되었습니다.
2018년에는 Join Table Engine이 도입되어, 우측 테이블 데이터를 미리 메모리에 적재해 반복적인 조인에서 디스크 접근 없이 빠른 해시 테이블 조회가 가능해졌습니다. joinGet 함수도 함께 등장해, Join 엔진 테이블을 외부 딕셔너리처럼 활용할 수 있게 했습니다[1].
2019: JOIN 유형 확장과 다중 JOIN 지원
2019년은 ClickHouse JOIN 기능의 대전환기였습니다. RIGHT JOIN과 FULL OUTER JOIN이 완전히 지원되기 시작해, 표준 SQL의 모든 JOIN 유형을 사용할 수 있게 되었습니다. 이로써 데이터 반정규화 없이도 다양한 관계형 조인을 ClickHouse에서 구현할 수 있는 기반이 마련되었습니다.
또한, 한 쿼리에서 여러 개의 JOIN을 사용할 수 있게 개선되었습니다. 초기에는 실험적 옵션(allow_experimental_multiple_joins_emulation) 하에 제한적으로 제공됐으나, 곧 안정화되어 일반적인 SQL 다중 JOIN 쿼리가 가능해졌습니다.
이 해에는 시계열 분석에 최적화된 ASOF JOIN이 도입되어, 근접한 시점의 값 조인 등 복잡한 시간 기반 조인을 간결하게 구현할 수 있게 되었습니다. ASOF JOIN은 거래 시점에 가장 가까운 가격 정보 등, 정확히 일치하지 않는 키를 근사 매칭할 때 매우 유용합니다.
분산 조인(distributed_product_mode), CROSS JOIN 자동 변환, ANY JOIN의 안전성 강화 등도 이 시기 이루어졌습니다. ANY JOIN은 2019년 후반부터 RIGHT/FULL 조인에서 기본 비활성화되고, 2020년부터는 우측 테이블 키가 고유하지 않으면 에러를 내도록 변경되어 결과의 결정성을 보장했습니다.
2020–2021: ANSI SQL 호환성 강화와 Merge JOIN 알고리즘 도입
2020년에는 SQL 표준의 SEMI JOIN, ANTI JOIN이 도입되어, EXISTS/IN, NOT EXISTS/NOT IN 서브쿼리 없이도 존재성 기반 필터링 조인을 효율적으로 수행할 수 있게 되었습니다.
이 시기 가장 큰 변화는 대용량 조인에서 메모리 부족 문제를 해결하기 위해 Merge Join 계열 알고리즘이 도입된 것입니다. Partial Merge Join은 우측 테이블을 전량 정렬하고, 좌측은 블록 단위로 처리해 메모리 사용을 줄입니다. Full Sorting Merge Join은 양쪽 테이블 모두 정렬 후 병합하여, 입력 데이터가 이미 정렬돼 있다면 매우 빠른 성능을 보입니다.
join_algorithm 설정을 통해 auto/hash/partial_merge/full_sorting_merge/prefer_partial_merge 등 다양한 조인 전략을 지정할 수 있게 되었고, ClickHouse는 상황에 따라 자동으로 최적의 알고리즘을 선택할 수 있게 되었습니다.
2022–2023: 해시 조인 고도화와 Grace Hash/Direct Join 도입
2022년에는 Parallel Hash Join(병렬 해시 조인)이 도입되어, 대형 우측 테이블 조인 시 해시 테이블 구축 단계를 여러 스레드로 병렬화함으로써 속도를 크게 향상시켰습니다. 이 기능은 max_threads 설정에 따라 수십 개의 해시 테이블을 병렬 생성해, 기존 대비 수배 빠른 조인 성능을 제공합니다.
22.12 버전에는 Grace Hash Join이 추가되어, 메모리 한계를 넘는 대용량 조인에서 해시 테이블을 파티셔닝해 디스크에 저장하고 처리함으로써, 수백 GB~TB급 데이터 조인도 가능해졌습니다. Grace Hash Join은 정렬이 필요 없는 외부 해시 조인 방식으로, Partial Merge Join 대비 성능상 이점이 있습니다[2][3][4].
2023년에는 Join 엔진 테이블이나 외부 Dictionary를 활용한 Direct Join 알고리즘이 공식화되었습니다. Direct Join은 우측 테이블이 이미 메모리에 로드된 키-값 저장소 형태일 때, 별도의 해시 테이블 구축 없이 즉각적인 조회로 조인을 수행해 실시간 분석에 최적화된 성능을 제공합니다. 주로 LEFT ANY JOIN 등에 적용됩니다[5]
2024–2025: 1차 JOIN 성능 혁신 – 기반 구축
2024년: 병렬 해시 조인과 기본 최적화
2024년 ClickHouse는 JOIN 성능 개선에 본격적으로 박차를 가했습니다. Alexey Milovidov(ClickHouse 창시자)는 24.5 릴리스 웨비나에서 "이제 모든 ClickHouse 릴리스마다 JOIN 개선사항을 보게 될 것"이라고 선언했습니다.
2024년 주요 개선사항:
- 24.7: 병렬 해시 조인을 위한 해시 테이블 할당 개선
- 24.12: 병렬 해시 조인을 기본 전략으로 설정, 2-테이블 조인의 로컬 자동 재정렬 도입
- 2단계 해시맵(two-level hash map) 구조 도입으로 대용량 조인에서 30~36% 이상의 속도 개선
- JOIN 알고리즘의 build phase(구축 단계)에서 스레드 경합 제거
2025년: probe phase 최적화와 분산 조인 강화
2025년 주요 개선사항:
- 25.1: 해시 조인 probe phase(탐색 단계) 속도 향상
- 25.2: 해시 조인 build phase에서 스레드 경합 제거로 병렬 처리 효율 개선
- 멀티스레드 파이프라인 최적화, 메모리 사용량 감소
분산 조인 최적화:
- 분산 테이블의 셔딩 키가 일치하는 경우: 각 샤드에서 로컬 조인을 수행하고 결과를 합치는 방식
- 셔딩 키가 다른 경우: 조인 키 기준으로 리셔플(재분배)하여 효율적으로 처리
- 대규모 분산 클러스터 환경에서 JOIN 성능과 네트워크 효율이 크게 향상
1년차 성과 (22.4 → 25.4):
TPC-H SF100 조인 중심 워크로드에서 4.4배 빠른 성능 달성[1][3][6]
ClickHouse JOIN 기능 도입 연혁 요약
기능/유형 | 도입 시기(버전) | 주요 특징 및 발전 |
ANY/ALL JOIN | 2016년~ | ANY: 중복 억제, ALL: 표준 동작, 2018년 ALL 기본값 변경 |
INNER/LEFT OUTER JOIN | 2016년~ | 기본 지원, 2018년 join_use_nulls 도입 |
RIGHT/FULL OUTER JOIN | 2018~2019년 | 표준 SQL 완전 지원 |
CROSS JOIN | 2016년~ | 컴마 구문→CROSS로 표준화(2019) |
Join Table Engine | 2018년 | 메모리 해시 테이블 사전 적재, joinGet 함수 |
Distributed JOIN | 2016년~ | local/global 모드, 2025년 동적 셔플 최적화 |
다중 JOIN | 2019년 | 한 쿼리 내 여러 JOIN 지원 |
ASOF JOIN | 2019년 | 근사 시점 조인, 시계열 분석 특화 |
SEMI/ANTI JOIN | 2020년 | EXISTS/NOT EXISTS 대체, SQL 표준화 |
Hash Join | 2016년~ | 기본 해시 테이블 기반 조인 |
Parallel Hash Join | 2022년 | 해시 테이블 병렬 구축, 대용량 조인 가속 |
Grace Hash Join | 2022년 | 디스크 기반 외부 해시 조인, 초대형 데이터 지원 |
Partial/Full Merge Join | 2020년 | 정렬 기반 병합 조인, 메모리 절감 |
Direct Join | 2023년 | 사전 적재된 키-값 구조 즉시 조회 |
2025–2026: 2차 JOIN 성능 혁신 – 기본 설정으로 경쟁력 확보
26배 성능 향상의 배경
2024~2026년 2년간의 집중적인 JOIN 엔지니어링을 통해 ClickHouse는 TPC-H SF100 조인 중심 워크로드에서 26배 빠른 성능을 달성했습니다.
성능 향상 추이:
- 1년차 (22.4 → 25.4): 4.4배 빠름 (기반 구축)
- 2년차 (25.4 → 26.4): 추가 6배 빠름 (기본 설정 최적화)
- 총합 (22.4 → 26.4): 26배 빠름
2년차의 핵심 목표는 "기본 설정으로 경쟁력 확보"였습니다. 사용자가 쿼리를 재작성하거나, 조인 순서를 수동으로 조정하거나, 내부 최적화 옵션을 알 필요 없이, ClickHouse가 자동으로 조인 중심 SQL을 인식하고 최적의 플랜을 선택하며 불필요한 작업을 회피하도록 했습니다.
① Correlated Subqueries in JOINs: 실전 SQL 완전 지원
문제점: 2년 전까지 ClickHouse는 correlated subquery(상관 서브쿼리)를 완전히 지원하지 못했습니다. VLDB 2024 논문에서 TPC-H 벤치마크의 7개 쿼리(Q2, Q4, Q13, Q17, Q20-Q22)를 제외해야 했던 이유가 바로 이것입니다.
Correlated Subquery란?
- 내부 쿼리가 외부 쿼리의 테이블을 참조하는 서브쿼리
- SQL 작성이 자연스럽고, AI 에이전트가 생성하는 SQL에서도 흔히 사용됨
- 하지만 데이터베이스 엔진이 효율적으로 실행하기 매우 어려움
TPC-H Q4 예시:
SELECT o_orderpriority, count(*) AS order_count
FROM orders
WHERE o_orderdate >= DATE '1993-07-01'
AND o_orderdate < DATE '1993-10-01'
AND EXISTS (
SELECT *
FROM lineitem
WHERE l_orderkey = o_orderkey -- 외부 쿼리의 orders 참조
AND l_commitdate < l_receiptdate
)
GROUP BY o_orderpriority
ORDER BY o_orderpriority;해결책: ClickHouse는 이제 correlated subquery를 자동으로 decorrelate(비상관화)하여 set-oriented plan(조인, 집계, semi join, anti join 등)으로 재작성합니다. 이 재작성은 SQL 의미론(중복, 집계, NULL 처리)을 정확히 보존하면서도 row-by-row 실행 대신 효율적인 집합 연산으로 변환합니다.
결과:
Correlated subquery가 이제 ClickHouse의 일급 SQL 기능입니다. 수동 재작성이 필요했던 쿼리들(TPC-H 제외 쿼리 포함)이 이제 직접 실행됩니다.
② Lazy Column Replication: 불필요한 복사 회피
문제점: JOIN 결과에서 하나의 입력 행이 여러 출력 행을 생성할 때, 논리적으로 반복되는 값들이 물리적으로 복사됩니다.
예시:
SELECT o.o_orderkey, o.o_orderdate,
c.c_name, c.c_address, c.c_comment
FROM orders AS o
INNER JOIN customer AS c ON o.o_custkey = c.c_custkey;한 고객(customer)이 여러 주문(orders)을 가질 때, c_name, c_address, c_comment 같은 넓은 컬럼들이 각 주문마다 반복 복사됩니다. 이후 집계 등에서 대부분 행이 버려진다면, 이 복사는 불필요한 낭비입니다.
해결책: Lazy column replication은 반복되는 값을 즉시 복사하지 않고, 작은 딕셔너리에 원본을 한 번만 저장하고 조인된 컬럼을 인덱스 값으로 표현합니다. 나중 쿼리 단계가 완전히 구체화된 컬럼이 필요하면 그때 생성하지만, 많은 분석 연산은 압축된 표현으로 직접 작업할 수 있어 반복 값을 전혀 복사하지 않습니다.
벤치마크 (TPC-H SF100):
-- Lazy replication 비활성화
SELECT sum(cityHash64(t1.o_comment))
FROM orders AS t1
INNER JOIN orders AS t2 ON t1.o_custkey = t2.o_custkey
SETTINGS enable_lazy_columns_replication = 0;
-- 결과: 5.419초, 메모리 5.27 GiB
-- Lazy replication 활성화
SETTINGS enable_lazy_columns_replication = 1;
-- 결과: 2.847초, 메모리 5.22 GiB결과:
Lazy column replication으로 조인이 1.9배 빠름 (5.419s → 2.847s), 메모리도 약간 감소 (5.27 GiB → 5.22 GiB)
③ Runtime Filters in JOINs: 불필요한 Probe 작업 제거
문제점: 병렬 해시 조인에서 build side(우측 테이블)가 필터링되어 일부 키만 해시 테이블에 들어가더라도, probe side(좌측 테이블)는 모든 행을 처리합니다. 매칭될 수 없는 행도 해시 테이블 조회까지 가서야 거부되므로 CPU와 메모리 대역폭이 낭비됩니다.
해결책: Runtime filter는 해시 테이블 구축 중에 실제로 들어간 조인 키들로부터 작은 블룸 필터 또는 min/max 값을 생성합니다. 이 필터는 해시 테이블보다 훨씬 작아 CPU 캐시(L1/L2)에 근접하게 유지되며, probe side 행이 해시 테이블 조회 전에 먼저 이 필터를 통과합니다. 필터를 통과하지 못한 행은 즉시 버려집니다.
쿼리 플랜에서의 모습:
EXPLAIN plan
SELECT * FROM orders, customer
WHERE o_custkey = c_custkey
SETTINGS enable_join_runtime_filters = 1;플랜 출력:
Join
Prewhere filter: __filterContains(_runtime_filter_..., __table1.o_custkey)
BuildRuntimeFilter (Build runtime join filter on __table2.c_custkey)ClickHouse는 먼저 우측(customer) 조인 키로부터 runtime filter를 구축하고, 이를 좌측(orders) 테이블에 PREWHERE 필터로 적용하여 조인 실행 전에 무관한 행을 건너뜁니다.
벤치마크 (TPC-H SF100, orders-customer-nation 3-way join):
-- Runtime filter 비활성화
SELECT avg(o_totalprice)
FROM orders, customer, nation
WHERE (c_custkey = o_custkey) AND (c_nationkey = n_nationkey)
AND (n_name = 'FRANCE')
SETTINGS enable_join_runtime_filters = 0;
-- 결과: 1.005초, 메모리 1.24 GiB
-- Runtime filter 활성화
SETTINGS enable_join_runtime_filters = 1;
-- 결과: 0.471초, 메모리 185.18 MiB결과:
Runtime filter로 쿼리가 2.1배 빠름 (1.005s → 0.471s), 메모리는 약 7배 감소 (1.24 GiB → 185 MiB)
④ Statistics-based Join Reordering: 자동 최적 플랜 선택
문제점: SQL 조인은 결합법칙(associative)이 성립하므로 동일한 논리 쿼리를 다양한 조인 순서로 실행할 수 있습니다. 결과는 같지만 실행 시간은 수 배~수천 배 차이날 수 있습니다. 12개 테이블 조인의 경우 가능한 조인 순서는 28조 개입니다.
해결책: ClickHouse는 조인 순서 옵티마이저를 사용하여 후보 조인 트리를 열거하고, 비용을 추정하여 좋은 순서를 선택합니다.
핵심: 컬럼 통계
옵티마이저는 cardinality 추정(필터와 조인 술어 적용 후 각 중간 조인 결과가 예상되는 행 수)이 필요합니다. 이 추정치는 컬럼 통계에서 나옵니다.
26.4 버전부터: ClickHouse는 모든 테이블에 대해 자동으로 컬럼 통계를 생성하므로, 기본 설정으로 statistics-based join reordering이 효과적으로 작동합니다.
조인 그래프 크기에 따른 전략:
- 작은 조인 그래프: 완전 동적 프로그래밍(DPSize)으로 최적 순서 탐색
- 큰 조인 그래프: 그리디 탐색(더 빠르지만 최적 보장 안 됨)
벤치마크 (TPC-H SF100, 6-테이블 조인):
-- 통계 없는 테이블
USE tpch_no_stats;
SET query_plan_optimize_join_order_limit = 10;
SET allow_statistics_optimize = 1;
-- 결과: 3903.678초 (약 1시간), 메모리 99.12 GiB
-- 통계 있는 테이블
USE tpch_stats;
-- 결과: 2.702초, 메모리 3.94 GiB결과:
Statistics-based join reordering으로 6-테이블 TPC-H 쿼리가 약 1,450배 빠름 (3903.7s → 2.7s), 메모리는 99.1 GiB → 3.9 GiB로 감소
2년간의 성과 요약
1년차 (22.4 → 25.4): 기반 구축
- 빠른 병렬 해시 조인
- 스마트한 build/probe side 선택
- 더 나은 플래닝, 공격적인 필터 pushdown
- 로컬 조인 재정렬
- 결과: TPC-H SF100에서 4.4배 빠름
2년차 (25.4 → 26.4): 기본 설정 최적화
- ① Correlated subqueries: SQL 호환성 격차 제거
- ② Lazy column replication: 1.9배 빠름
- ③ Runtime filters: 2.1배 빠름, 메모리 7배 감소
- ④ Statistics-based join reordering: 1,450배 빠름 (6-테이블 조인)
- 결과: 추가 6배 빠름
총합 (22.4 → 26.4):
ClickHouse는 조인 중심 워크로드에서 26배 빠른 성능을 달성했으며, ClickHouse Cloud는 이제 TPC-H SF100 전체를 1센트 미만 비용으로 실행 가능합니다.
향후 계획: 3년차 분산 조인과 그 이상
ClickHouse는 조인에서 큰 진전을 이루었지만, 이제 막 시작입니다.
Alexey Milovidov는 ClickHouse 25.10 릴리스 웨비나에서 이렇게 말했습니다:
"언제 조인 성능 최적화를 멈출 것인가? 우리는 절대 멈추지 않을 것입니다!"
3년차 로드맵:
- 분산 조인(Distributed Joins) 강화
- TPC-H SF1000 및 그 이상의 대규모 워크로드 지원
- 지속적인 알고리즘 최적화와 자동화 개선
결론
ClickHouse의 JOIN 기능은 2016년 초기의 제한적인 지원에서 출발해, 2024~2026년 2년간의 집중적인 엔지니어링을 통해 26배 빠른 성능을 달성하며 OLAP 데이터베이스의 조인 성능 기준을 새롭게 정의했습니다.
발전의 핵심 마일스톤:
- 2016~2019: ANY/ALL JOIN, 다중 JOIN, RIGHT/FULL OUTER JOIN, ASOF JOIN 등 SQL 표준 완전 지원
- 2020~2023: Parallel Hash Join, Grace Hash Join, Merge Join, Direct Join 등 6종 이상의 조인 알고리즘 도입
- 2024~2025: 병렬 해시 조인 기본화, 자동 조인 재정렬, probe phase 최적화 → 4.4배 성능 향상
- 2025~2026: Correlated subqueries, lazy column replication, runtime filters, statistics-based join reordering → 추가 6배 성능 향상
현재 ClickHouse JOIN의 강점:
- SQL 표준 완전 지원 (correlated subquery 포함)
- 6종 이상의 조인 알고리즘 자동 선택
- 기본 설정으로 경쟁력 있는 성능 (수동 튜닝 불필요)
- TPC-H SF100을 1센트 미만 비용으로 실행 가능
- 초대형 데이터 환경에서도 JOIN 제약 없이 빠르고 안정적인 분석
미래 비전:
"우리는 절대 멈추지 않을 것입니다" - ClickHouse 팀은 분산 조인 강화, TPC-H SF1000 이상의 워크로드 지원, 지속적인 알고리즘 최적화를 통해 조인 성능의 한계를 계속 넓혀갈 것입니다.
ClickHouse의 JOIN 진화는 OLAP 데이터베이스가 복잡한 관계형 쿼리에서도 최고의 성능을 낼 수 있음을 증명한 기술적 성취입니다.