
ClickHouse๋ฅผ ์ฒ์ ์ฐ๋ค ๋ณด๋ฉด ํ ๋ฒ์ฏค ์ด๋ฐ ๊ฒฝํ์ ํ๋ค. MySQL์์๋ ์๋ฌด ๋ฌธ์ ์์ด ๋์๊ฐ๋ JOIN ์ฟผ๋ฆฌ๊ฐ ClickHouse์์๋ ์ ๋ ๋๋ฆฌ๊ฑฐ๋, WHERE ์กฐ๊ฑด์ ๋ค ๊ฑธ์๋๋ฐ๋ ํ์ค์บ ์์ค์ ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ ๊ฒ์ด๋ค. ์ด ๊ธ์์๋ ๊ทธ ์ด์ ๋ฅผ ClickHouse์ ๋ด๋ถ ๋์ ๋ฐฉ์์์ ์ฐพ๊ณ , ์ค์ ๋ก ์ธ ์ ์๋ ์ต์ ํ ์ ๋ต์ ์ ๋ฆฌํ๋ค.
์ด๋ค ์ํฉ์ธ๊ฐ
์์ต ๊ฑด์ ์ ์ ํ๋ ๋ก๊ทธ(page_view_log)์ ์ฃผ๋ฌธ ๋ฐ์ดํฐ(order_log)๋ฅผ JOINํด์ ํ์ด์ง๋ฅผ ๋ณธ ํน์ ๊ธฐ๊ฐ, ํน์ ์ ์ ์ ๊ตฌ๋งค ๊ฑด์๋ฅผ ์ง๊ณํ๋ค๊ณ ํด๋ณด์. ์๋์ฒ๋ผ ์ฐ๋ ๊ฒ์ด ์์ฐ์ค๋ฝ๋ค.
SELECT count(*)
FROM default.page_view_log p
JOIN default.order_log o
ON p.user_id = o.user_id
AND p.session_id = o.session_id
WHERE o.order_date BETWEEN '20260604' AND '20260610'
AND o.user_id = 'user_001'
๊ทธ๋ฐ๋ฐ ์ด ์ฟผ๋ฆฌ๋ ์ ๋ ๋๋ฆฌ๋ค.
์ค์ ๋ก ์ผ์ชฝ ํ ์ด๋ธ ์ฝ 3์ต ๊ฑด, ์ค๋ฅธ์ชฝ ํ ์ด๋ธ ์ฝ 7์ฒ๋ง ๊ฑด์ ๋ฐ์ดํฐ๋ฅผ ์์ ์ฟผ๋ฆฌ๋ก ์กฐํํ์ ๋ ์ฝ 1๋ถ 40์ด๊ฐ ๊ฑธ๋ ธ๋ค.

๋ฐ๋ฉด ์๋์ฒ๋ผ ์๋ธ์ฟผ๋ฆฌ๋ก ๋จผ์ ํํฐ๋งํ๋ฉด ์ฒด๊ฐ์ด ๋ค๋ฅผ ์ ๋๋ก ๋น ๋ฅด๊ฒ ์คํ๋๋ค.
SELECT count(*)
FROM
(SELECT * FROM default.order_log
WHERE order_date BETWEEN '20260604' AND '20260610'
AND user_id = 'user_001'
) O
INNER JOIN (
SELECT * FROM default.page_view_log
WHERE user_id = 'user_001'
AND created_at > '20260504'
) P ON P.user_id = O.user_id AND P.session_id = O.session_id
์ค์ ๋ก ์์ ๊ฐ์ ๋ ์ฟผ๋ฆฌ๋ก ๋ค์ ์กฐํํ์ ๋ ์ฝ 0.6์ด๊ฐ ๊ฑธ๋ ธ๋ค.

๋ ์ฟผ๋ฆฌ์ ๋ ผ๋ฆฌ์ ๊ฒฐ๊ณผ๋ ๊ฐ๋ค. ๊ทธ๋ฐ๋ฐ ์ ์คํ ์๊ฐ์ด ๋ค๋ฅผ๊น?
์ด์ 1 - ClickHouse๋ Predicate Pushdown์ ์๋์ผ๋ก ํ์ง ์๋๋ค
MySQL, PostgreSQL ๊ฐ์ RDBMS๋ Cost-Based Optimizer(CBO) ๋ฅผ ํ์ฌํ๊ณ ์๋ค. ํ ์ด๋ธ ํต๊ณ๋ฅผ ๋ฐํ์ผ๋ก "์ด๋ค ์์๋ก ์คํํ๋ฉด ๋น์ฉ์ด ๊ฐ์ฅ ๋ฎ์์ง"๋ฅผ ๊ณ์ฐํ๊ณ , WHERE ์กฐ๊ฑด์ JOIN ์ด์ ๋จ๊ณ๋ก ๋ฐ์ด ๋ฃ๋ Predicate Pushdown์ ์๋์ผ๋ก ์ํํ๋ค.
Cost-Based Optimizer


CBO๋ ํ ์ด๋ธ ํต๊ณ๋ฅผ ๋ฐํ์ผ๋ก ์คํ ๋น์ฉ์ ๊ณ๋ํํด์, ๊ฐ์ฅ ๋ฎ์ ๋น์ฉ์ ์คํ ๊ณํ์ ์๋์ผ๋ก ์ ํํ๋ ์ตํฐ๋ง์ด์ ๋ค. ํ ์ด๋ธ ํ ์, ์ปฌ๋ผ ์นด๋๋๋ฆฌํฐ, ๊ฐ์ ๋ถํฌ(ํ์คํ ๊ทธ๋จ), ์ธ๋ฑ์ค ์ ๋ฌด ๊ฐ์ ํต๊ณ ์ ๋ณด๋ฅผ ํ์ฉํด "A ๋ฐฉ๋ฒ์ ์์ ๋น์ฉ 1200, B ๋ฐฉ๋ฒ์ ์์ ๋น์ฉ 340" ์์ผ๋ก ๊ณ์ฐํ ๋ค ์ต์ ์์๋ฅผ ๊ฒฐ์ ํ๋ค.
์ด๊ฒ ํ์ํ ์ด์ ๋ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ ๋ฐฉ๋ฒ์ด ํ๋๊ฐ ์๋๊ธฐ ๋๋ฌธ์ด๋ค. ์๋ ์ฟผ๋ฆฌ ํ๋๋ง ํด๋ ์คํ ์์๊ฐ ์ฌ๋ฌ ๊ฐ์ง๋ค.
SELECT * FROM orders o JOIN users u ON o.user_id = u.id
WHERE o.created_at > '2026-01-01' AND u.country = 'KR'
- orders๋ฅผ ๋จผ์ ํํฐ๋งํ ๋ค users์ JOINํ๊ฑฐ๋
- users๋ฅผ ๋จผ์ ํํฐ๋งํ ๋ค orders์ JOINํ๊ฑฐ๋
- ๋ ํ ์ด๋ธ์ ํต์งธ๋ก JOINํ ๋ค์ WHERE๋ฅผ ์ ์ฉํ๊ฑฐ๋
์ด๋ค ์์๊ฐ ๋น ๋ฅธ์ง๋ ๋ฐ์ดํฐ ๋ถํฌ์ ๋ฐ๋ผ ๋ค๋ฅด๋ค. country = 'KR'์ด ์ ์ฒด ์ ์ ์ 90%๋ผ๋ฉด users ํํฐ๋ ๋ณ ๋์์ด ์ ๋๊ณ , created_at ์กฐ๊ฑด์ด orders๋ฅผ 1%๋ก ์ค์ธ๋ค๋ฉด ๊ฑฐ๊ธฐ์ ๋จผ์ ํํฐ๋งํ๋ ๊ฒ ํจ์ฌ ์ ๋ฆฌํ๋ค. CBO๋ ์ด ํ๋จ์ ์ฌ๋ ๋์ ์๋์ผ๋ก ํด์ค๋ค.
Predicate Pushdown
"Predicate(์กฐ๊ฑด)๋ฅผ Pushdown(๋ฐ์ด ๋ฃ๋๋ค)"์ด๋ผ๋ ์ด๋ฆ ๊ทธ๋๋ก, WHERE ์กฐ๊ฑด์ ๊ฐ๋ฅํ ํ ๋ฐ์ดํฐ ์์ค์ ๊ฐ๊น๊ฒ ๋ฐ์ด ๋ฃ์ด ์ผ์ฐ ํํฐ๋งํ๋ ์ต์ ํ๋ค. ๋ฐ์ดํฐ๋ ๋ฆ๊ฒ ๊ฑธ๋ฌ๋ผ์๋ก ์ํด๋ค. 100๋ง ๊ฑด์ JOINํ ๋ค 1๋ง ๊ฑด๋ง ๋จ๊ธฐ๋ ๊ฒ๋ณด๋ค, ๋จผ์ 1๋ง ๊ฑด์ผ๋ก ์ค์ธ ๋ค JOINํ๋ ๊ฒ ๋น์ฐํ ๋น ๋ฅด๋ค.
-- ๊ฐ๋ฐ์๊ฐ ์์ฑํ ์ฟผ๋ฆฌ
SELECT * FROM orders o JOIN users u ON o.user_id = u.id
WHERE o.created_at > '2026-01-01' AND u.country = 'KR'
-- ์ตํฐ๋ง์ด์ ๊ฐ Predicate Pushdown ์ ์ฉ ํ ์คํํ๋ ํํ
SELECT * FROM
(SELECT * FROM orders WHERE created_at > '2026-01-01') o
JOIN
(SELECT * FROM users WHERE country = 'KR') u
ON o.user_id = u.id
JOIN ์ ์ ์์ชฝ์ ๊ฐ์ ์ค์ฌ๋์ผ๋, ๋ถ์ฌ์ผ ํ ๋ฐ์ดํฐ ์์ฒด๊ฐ ์์์ง๋ค. ๊ฐ๋ฐ์๊ฐ WHERE๋ฅผ ์ด๋์ ๋๋ ์ตํฐ๋ง์ด์ ๊ฐ ์์์ ์ด ์์๋ก ์ฌ๊ตฌ์ฑํ๋ค.
๋จ, Predicate Pushdown์ด ํญ์ ๊ฐ๋ฅํ ๊ฑด ์๋๋ค. HAVING์ฒ๋ผ ์ง๊ณ ์ดํ์ ์ ์ฉ๋๋ ์กฐ๊ฑด์ด๋, ๋น๊ฒฐ์ ์ ํจ์(NOW() ๋ฑ)๊ฐ ํฌํจ๋ ์กฐ๊ฑด์ ๋ฐ์ด ๋ฃ์ ์ ์๋ค.
-- HAVING: ์ง๊ณ๊ฐ ๋๋์ผ cnt ๊ฐ์ด ์๊ธฐ๋ฏ๋ก GROUP BY ์ด์ ์ผ๋ก ๋ฐ์ด ๋ฃ์ ์ ์๋ค
SELECT user_id, count(*) AS cnt
FROM orders
GROUP BY user_id
HAVING cnt > 10
-- ๋น๊ฒฐ์ ์ ํจ์: ์คํ ์์ ๋ง๋ค ๊ฐ์ด ๋ฌ๋ผ์ง๋ฏ๋ก pushdown์ด ์ ํ๋๋ค
WHERE created_at > NOW() - INTERVAL 7 DAY
ClickHouse์์๋?
ClickHouse๋ ์ผ๋ถ Cost-Based ์ต์ ํ๋ฅผ ์ง์ํ์ง๋ง, MySQL·PostgreSQL์ CBO์ฒ๋ผ JOIN ์คํ ์์๋ Predicate Pushdown์ด RDBMS ์์ค์ผ๋ก ๋ณด์ฅ๋์ง๋ ์๋๋ค. ๋จ์ผ ํ ์ด๋ธ์ ๋ํ ๋จ์ํ ์กฐ๊ฑด์ pushdown๋๊ธฐ๋ ํ์ง๋ง, JOIN์ด ํฌํจ๋ ๋ณต์กํ ์ฟผ๋ฆฌ์์๋ WHERE ์กฐ๊ฑด์ด JOIN ์ดํ์ ์ ์ฉ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ์ตํฐ๋ง์ด์ ๊ฐ ์์์ ํด์ค ๊ฑฐ๋ผ๊ณ ๋ฏฟ๊ณ ์ฐ๊ธฐ ์ด๋ ต๋ค.
page_view_log (์ ์ฒด) × order_log (์ ์ฒด) → JOIN → WHERE ํํฐ
ํํฐ๊ฐ ๋ถ๊ธฐ ์ ์ ์ด๋ฏธ ์์ต ๊ฑด์ง๋ฆฌ ํ ์ด๋ธ ๋ ๊ฐ๊ฐ ์ ๋ถ ์กฐ์ธ๋๋ค. ์ฟผ๋ฆฌ๊ฐ ๋๋ฆฐ ๊ฑด ๋น์ฐํ ๊ฒฐ๊ณผ๋ค.
pushdown์ด ๋๋๋ผ๋ ๋ถ๋ถ์ ์ผ๋ก๋ง ์ ์ฉ๋๊ฑฐ๋ ์์ ๋์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์, ๊ฒฐ๊ณผ๋ฅผ ์์ธกํ๊ธฐ ์ด๋ ต๋ค. ๊ฒฐ๊ตญ ๋ค์์ ๋ค๋ฃฐ "์๋ธ์ฟผ๋ฆฌ๋ก ๋จผ์ ์ค์ด๊ธฐ" ํจํด์ CBO + Predicate Pushdown์ ๊ฐ๋ฐ์๊ฐ ์ง์ ๋ช ์์ ์ผ๋ก ๊ตฌํํ๋ ๊ฒ๊ณผ ๊ฐ๋ค.
์ด์ 2 - Hash Join์ ์ค๋ฅธ์ชฝ ํ ์ด๋ธ์ ๋ฉ๋ชจ๋ฆฌ์ ํต์งธ๋ก ์ฌ๋ฆฐ๋ค
ClickHouse์ ๊ธฐ๋ณธ JOIN ์๊ณ ๋ฆฌ์ฆ์ Hash Join์ด๋ค.
- ์ค๋ฅธ์ชฝ ํ ์ด๋ธ ์ ์ฒด๋ฅผ ํด์ ํ ์ด๋ธ๋ก ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฌ
- ์ผ์ชฝ ํ ์ด๋ธ์ ์คํธ๋ฆฌ๋ฐํ๋ฉด์ ํด์ ํ ์ด๋ธ๊ณผ ๋งค์นญ


ํด์ ํ ์ด๋ธ์ ํค๋ก ์ฆ์ ์กฐํ๊ฐ ๊ฐ๋ฅํ ์๋ฃ๊ตฌ์กฐ๋ค. ์ค๋ฅธ์ชฝ์ ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ ค๋๊ณ , ์ผ์ชฝ ํ์ด ๋ค์ด์ฌ ๋๋ง๋ค ํด๋น ํค๋ก ๋ฐ๋ก ์ฐพ์ ๋งค์นญํ๋ ๋ฐฉ์์ด๋ค. ์กฐํ ์์ฒด๋ ๋น ๋ฅด์ง๋ง, ์ค๋ฅธ์ชฝ ํ ์ด๋ธ ์ ์ฒด๊ฐ ๋ฉ๋ชจ๋ฆฌ์ ๋ค์ด๊ฐ์ผ ํ๋ค๋ ์ ์ ๊ฐ ์๋ค.
์ค๋ฅธ์ชฝ ํ ์ด๋ธ์ด ํํฐ๋ง๋์ง ์์ ๋์ฉ๋ ๋ก๊ทธ๋ผ๋ฉด ์์ต ๊ฑด์ด ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ผ๊ฐ์ผ ํ๊ณ , ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋ถ์กฑํ๋ฉด ๋์คํฌ๋ก ๋์ณ ๋ ๋๋ ค์ง๋ค. ์๋ธ์ฟผ๋ฆฌ๋ก ๋ฏธ๋ฆฌ ๊ฑธ๋ฌ์ ์ค๋ฅธ์ชฝ์ ์๋์ผ๋ก ์ค์ด๋ฉด, ๋ฉ๋ชจ๋ฆฌ ์ ์ฌ ๋น์ฉ ์์ฒด๊ฐ ์์์ ธ ์ ์ฒด ์ฒ๋ฆฌ ์๊ฐ์ด ํฌ๊ฒ ๋จ์ถ๋๋ค.
ClickHouse์ RDBMS, ์ค๊ณ ๋ชฉ์ ์ด ๋ค๋ฅด๋ค
์์ ๋ ๊ฐ์ง ์ด์ (Predicate Pushdown ๋ฏธ๋ณด์ฅ, Hash Join์ ๋ฉ๋ชจ๋ฆฌ ์ ์ฌ ๋ฐฉ์)๋ ๋ชจ๋ ClickHouse๊ฐ RDBMS์ ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ์ค๊ณ ์ฒ ํ์ ๊ฐ๊ณ ์๊ธฐ ๋๋ฌธ์ ์๊ธด๋ค.
| ํญ๋ชฉ | RDBMS | ClickHouse |
| ์ค๊ณ ๋ชฉ์ | OLTP - ๋ค์ํ ์ฟผ๋ฆฌ ํจํด์ ๊ท ํ ์๊ฒ ์ฒ๋ฆฌ | OLAP - ๋จ์ผ ํ ์ด๋ธ ๋๊ท๋ชจ ์ค์บ ํนํ |
| ์ตํฐ๋ง์ด์ | Cost-Based, ํต๊ณ ๊ธฐ๋ฐ ์คํ ๊ณํ ์๋ฆฝ | ์๋์ ์ผ๋ก ๋จ์ํ Rule-Based |
| Predicate Pushdown | ์๋ ์ํ | ์ ํ์ , ๋ช ์์ ์ผ๋ก ์์ฑํด์ผ ํจ |
| JOIN ์๊ณ ๋ฆฌ์ฆ ์ ํ | Nested Loop / Hash / Merge ์ค ์๋ ์ ํ | ๊ธฐ๋ณธ Hash Join ๊ณ ์ |
ClickHouse๋ ์ฒ์๋ถํฐ "๋จ์ผ ํ ์ด๋ธ ์ง๊ณ๋ฅผ ๊ทนํ์ผ๋ก ๋น ๋ฅด๊ฒ" ํ๋ ๋ฐ ์ง์คํด์ ์ค๊ณ๋๋ค. ๋ณต์กํ ์ตํฐ๋ง์ด์ ๋์ ์ปฌ๋ผ ์คํ ๋ฆฌ์ง์ ๋ฒกํฐํ ์ฐ์ฐ์ ํฌ์ํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ ๋จ์ผ ํ ์ด๋ธ ์ง๊ณ๋ ๋น ๋ฅด์ง๋ง JOIN์ ๊ฐ๋ฐ์๊ฐ ์ง์ ์ฑ๊ฒจ์ผ ํ๋ค.
์ต์ ํ ์ ๋ต
Dictionary, View์ฒ๋ผ DDL ๊ถํ์ด ํ์ํ ๋ฐฉ๋ฒ๋ ์์ง๋ง, ์ฌ๊ธฐ์๋ ์ฟผ๋ฆฌ ์์ฑ ์์ค์์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ๋ง ๋ค๋ฃฌ๋ค.
1. ์๋ธ์ฟผ๋ฆฌ๋ก ๋ฐ์ดํฐ๋ฅผ ๋จผ์ ์ค์ด๊ธฐ
๊ฐ์ฅ ๊ธฐ๋ณธ์ด์ ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ด๋ค. JOIN ์ ์ ๊ฐ ํ ์ด๋ธ์ ์๋ธ์ฟผ๋ฆฌ๋ก ํํฐ๋งํด์ ์ค์ JOIN ๋์ ๋ฐ์ดํฐ๋ฅผ ์ต์ํํ๋ค. ์์์ ๋ณธ ๋ ๋ฒ์งธ ์ฟผ๋ฆฌ๊ฐ ์ด ์์น์ ์ ์ฉํ ํํ๋ค.
SELECT count(*)
FROM
(SELECT * FROM default.order_log
WHERE order_date BETWEEN '20260604' AND '20260610'
AND user_id = 'user_001'
) O
INNER JOIN (
SELECT * FROM default.page_view_log
WHERE user_id = 'user_001'
AND created_at > '20260504'
) P ON P.user_id = O.user_id AND P.session_id = O.session_id
2. ์์ ํ ์ด๋ธ์ ์ค๋ฅธ์ชฝ์ ๋ฐฐ์น
Hash Join ํน์ฑ์ ์ค๋ฅธ์ชฝ ํ ์ด๋ธ์ด ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ผ๊ฐ๋ฏ๋ก, ํญ์ ๋ ์์ ์ชฝ์ ์ค๋ฅธ์ชฝ์ ๋์ด์ผ ํ๋ค.
-- ๋์จ: ํํฐ๋ง๋์ง ์์ ๋์ฉ๋ ํ
์ด๋ธ์ด ์ค๋ฅธ์ชฝ
FROM order_log O
JOIN page_view_log P ON ...
-- ์ข์: ํํฐ๋ง๋ ์์ ์ชฝ์ด ์ค๋ฅธ์ชฝ
FROM (SELECT * FROM page_view_log WHERE ...) P
JOIN (SELECT * FROM order_log WHERE ...) O ON ...
3. JOIN ์๊ณ ๋ฆฌ์ฆ ๋ช ์์ ์ง์
SETTINGS๋ก ์๊ณ ๋ฆฌ์ฆ์ ์ง์ ์ง์ ํ ์ ์๋ค.
SELECT ...
FROM A JOIN B ON ...
SETTINGS join_algorithm = 'grace_hash'
| ์๊ณ ๋ฆฌ์ฆ | ํน์ง |
| hash | ๊ธฐ๋ณธ๊ฐ. ์ค๋ฅธ์ชฝ ์ ์ฒด๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฌ |
| partial_merge | ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์๋ ๋. ์๋๋ ๋๋ฆผ |
| grace_hash | ๋์ฉ๋ JOIN. ๋์คํฌ๋ฅผ ๋ณด์กฐ๋ก ํ์ฉ |
| auto | ๋ฉ๋ชจ๋ฆฌ ์ํฉ์ ๋ฐ๋ผ ์๋ ์ ํ |
๋ง์น๋ฉฐ
ClickHouse์์ JOIN ์ฑ๋ฅ์ ์ตํฐ๋ง์ด์ ๋ฅผ ๋ฏฟ๋ ๊ฒ์ด ์๋๋ผ ๊ฐ๋ฐ์๊ฐ ์คํ ์์๋ฅผ ์ง์ ์ค๊ณํ๋ ๋ฐ์ ๋์จ๋ค. ์๋ธ์ฟผ๋ฆฌ๋ก ๋ฐ์ดํฐ๋ฅผ ์ถฉ๋ถํ ์ค์ด๊ณ , ์์ ์ชฝ์ ์ค๋ฅธ์ชฝ์ ๋๋ ๊ฒ๋ง์ผ๋ก๋ ๋๋ถ๋ถ์ ์ฑ๋ฅ ๋ฌธ์ ๋ ํด๊ฒฐ๋๋ค.