Ken
MySQL 워크로드를 ClickHouse로 전환할 때 가장 많이 받는 질문이 있습니다.
"분석 쿼리가 빠른 건 알겠는데, Point Query는 어떤가요?"
이전 글에서 ClickHouse의 MySQL Interface를 통해 기존 MySQL 클라이언트와 애플리케이션을 그대로 사용할 수 있다는 점을 다뤘습니다. 그리고 분석 쿼리에서 ClickHouse가 MySQL보다 월등히 빠르다는 것은 이미 널리 알려진 사실입니다.
그렇다면 남은 질문은 하나입니다: Point Query는 어떨까요?
이번 글에서는 Point Query 성능을 MySQL 수준으로 끌어올리는 방법을 실제 벤치마크와 함께 살펴보겠습니다.
- 테스트 시나리오: 게임 서버의 플레이어 조회
- Baseline: 아무 최적화 없이 시작하기
- 테이블 스키마
- Baseline 성능 결과
- 왜 ClickHouse의 Point Query가 MySQL보다 느린가?
- 최적화 전략 1: Index Granularity 조정
- ClickHouse Cloud에서 지원 여부 확인
- 최적화된 스키마
- 효과 분석
- 최적화 전략 2: Bloom Filter Index
- 최적화 전략 3: PREWHERE 활용
- 최종 벤치마크 결과
- QPS 비교
- 레이턴시 비교 (P50)
- 성능 요약
- 최적화 Trade-off 정리
- 더 높은 성능이 필요하다면: Dictionary 엔진
- 결론: ClickHouse로 Point Query를 처리해도 될까?
- 핵심 포인트
- 참고 자료
테스트 시나리오: 게임 서버의 플레이어 조회
실제 고객 문의를 바탕으로 테스트를 설계했습니다.
유즈케이스: 게임 앱에서 플레이어의 마지막 접속 정보를 조회
- 데이터 규모: 200만 유저
- 쿼리 패턴:
WHERE player_id = ?(Primary Key 기반 단일 row 조회) - 요구사항: 낮은 레이턴시, 높은 동시성 지원
핵심 질문은 이것입니다: 컬럼 스토어인 ClickHouse가 row 단위 조회에서 얼마나 효율적일 수 있는가?
Baseline: 아무 최적화 없이 시작하기
먼저 기본 설정으로 MySQL과 ClickHouse를 비교했습니다.
테이블 스키마
Baseline 성능 결과
지표 | MySQL | ClickHouse (기본) | 비율 |
평균 QPS | 4,687 | 3,070 | 65% |
P50 레이턴시 | 0.65ms | 2.10ms | 3.2배 |
P95 레이턴시 | 1.81ms | 3.80ms | 2.1배 |
MySQL 대비 65% 수준입니다. 나쁘지 않지만, 개선의 여지가 있습니다.
왜 ClickHouse의 Point Query가 MySQL보다 느린가?
성능 차이를 이해하려면 두 데이터베이스의 인덱스 구조를 비교해야 합니다.
MySQL B-Tree: O(log n) 복잡도로 정확한 row 위치를 찾습니다.
ClickHouse Sparse Index: 모든 row가 아닌 granule 단위(기본 8,192 rows)로만 인덱스를 저장합니다. 200만 rows는 약 244개 granule로 나뉘고, Point Query 시 해당 granule을 찾은 후 내부에서 추가 스캔이 필요합니다.
이것이 근본적인 성능 차이의 원인입니다. 하지만 ClickHouse는 이를 보완할 수 있는 여러 최적화 옵션을 제공합니다.
최적화 전략 1: Index Granularity 조정
가장 효과적인 최적화는 index_granularity를 줄이는 것입니다.
ClickHouse Cloud에서 지원 여부 확인
먼저 ClickHouse Cloud에서 이 설정을 변경할 수 있는지 직접 테스트했습니다.
-- ClickHouse Cloud에서 테스트
CREATE TABLE test_granularity_256 (
id UInt64,
name String
) ENGINE = MergeTree()
ORDER BY id
SETTINGS index_granularity = 256;
-- 결과: 성공! 테이블 생성 완료
결론: ClickHouse Cloud에서 index_granularity 축소가 완전히 지원됩니다.
최적화된 스키마
CREATE TABLE player_last_login_optimized
(
player_id UInt64,
player_name String,
-- ... 기타 컬럼 ...
-- Bloom Filter Index 추가
INDEX idx_player_id_bloom player_id TYPE bloom_filter(0.01) GRANULARITY 1
)
ENGINE = MergeTree()
ORDER BY player_id
SETTINGS
index_granularity = 256; -- 8192 → 256 (32배 축소)
효과 분석
설정 | Granule 수 | Point Query 시 최대 스캔 | 인덱스 크기 |
8,192 (기본) | 244 | 8,192 rows | ~2KB |
256 (최적화) | 7,813 | 256 rows | ~62KB |
Granule 크기를 32배 줄이면, Point Query 시 스캔해야 할 최대 row 수도 32배 감소합니다.
최적화 전략 2: Bloom Filter Index
Bloom Filter는 "이 granule에 해당 값이 확실히 없다"를 빠르게 판단하는 확률적 자료구조입니다.
INDEX idx_player_id_bloom player_id TYPE bloom_filter(0.01) GRANULARITY 1
- False Positive Rate 0.01: 1% 확률로 "있을 수도 있다"고 잘못 판단
- GRANULARITY 1: 각 granule마다 별도 Bloom Filter 생성
Primary Key에 이미 Sparse Index가 있지만, Bloom Filter는 추가적인 필터링 레이어로 작동합니다.
최적화 전략 3: PREWHERE 활용
ClickHouse는 WHERE 대신 PREWHERE를 사용하면 필터 컬럼만 먼저 읽어 필터링한 후, 매칭되는 row의 나머지 컬럼을 읽습니다.
-- 기본
SELECT * FROM player_last_login WHERE player_id = 12345;
-- 최적화
SELECT * FROM player_last_login PREWHERE player_id = 12345;
테스트 결과: 동시성 16 이상에서 3~8% 성능 향상
최종 벤치마크 결과
모든 최적화를 적용한 결과입니다.
QPS 비교
동시성 | MySQL | CH 기본 | CH 최적화 | 최적화 효과 |
8 | 4,698 | 3,183 | 3,418 | +7.4% |
16 | 4,547 | 2,972 | 3,281 | +10.4% |
24 | 4,646 | 3,104 | 3,159 | +1.8% |
32 | 4,860 | 3,022 | 3,142 | +4.0% |
레이턴시 비교 (P50)
동시성 | MySQL | CH 기본 | CH 최적화 | 개선율 |
8 | 0.70ms | 2.10ms | 1.89ms | -10% |
16 | 0.81ms | 2.74ms | 2.58ms | -6% |
24 | 0.91ms | 3.83ms | 3.53ms | -8% |
32 | 1.08ms | 5.29ms | 4.47ms | -15% |
성능 요약
구성 | 평균 QPS | MySQL 대비 | 적용 최적화 |
MySQL | 4,687 | 100% | - |
ClickHouse 최적화 | 3,250 | 69% | granularity=256 + PREWHERE |
ClickHouse 기본 | 3,070 | 65% | 없음 |
최적화 Trade-off 정리
최적화 | 성능 향상 | 비용 | 권장 여부 |
index_granularity=256 | +5~11% | 인덱스 +60KB | ✅ 강력 추천 |
Bloom Filter Index | +2~3% | 저장 공간 미미 | ✅ 추천 |
PREWHERE (동시성 16+) | +3~8% | 없음 | ✅ 추천 |
PREWHERE (동시성 8 이하) | -1~2% | - | ❌ 비추천 |
더 높은 성능이 필요하다면: Dictionary 엔진
Point Query 성능을 MySQL과 동등하거나 그 이상으로 끌어올리려면 Dictionary 엔진을 고려할 수 있습니다.
예상 성능: MergeTree 대비 2~3배 향상 (10,000+ QPS)
Trade-off:
- ✅ 메모리 내 해시테이블로 O(1) 조회
- ❌ 메모리 사용량 증가 (200만 rows ≈ 1GB)
- ❌ 실시간 업데이트 불가 (주기적 새로고침)
결론: ClickHouse로 Point Query를 처리해도 될까?
답은 "Yes, 충분히 실용적"입니다.
핵심 포인트
- ClickHouse 기본 설정으로도 MySQL 대비 65% 성능 - 대부분의 앱 API 요구사항(< 100ms)을 충족
- 최적화 적용 시 69~73%까지 향상 -
index_granularity=256+PREWHERE조합 - P50 레이턴시 2~5ms - 실시간 게임 서버에서도 충분히 사용 가능한 수준
- 분석 쿼리는 ClickHouse가 압도적으로 빠름 - 이미 검증된 사실이므로, Point Query만 해결하면 완전한 전환이 가능
MySQL에서 ClickHouse로 전환을 고려하고 있다면, Point Query 성능은 더 이상 장벽이 아닙니다. 분석 쿼리에서의 압도적인 성능 + Point Query에서의 실용적인 성능을 갖춘 ClickHouse는 OLTP와 OLAP 워크로드를 모두 처리할 수 있는 강력한 선택지입니다.
참고 자료
- ClickHouse Primary Indexes 가이드
- Altinity: Maximum QPS for key-value lookups
- ClickHouse Skipping Indices