시작하기 전에
2편에서 메트릭 데이터를 MySQL과 ClickHouse에 똑같이 넣고 비교했을 때, 컬럼형인 ClickHouse가 대부분의 쿼리에서 크게 앞섰다.
그런데 대규모 메트릭 데이터를 효과적으로 처리하는 방법이 이 둘만 있는 건 아니다. 이미 쓰던 MySQL에 사전집계를 더해볼 수도 있고, 시계열 전용으로 나온 TimescaleDB도 있고, Cassandra나 Druid를 떠올릴 수도 있다. 2편을 마무리하면서 이 선택지들이 계속 걸렸다. 같은 문제를 이 DB들로 풀면 어떻게 다를까 싶었다.
그래서 3편에서는 2편과 같은 메트릭 수집 시나리오를 그대로 두고, 네 가지 방식을 하나씩 정리해봤다. 먼저 각각이 어떤 DB인지 개념을 살펴보고, 그중 직접 띄워볼 수 있는 두 가지(MySQL + 사전집계, TimescaleDB)는 측정까지 했다.
비교 기준
2편의 셋업을 그대로 가져왔다.
- 데이터: 50서버 × 15메트릭 × 10초 간격 × 30일, 약 1.94억 행
- 쿼리: Q1~Q7 (단일 host 조회 / region 집계 / 임계 스캔 / 다운샘플링 / top-N / 단건 INSERT / 벌크 적재)
- 자원: 컨테이너당 4코어 / 8GB
여기에 새로 측정할 MySQL + 사전집계, TimescaleDB를 더하고, 2편에서 측정한 MySQL(raw)·ClickHouse 값을 비교 대상으로 함께 놓는다.
MySQL + 사전집계
row 지향인 MySQL을 그대로 두고, 미리 집계한 요약본으로 느린 집계 쿼리를 메우는 방법이다.
사전집계란
- 2편에서 MySQL이 가장 느렸던 쿼리는 다운샘플링 — 7일치 cpu_user 분 단위 평균에 raw 수천만 행 풀스캔, 8초 소요
- 자주 보는 집계는 대개 정해져 있음 → 미리 계산해 작은 테이블에 저장하고, 조회는 그 테이블만 읽기
- 1.94억 행을 매번 훑는 대신 미리 집계된 3,240만 행만 조회
- 미리 계산해 저장하는 방식이 사전집계, 그 결과를 테이블로 구체화한 것이 Materialized View
MySQL에는 Materialized View가 없다
- MySQL엔 Materialized View 자체가 없음 (PostgreSQL·Oracle엔 있음)
- 요약 테이블 + 갱신 로직(트리거·배치·앱 코드)을 직접 구현해야 함
- 컬럼형과 접근 층위가 다름
- 컬럼형: 저장 구조를 바꿔 스캔량 자체를 줄임
- 사전집계: 저장은 row인 채로, 읽을 데이터 양을 미리 줄임
TimescaleDB
PostgreSQL을 기반으로 시계열을 다루도록 확장한 DB다.
PostgreSQL 확장으로 동작
- 독립 DB가 아니라 PostgreSQL 확장(extension)
- PostgreSQL은 MySQL과 같은 행 지향(row) DB → 포크 없이 확장만으로 시계열 기능 추가
- 기존 PostgreSQL 클라이언트·드라이버·SQL·생태계를 그대로 사용
- 도구·운영을 새로 익혀야 하는 ClickHouse와 갈리는 지점
- 가능한 이유: PostgreSQL이 코어 수정 없이 커스텀 인덱스·저장 엔진(table access method)을 추가하도록 열려 있음
- hypertable(시간 자동 분할)과 Hypercore 압축이 모두 그 위에 구현됨
Hypercore
- 최근 데이터는 row, 식으면 컬럼 형태로 변환·압축하는 하이브리드 엔진
- 변환 시점은 정책으로 지정
- 예: "7일 지난 데이터 압축" → 백그라운드 작업이 주기적으로 변환
- 1,000행씩 묶어 컬럼별 배열로 압축, 타입에 맞는 알고리즘 적용
- 2편 ClickHouse 코덱(
DoubleDelta,Gorilla)과 같은 계열
- 2편 ClickHouse 코덱(
- 압축이 끝난 raw 테이블 자체가 ClickHouse 같은 컬럼형이 됨
- 최근 데이터는 row(쓰기 유리), 오래된 데이터는 컬럼형(분석 유리)
- 사전집계 없이 raw만으로 컬럼형 (측정에서 확인)
Cassandra
Cassandra는 이름에 column이 들어가 컬럼형으로 오해하기 쉽지만, 우리가 말하는 컬럼형 분석 DB가 아니다. NoSQL을 크게 key-value, document, wide-column, graph로 나눌 때 wide-column 계열에 속하는 분산 NoSQL이다.
wide-column이란
- 데이터를 partition(파티션 키)으로 나누고, 한 파티션 안에 정렬된 여러 row를 담는 구조
- 이름은 column이지만 컬럼별로 모아 압축·스캔하는 컬럼형 분석 엔진과는 다름
- 파티션 안에서 정렬된 행을 키로 빠르게 찾아 읽는 데 초점
분산과 동시성
- partition key를 해시해 여러 노드에 분산, 각 파티션을 복제 계수(RF)만큼 복제
- 마스터 없이(masterless) 모든 노드가 대등 → 고가용성·쓰기 확장에 강함
- 동시 쓰기는 락이 아니라 타임스탬프 기반 last-write-wins로 해소
강점과 약점
- 강점: 대규모 분산 쓰기, 고가용성, 키 기반 조회
- 약점: 대량 집계·애드혹 분석에 약함 (풀스캔이 비싸고 집계 기능이 제한적)
컬럼형 OLAP과의 차이
- 메트릭 맥락에선 분석 엔진보다 대규모 저장소에 가까움 (Cassandra 위 시계열 DB로 KairosDB)
- ClickHouse와 비교 축이 안 맞아 측정에서 제외, 개념만 정리
Druid
Druid는 실시간 분석에 특화된 분산 컬럼형 OLAP이다. ClickHouse처럼 컬럼 지향이 맞지만 지향점이 다르다.
기본 구조
- 데이터를 시간 단위 segment로 쪼개 컬럼별로 저장
- dimension마다 비트맵 인덱스를 둬서 필터링이 빠름
- 적재 시점에 roll-up(사전집계)을 기본 적용
- 쿼리·관리·적재를 나눠 맡는 프로세스 + deep storage(S3 등) + 메타데이터 DB로 구성된 분산 클러스터 전제
실시간 적재와 즉시 조회
- 적재 프로세스가 들어오는 데이터를 메모리에 모아 두었다가 일정 단위로 묶어 디스크 segment로 내려보냄
- 단건이 곧바로 디스크 조각이 되지 않음 → 단건 쓰기에 약한 순수 컬럼형과 달리 고빈도 스트리밍에 부담이 적음
- 메모리 단계의 데이터도 쿼리에 응답, deep storage로 넘어간 과거 데이터는 다른 노드가 서빙
- 쿼리 시 양쪽 결과를 합침 → 적재와 거의 동시에 조회
높은 동시성
- 적재·조회·라우팅 프로세스가 분리돼 적재 부하가 조회를 건드리지 않음
- segment가 여러 노드에 분산·복제 → 한 쿼리도 여러 노드에서 병렬 처리
- 비트맵 인덱스로 카디널리티 높은 필터에도 강함
- 노드 추가로 수평 확장 용이
단점
- 구성 요소가 많아 운영이 무거움
- JOIN이 약함
- 작은 규모엔 과함
ClickHouse와의 차이
- ClickHouse: 단일 노드로도 강력, SQL로 무거운 분석을 빠르게 처리하는 단순·범용 분석 엔진
- 배치 INSERT 중심, 정렬된 sparse 인덱스로 넓은 범위 스캔에 강함
- Druid: 스트리밍을 실시간으로 받아 다수 사용자의 동시 대시보드를 떠받치는 데 특화
- 비트맵 인덱스로 특정 값 필터링에 강한 대신 분산 구성·운영 부담 전제
- 메트릭으로 치면 — 깊게 파고드는 분석은 ClickHouse, 실시간 모니터링 대시보드는 Druid
이번 실험은 단일 노드 비교라, Druid는 개념만 정리하고 측정에서는 뺐다.
측정 결과
MySQL + 사전집계와 TimescaleDB를 2편과 같은 쿼리로 측정하고, 2편의 MySQL(raw)·ClickHouse 값과 나란히 놓았다. TimescaleDB는 압축이 끝난 raw 테이블에 그대로 쿼리를 날린 값이다.
| 쿼리 | MySQL raw | MySQL+사전집계 | TimescaleDB | ClickHouse |
|---|---|---|---|---|
| Q1 단일 host 추이 | 61 | 61 | 63 | 74 |
| Q2 region 집계(7일) | 1,009 | 175 | 88 | 94 |
| Q3 임계 스캔(1일) | 400 | 117 | 70 | 83 |
| Q4 다운샘플(7일) | 8,200 | 662 | 118 | 92 |
| Q5 top-N(1시간) | 75 | 66 | 67 | 76 |
| Q6 단건 INSERT ×1000 | 632 | 1,249 | 204 | 1,094 |
| Q7 벌크 적재(27만행) | 2,222 | 2,248 | 440 | 290 |
(단위: ms, warm 5회 평균)
MySQL + 사전집계 — 집계는 빨라지지만 대가가 있다
집계 쿼리에서는 효과가 분명했다. 다운샘플링(Q4)이 8.2초에서 662ms로, region 집계(Q2)가 1초에서 175ms로 줄었다. 미리 집계해둔 작은 테이블만 읽으니 당연한 결과다.
다만 모든 쿼리가 빨라지지는 않았다. 단일 host의 10초 단위 추이(Q1)는 사전집계로 바꿀 수 없다. 분 단위로 묶으면 원본 해상도가 사라지기 때문에, 그대로 raw를 읽어야 하고 시간도 같았다(61ms). 1시간 범위의 top-N(Q5)은 raw나 사전집계나 비슷했는데, 범위가 작으면 raw를 훑어도 부담이 크지 않아서다.
대가도 있었다. 단건 INSERT를 1,000번 날렸더니 raw만 넣을 때는 632ms인데, 롤업까지 함께 갱신하면 1,249ms로 두 배가 됐다. 데이터가 들어올 때마다 원본과 요약본을 둘 다 손대야 하니 쓰기가 무거워진다. 디스크도 롤업 테이블이 5.5GB를 더 차지했다.
정리하면 MySQL + 사전집계는 row DB의 느린 집계를 우회하는 방법이다. raw가 느리니까 미리 집계해서 메우는 것인데, 애초에 raw가 빠르면 이 작업이 필요 없을 것이다. 그게 다음 결과로 이어진다.
TimescaleDB — raw만으로 ClickHouse에 붙는다
TimescaleDB는 압축된 raw 테이블에 집계 쿼리를 그대로 날렸다. 사전집계 없이 raw만으로 측정한 값이다.
집계 쿼리에서 TimescaleDB가 ClickHouse와 거의 같은 수준으로 나왔다. 다운샘플링(Q4)은 118ms로 ClickHouse(92ms)에 조금 못 미쳤지만, region 집계(Q2)나 임계 스캔(Q3)에서는 오히려 미세하게 빨랐다. MySQL이 사전집계까지 동원해서 만든 662ms를, TimescaleDB는 raw 그대로 118ms에 처리했다. 압축된 raw가 곧 컬럼형이라는 게 숫자로 드러난 셈이다.
단건 INSERT 1,000번에서는 TimescaleDB가 204ms로 가장 빨랐다. ClickHouse(1,094ms)나 MySQL + 사전집계(1,249ms)와 차이가 컸다. 1편에서 컬럼형은 단건 쓰기에 약하다고 했는데, TimescaleDB는 Hypercore가 하이브리드라 최근 데이터를 일단 row로 받기 때문에 단건도 가볍다. 컬럼 변환은 데이터가 식은 뒤 뒤에서 일어난다. 순수 컬럼형인 ClickHouse가 단건에 약한 것과 대조적이다.
디스크
| 크기 | 압축 | |
|---|---|---|
| MySQL raw | 29.3GB | 1배 |
| MySQL 롤업(추가) | +5.5GB | |
| ClickHouse | 1.33GB | 22배 |
| TimescaleDB(압축 후) | 1.66GB | 11배 |
컬럼형인 ClickHouse(1.33GB)와 TimescaleDB(1.66GB)가 MySQL raw(29.3GB)에 비해 디스크를 크게 아꼈다. 눈에 띄는 건 MySQL 롤업 테이블(5.5GB)이 컬럼형으로 압축한 원본 전체보다 크다는 점이다. row DB는 사전집계로 속도는 메웠지만 디스크는 오히려 더 썼다.
솔루션별 특성 정리
측정과 개념을 모으면 이렇게 정리된다.
- 이미 MySQL을 쓰고 있고, 자주 보는 집계가 정해져 있다면 → MySQL + 사전집계. 새 DB 없이 집계 쿼리를 자릿수 단위로 끌어올릴 수 있다. 대신 쓰기가 무거워지고 디스크를 더 쓰며, 미리 정해둔 집계만 빨라진다.
- PostgreSQL을 쓰고 있고 시계열을 제대로 다루고 싶다면 → TimescaleDB. SQL과 도구를 그대로 쓰면서 raw만으로 ClickHouse에 가까운 읽기 성능을 냈고, 쓰기는 오히려 가장 빨랐다.
- 대량 분석 성능과 디스크 효율이 가장 중요하다면 → ClickHouse. 다운샘플링 같은 무거운 집계에서 가장 빨랐고 압축률도 가장 높았다.
- 대규모 분산 쓰기나 고가용성이 핵심이라면 → Cassandra(분석은 별도 레이어로) 또는 Druid(실시간 적재와 높은 동시성).
2편만 보면 ClickHouse가 정답처럼 보이지만, 다른 방법들을 직접 재보니 꼭 그렇지만은 않았다. 무거운 집계와 디스크 효율에서는 ClickHouse가 여전히 가장 좋았지만, 컬럼형이 ClickHouse만 있는 것도 아니었다. PostgreSQL을 쓰고 있다면 TimescaleDB로 비슷한 성능에 더 나은 쓰기를 얻을 수 있었고, MySQL을 떠나기 어렵다면 사전집계로 상당 부분 메울 수 있었다.
마무리
세 편에 걸쳐 컬럼형 DB를 이론(1편), 실측(2편), 대안 비교(3편)로 정리해봤다. 처음에는 "컬럼형이 왜 빠른가"라는 단순한 호기심에서 시작했는데, 끝에 와서는 "그래서 어떤 상황에 뭘 쓰는 게 맞나"에 대해 나름의 기준이 생긴 것 같다.
가장 인상 깊었던 건 TimescaleDB였다. PostgreSQL을 그대로 두고도 raw만으로 ClickHouse에 가까운 성능을 냈고 쓰기는 더 빨랐다. 같은 컬럼형이라도 순수 컬럼형(ClickHouse)과 하이브리드(TimescaleDB)가 쓰기에서 갈린다는 점이 특히 기억에 남는다.
'개발 공부 > DB' 카테고리의 다른 글
| MySQL에서 JSON 컬럼 써도 되나요? (0) | 2026.06.21 |
|---|---|
| [컬럼형 DB 파헤치기] 2편 - MySQL vs ClickHouse 성능 측정 (0) | 2026.05.31 |
| [컬럼형 DB 파헤치기] 1편 - Row DB와 뭐가 다른가 (1) | 2026.05.24 |
| 왜 서로 다른 값을 INSERT 했는데 데드락이 걸릴까? (0) | 2026.02.02 |