04 시장 쿼리 실행하기
26.5M건의 틱을 대상으로 각각 SQL 한 페이지를 대체하는 일곱 개의 쿼리: 근사 카운트, topK, OHLC 캔들스틱, 백분위수, -If 컴비네이터, argMax, 그리고 sort key 피날레.
이것들이 바로 ClickHouse의 "SQL 한 페이지를 대체하는 함수들"입니다. 각 블록을 붙여넣고 실행한 뒤,
초록색 박스에서 무엇이 나와야 하는지 확인하세요. 모두 방금 로드한 forex 테이블을 대상으로
실행됩니다.
4.1 근사값 대 정확값 — 핵심 트릭
26.5M건의 틱에 서로 다른 호가 타임스탬프는 몇 개나 있을까요? 세 함수가 같은 질문에 서로 다른 트레이드오프로 답합니다. 한 번에 하나씩 실행하면서 쿼리 통계의 실행 시간을 보세요 — 따로 실행하는 것이 이 절의 핵심입니다.
-- Approximate count of distinct timestamps (HyperLogLog)
SELECT uniq(datetime) AS distinct_ts FROM forex;-- Exact count — precise, but scans every value
SELECT uniqExact(datetime) AS distinct_ts FROM forex;-- Adaptive + tunable accuracy vs memory
SELECT uniqCombined(datetime) AS distinct_ts FROM forex;이렇게 보여야 합니다
세 함수 모두 약 2,460만 개의 고유 타임스탬프를 반환합니다. 하지만 uniq는 약 0.2초에
돌아오고, uniqExact는 정확한 숫자를 얻기 위해 약 3초(약 17배 느림)가 걸리며,
uniqCombined는 1초도 훨씬 안 되는 시간에 오차 약 0.4% 안으로 들어옵니다. 수십억 행 규모에서
이 격차는 즉시 결과가 나오는 것과 커피 한 잔 마시고 오는 것의 차이입니다.
4.2 함수 하나로 Top-N
가장 활발하게 호가된 통화 쌍 — GROUP BY / ORDER BY / LIMIT이 필요 없습니다:
-- One row: an array of the 8 most actively quoted pairs (by tick count)
SELECT topK(8)(pair) AS most_active FROM forex;이렇게 보여야 합니다
8개 통화 쌍의 배열이 담긴 셀 하나가 나오고, 호가가 많은 순서입니다(금, XAU/USD가 1위).
topK는 한 번의 패스로 테이블 전체를 그 순위 목록으로 압축합니다 —
GROUP BY … ORDER BY count() DESC LIMIT 8을 대신하는 셈입니다.
4.3 한 번의 패스로 시계열 — OHLC 캔들스틱
금의 일별 시가 / 고가 / 저가 / 종가 — 시장 분석의 기본 쿼리입니다:
-- One row per day: gold (XAU/USD) open, high, low, close — a candlestick chart
SELECT
toDate(datetime) AS day,
argMin(bid, datetime) AS open, -- bid at the day's first tick
max(bid) AS high,
min(bid) AS low,
argMax(bid, datetime) AS close -- bid at the day's last tick
FROM forex
WHERE base = 'XAU' AND quote = 'USD'
GROUP BY day
ORDER BY day;이렇게 보여야 합니다
2020년 1월의 하루당 한 행. argMin(bid, datetime)은 그날 첫 틱의 bid(시가)이고 argMax는
마지막 틱의 bid(종가)입니다 — 윈도 함수도, 셀프 조인도 없습니다. 금이 한 달 동안 약 1,520에서
약 1,610까지 오르는 것을 보세요.
4.4 윈도 함수 곡예 없이 백분위수
bid/ask 스프레드는 유동성 지표입니다 — 그리고 평균은 꼬리를 감추므로 백분위수를 사용하세요:
-- One row per pair: median and 99th-percentile bid/ask spread (tighter = more liquid)
SELECT
pair,
round(quantile(0.5)(ask - bid), 6) AS median_spread,
round(quantile(0.99)(ask - bid), 6) AS p99_spread
FROM forex
GROUP BY pair
ORDER BY median_spread ASC;이렇게 보여야 합니다
통화 쌍당 한 행, 스프레드가 가장 좁은 것부터. EUR/USD가 가장 유동성이 높게 나옵니다
(약 0.00002, 1핍 이하). 금이 가장 넓습니다. quantile은 한 번의 패스로 근사 계산됩니다 —
컬럼 전체를 정렬하지 않습니다.
4.5 컴비네이터 — 한 번의 스캔, 두 개의 답
활발한 거래 시간대와 한산한 시간대의 평균 스프레드를 한 번의 스캔으로 나란히 비교합니다:
-- One row per pair: avg spread during the active window vs quiet hours
SELECT
pair,
count() AS ticks,
round(avgIf(ask - bid, toHour(datetime) BETWEEN 7 AND 20), 6) AS spread_active,
round(avgIf(ask - bid, toHour(datetime) NOT BETWEEN 7 AND 20), 6) AS spread_quiet
FROM forex
GROUP BY pair
ORDER BY ticks DESC;이렇게 보여야 합니다
활발한 시간대와 한산한 시간대의 스프레드가 같은 결과에 함께 나옵니다. -If 컴비네이터는 어떤
집계 함수에든 조건을 붙여줍니다: avgIf(x, cond)는 cond가 참인 곳의 x만 평균합니다 —
두 번 스캔하는 쿼리도, CASE WHEN도 필요 없습니다. 거의 모든 집계 함수가 이를 지원합니다
(countIf, sumIf, quantileIf, …).
4.6 최신 값 — argMax
모든 통화 쌍의 가장 최근 호가를 한 번의 패스로:
-- One row per pair: the latest quoted bid and the timestamp it was seen
SELECT
pair,
argMax(bid, datetime) AS last_bid,
max(datetime) AS as_of
FROM forex
GROUP BY pair
ORDER BY pair;이렇게 보여야 합니다
통화 쌍별 최신 bid. argMax(bid, datetime)은 datetime이 가장 큰 행의 bid를 반환합니다 —
윈도 함수나 셀프 조인 없이 처리하는, 일상적인 "마지막 가격 / 최신 상태" 쿼리입니다.
4.7 피날레 — sort key가 있을 때와 없을 때
쿼리 모양은 같습니다 — 조건에 맞는 틱을 세는 것 — 하지만 한 필터는 sort key를 타고 다른 하나는 그렇지 않습니다. 먼저 두 카운트를 실행하세요:
-- Optimized: base+quote ARE the leading sort key — the index skips to that pair
SELECT count() FROM forex WHERE base = 'XAU' AND quote = 'USD';
-- Non-optimized: bid is NOT in the sort key — no index, scans all 26.5M ticks.
-- (cache off so the full scan shows on every run)
SELECT count() FROM forex WHERE bid > 1.5
SETTINGS use_query_condition_cache = 0;차이를 알아보기 어렵나요?
두 카운트 모두 몇 밀리초 만에 돌아오므로 시간만 보면 차이가 거의 없습니다 — count()는 어느
쪽이든 빠릅니다. 진짜 차이는 각각이 얼마나 많은 데이터를 건드려야 하는가입니다. 이를 실제로
보려면 EXPLAIN indexes = 1로 ClickHouse의 실행 계획을 확인하세요. 읽게 될
그래뉼(약 8,192행 블록) 수를 알려줍니다.
-- Optimized: the primary index skips straight to the XAU/USD rows
EXPLAIN indexes = 1
SELECT count() FROM forex WHERE base = 'XAU' AND quote = 'USD';
-- Non-optimized: bid isn't in the sort key, so nothing can be skipped
EXPLAIN indexes = 1
SELECT count() FROM forex WHERE bid > 1.5;이렇게 보여야 합니다
최적화된 계획에서는 Indexes → PrimaryKey 섹션에 선택된 그래뉼이 일부만 표시됩니다 —
약 492 / 3,234(약 16K 행). 최적화되지 않은 계획에서는 적용할 인덱스가 없으므로
3,234 / 3,234 그래뉼, 즉 모든 행을 읽습니다. 같은 쿼리 모양인데 작업량은 완전히 다릅니다.
교훈: 가장 자주 필터링하는 컬럼을 ORDER BY 앞쪽에 두세요.
이 쿼리는 클립보드에 남겨두세요
최적화되지 않은 그 WHERE bid > 1.5 쿼리를
다음 모듈에서 AI에게 넘기게 됩니다.