실시간 분석 엔진, 무엇부터 봐야 할까?
실시간 분석 엔진을 비교할 때 결국 다루는 것은 세 가지 상충이다: 컬럼 지향 스토리지가 제공하는 질의 속도, 수집 파이프라인의 처리량과 지연, 집계 구조(머티리얼라이즈드 뷰)의 유지 비용. 어느 한쪽을 최적화하면 다른 쪽이 밀린다. 엔진을 선택하려면 먼저 이 세 축에서 시스템이 어느 지점에 있는지 읽어야 한다.
컬럼 스토어는 왜 실시간 질의에 필수인가?
컬럼 지향 저장소(columnar storage)가 없으면 대량 이벤트의 질의 지연을 초저(밀리초 단위)로 유지할 수 없다. 로우 지향 스토리지는 집계 쿼리에서 대부분의 컬럼을 메모리에 로드해야 하는데, 컬럼 스토어는 필요한 컬럼만 읽기 때문에 I/O가 1/10~1/100으로 줄어난다.
ClickHouse, DuckDB, Druid 같은 실시간 분석 엔진이 모두 컬럼 저장을 기본으로 하는 이유가 여기 있다. 예를 들어 10억 건의 이벤트 중 특정 사용자 ID와 타임스탐프 두 컬럼만 필요한 경우, 컬럼 스토어는 약 16바이트(ID 8바이트 + 타임스탐프 8바이트) × 10억 = 16GB를 스캔하지만, 로우 지향 스토리지는 평균 행 크기(예: 1KB)를 곱한 1TB가 필요하다. 압축까지 고려하면 그 차이는 더 벌어진다.
다만 트레이드오프가 있다. 컬럼 스토어는 단일 행 삽입이 비효율적이다. 배치 쓰기를 기준으로 최적화되어 있기 때문에, 초당 수천 개의 개별 이벤트가 들어오면 쓰기 지연이 커진다. 이를 보정하려면 메모리 버퍼(write-ahead log)를 두고 일정 시간 또는 크기 단위로 플러시해야 한다.
수집 파이프라인에서 처리량과 지연 중 어느 것을 우선할 것인가?
실시간 분석 엔진으로 들어오는 이벤트 스트림의 설계는 두 가지 극점 사이에 있다. 한쪽은 초저 지연 — 이벤트 도착 후 1~2초 내 쿼리 가능 — 이고, 다른 쪽은 초고 처리량 — 초당 수백만 이벤트 수집.
초저 지연을 위해서는 배치 간격(flush interval)을 짧게(1초 미만) 해야 하는데, 이렇게 하면 작은 파일/세그먼트가 자주 생겨 이후 머지 비용이 증가한다. 초고 처리량을 위해서는 배치 간격을 길게(10~60초) 해야 하는데, 그러면 쿼리 시점에 따라 최대 60초 오래된 데이터만 볼 수도 있다.
2026년 기준으로 업계 권장은 적응형 배칭(adaptive batching): CPU/메모리 압력이 낮으면 배치 간격을 짧게, 높으면 길게 자동 조정한다. 이렇게 해도 보장되는 것은 없고, 최악의 경우 백프레셔(backpressure) 처리로 수집 지연이 늘어난다.
카디널리티 폭발은 언제 문제가 되나?
높은 카디널리티(서로 다른 값의 개수가 많은 컬럼)는 컬럼 스토어의 압축 효율을 떨어뜨린다. 예를 들어 사용자 ID가 10억 개이면, 각 ID를 정수 인코딩했을 때 카디널리티 딕셔너리가 매우 커진다. 압축률이 낮아지고, 결과적으로 메모리와 디스크 I/O가 증가한다.
더 심각한 문제는 그룹핑 쿼리다. GROUP BY user_id를 실행할 때 높은 카디널리티는 중간 집계 해시 테이블의 크기를 키우므로, 메모리 압력이 증가하거나 디스크 spill이 일어난다. 초당 10만 이벤트를 처리하면서 동시에 수십억 개 고유 사용자를 그룹핑하려면, 엔진이 지정된 메모리 한계를 넘길 수 있다.
대응 방법은 두 가지다. 첫째, 선택적 인덱싱 — 쿼리 패턴이 명확하면 자주 필터링되는 컬럼에 블룸 필터나 경량 인덱스를 구성한다. 둘째, 머티리얼라이즈드 뷰 — 고주파 쿼리(예: 상위 100 사용자별 이벤트 수)는 미리 집계하고 카디널리티를 제한한다.
머티리얼라이즈드 뷰의 유지는 얼마나 비싼가?
머티리얼라이즈드 뷰(사전 집계된 결과 테이블)는 질의 지연을 극적으로 줄인다. 상위 100개 사용자별 집계는 수십억 행을 스캔하는 대신 100행만 읽으면 된다. 하지만 이 뷰를 항상 최신 상태로 유지해야 한다는 비용이 따른다.
매 배치 도착 시마다 뷰를 재계산하면 계산량이 선형으로 증가한다. 만약 배치 간격이 1초이고, 뷰 계산이 500ms 걸리면, 뷰 유지 자체가 전체 수집 파이프라인의 절반 이상을 먹는다. 또한 뷰가 많아질수록 이 비용은 누적된다(뷰 A는 뷰 B에 의존하고, 뷰 B는 뷰 C에 의존하는 식).
엔진별로 대응이 다르다. ClickHouse는 materialized view를 전용 시스템으로 갖추고 증분 갱신(incremental update) 최적화를 제공한다. Druid는 롤업(rollup)이라 불리는 사전 집계 구조로, 시간 단위 집계는 저렴하지만 임의의 집계는 여전히 비싸다. DuckDB는 관계형 뷰만 지원하고 증분 갱신은 명시적으로 코딩해야 한다.
압축과 머지는 왜 실시간 성능을 좌우하나?
컬럼 스토어가 압축되지 않으면 초고 처리량 상황에서 곧 스토리지 병목이 된다. 하지만 압축(ZSTD, LZ4 등)은 CPU 집약적이다. 배치마다 압축을 하면 인제스트 CPU가 증가하고, 나중에 하면 스토리지가 증가한다.
또한 배치가 자주 생기면(초저 지연 설정) 파일 조각화 문제가 생긴다. 예를 들어 1분마다 배치를 플러시하면 하루에 1,440개 파일이 생긴다. 질의할 때 이 모든 파일을 오픈하고 메타데이터를 읽는 것만 해도 지연이 누적된다. 이를 막으려면 주기적으로 파일을 머지해야 하는데, 머지도 CPU와 I/O를 사용한다.
실무에서는 두 가지 전략이 쓰인다. 하나는 백그라운드 머지 — 머지 스케줄을 낮은 우선순위로 두고, 수집 부하가 낮을 때 실행한다. 다른 하나는 자동 파티셔닝 — 시간 기준으로 데이터를 분할해 오래된 파티션만 머지한다. ClickHouse의 ReplacingMergeTree는 이 패턴을 엔진 수준에서 구현한다.
실시간 분석 엔진을 고르는 기준은 무엇인가?
엔진 선택은 데이터 특성과 쿼리 패턴에 따라 나뉜다.
고처리량, 낮은 카디널리티 환경(예: 웹 애플리케이션 로그 분석):
- 시간 범위 기반 필터링이 주요 패턴이면 ClickHouse가 유리하다. 파티션 프루닝과 병렬 쿼리가 최적화되어 있기 때문이다.
- Druid는 롤업 집계를 사전 정의할 수 있으면 추천된다. 단, 임의의 새로운 쿼리는 느리다.
저처리량, 높은 카디널리티 환경(예: 금융 거래 분석):
- DuckDB가 적합하다. 임의의 SQL 쿼리를 빠르게 실행하고, 메모리 효율이 좋으며, 메모리 기반 연산에 최적화되어 있다. 대신 스케일 아웃은 약하다.
혼합 패턴 (예: 실시간 대시보드 + 임시 탐색 쿼리):
- ClickHouse + 별도 OLAP 레이어 구성이 권장된다. 고정된 집계는 ClickHouse에서, 임시 쿼리는 다른 엔진으로 분산한다.
초저 지연을 위해 놓치기 쉬운 함정은?
"우리는 100ms 지연이 필요해"라고 할 때 대부분의 팀이 실수하는 지점이 있다. 질의 지연만 100ms를 만족한다고 해서 전체 지연이 100ms인 것은 아니다.
숨겨진 지연 구성은 다음과 같다:
- 이벤트 생성 → 수집 큐(Kafka, RabbitMQ): 최대 배치 간격만큼 (보통 1~10초)
- 큐 → 분석 엔진 수집 파이프라인: 추가 1~5초
- 분석 엔진 수집 완료 → 쿼리 가능: 배치 플러시 간격 (1~10초)
- 쿼리 실행: 100ms
- 결과 → 프론트엔드 렌더링: 100~500ms
합계는 2~26초 범위다. 분석 엔진의 100ms만으로는 E2E 지연을 낮출 수 없다. 전체 파이프라인을 아우르는 설계가 필요하다.
또한 "평균" vs "P99"를 혼동하면 안 된다. 평균 질의 지연이 100ms여도 P99(최악의 1%)가 5초일 수 있다. 우선순위 큐, 리소스 격리, 적응형 쓰로틀링으로 꼬리 지연을 제어해야 한다.
핵심 정리
-
컬럼 지향 저장소는 질의 I/O를 1/10~1/100으로 줄이지만, 단일 행 쓰기 효율이 낮아 배치 기반 수집이 필수다.
-
초저 지연(1~2초)과 초고 처리량(수백만 이벤트/초)은 상충 관계다. 배치 간격 설정이 그 중심이며, 적응형 배칭으로 동적 조정이 가능하다.
-
높은 카디널리티 컬럼은 압축 효율을 떨어뜨리고 그룹핑 쿼리의 메모리 압력을 증가시킨다. 머티리얼라이즈드 뷰로 제한된 카디널리티의 집계를 미리 계산하면 보정할 수 있다.
-
머티리얼라이즈드 뷰 유지는 배치마다 재계산되므로 배치 간격과 뷰 개수에 따라 CPU 비용이 선형 증가한다. 증분 갱신 지원 여부가 엔진 선택에 영향을 미친다.
-
파일 조각화를 막기 위해 배경 머지가 필수이며, 머지 스케줄과 파티셔닝 전략이 질의 성능(파일 수)과 수집 지연(머지 대기)의 균형을 결정한다.
-
엔진 선택은 카디널리티 분포, 쿼리 패턴의 고정성(롤업 가능 여부), 스케일 아웃 필요성에 따라 달라진다.
-
E2E 지연은 분석 엔진 질의만으로 결정되지 않으며, 수집 파이프라인 전체의 배치 간격, 버퍼링, 큐 대기를 함께 고려해야 한다.
자주 묻는 질문
컬럼 스토어와 로우 스토어 중 어떤 것을 선택해야 하나?
대량 이벤트의 집계 쿼리가 주요 패턴이면 컬럼 스토어다. 단일 행 조회나 매우 좁은 범위의 포인트 쿼리가 많으면 로우 스토어(또는 LSM 트리 기반 스토리지)가 낫다. 실시간 분석 엔진은 대부분 컬럼 스토어이므로, 선택지는 어느 컬럼 스토어 엔진을 쓸지로 좁혀진다.
배치 간격을 1초로 설정하면 안 되나?
기술적으로 가능하지만 여러 비용이 따른다. 매 1초마다 파일이 생기므로 하루에 86,400개 파일이 누적되고, 질의할 때 메타데이터 I/O가 증가한다. 또한 배경 머지 작업이 CPU를 계속 사용해 수집 성능을 압박한다. 실무에서는 5~10초가 권장된다.
머티리얼라이즈드 뷰는 몇 개까지 만들 수 있나?
엔진 기술적 한계는 없지만, 실제 한계는 CPU다. 각 배치마다 모든 뷰를 재계산하면, 뷰 개수에 비례해 CPU가 소비된다. 보통 10~20개 정도 뷰를 유지하면서 배치 간격을 10초 이상으로 설정하는 것이 안정적이다. 더 많은 뷰가 필요하면 뷰 계산을 샤딩하거나 일부를 야간 배치로 옮긴다.
ClickHouse와 DuckDB 중 어느 것이 더 빠른가?
상황에 따라 다르다. ClickHouse는 초고 처리량(초당 백만 이벤트 이상), 시간 범위 기반 쿼리에 최적화됐다. DuckDB는 메모리 기반 임의 SQL 쿼리에 최적화됐다. 만약 데이터가 메모리에 들어가고 쿼리가 다양하면 DuckDB가 빠르고, 데이터가 크고 시간 필터링이 대부분이면 ClickHouse가 빠르다.
카디널리티 폭발 상황에서 디스크 spill이 일어나면 어떻게 되나?
그룹핑 쿼리 중 메모리가 부족하면 엔진이 중간 해시 테이블을 디스크로 흘린다. 이 경우 질의 지연이 수십 배 증가한다(메모리: 수십ms → 디스크: 수초). 근본 해결책은 쿼리 설계 변경(카디널리티 제한) 또는 메모리 증설이다.
압축률이 높을수록 좋은가?
아니다. 높은 압축률(ZSTD)은 CPU를 더 사용하고, 낮은 압축률(LZ4)은 CPU를 덜 사용한다. 수집 파이프라인이 CPU 병목이면 LZ4를 써서 압축 오버헤드를 줄이고, 스토리지가 병목이면 ZSTD를 써서 압축률을 높인다.
정말 실시간(밀리초 단위)이 가능한가?
매우 제한적이다. 쿼리 실행은 100ms 수준이 가능하지만, 이벤트 도착부터 쿼리 가능까지의 E2E 지연은 최소 수초다. 정말 밀리초 단위가 필요하면 별도의 인메모리 스트림 처리(Flink, Spark Streaming) + 분석 엔진 이원 구조를 고려해야 한다.