Skip to content
Published on

시계열 데이터베이스 완전 가이드 2025: TimescaleDB, InfluxDB, Prometheus, ClickHouse 비교

Authors

TL;DR

  • 시계열 데이터의 특성: 쓰기 압도적, 시간 순서, 거의 변경 없음, 시간 범위 쿼리, 집계가 핵심
  • 5대 옵션: TimescaleDB(PG 확장), InfluxDB, Prometheus, ClickHouse, VictoriaMetrics
  • 카디널리티가 모든 것: 레이블 폭증 시 성능이 무너짐. 설계 단계에서 신중히
  • 압축이 핵심: Gorilla(Facebook)가 표준. 12배 압축으로 디스크 비용 절감
  • 다운샘플링과 보존: 오래된 데이터는 낮은 해상도로 + 자동 삭제

1. 시계열 데이터의 본질

1.1 시계열 데이터란?

2025-04-15 10:00:00, sensor_1, temperature, 23.5
2025-04-15 10:00:01, sensor_1, temperature, 23.6
2025-04-15 10:00:02, sensor_1, temperature, 23.5
...

핵심 특성:

  1. 타임스탬프 포함 — 각 데이터가 시간을 가짐
  2. append-only — 거의 변경 안 됨
  3. 순차적 쓰기 — 보통 최신 데이터 순으로
  4. 시간 범위 쿼리 — "지난 1시간", "어제 vs 오늘"
  5. 집계 위주avg, max, percentile
  6. 대용량 — 초당 수백만 데이터 포인트

1.2 사용 사례

분야데이터
모니터링CPU, 메모리, 응답 시간
IoT센서 (온도, 습도, 진동)
금융주가, 거래량, 환율
DevOps로그, 메트릭, 트레이스
에너지전력 소비, 태양광 발전
자동차OBD, 텔레메트리
물류GPS, 차량 추적

1.3 RDB로는 안 되나?

가능하지만 비효율적:

CREATE TABLE metrics (
  time TIMESTAMP,
  device_id INT,
  metric_name VARCHAR(50),
  value FLOAT
);

CREATE INDEX idx_time ON metrics(time);

문제점:

  1. B-Tree가 시간순 삽입에 최적이 아님 — 항상 끝에 추가 = 인덱스 비효율
  2. 압축 없음 — 비슷한 값이 연속이지만 이를 활용 못 함
  3. 자동 파티셔닝 없음 — 1년치 1억 행이 단일 테이블
  4. 자동 다운샘플링 없음 — 오래된 데이터를 압축 못 함

시계열 전용 DB가 필요합니다.


2. 5대 시계열 DB 비교

2.1 TimescaleDB

기본: PostgreSQL 확장.

CREATE EXTENSION timescaledb;

CREATE TABLE metrics (
  time TIMESTAMPTZ NOT NULL,
  device_id INT,
  temperature DOUBLE PRECISION
);

-- 하이퍼테이블로 변환
SELECT create_hypertable('metrics', 'time', chunk_time_interval => INTERVAL '1 day');

장점:

  • PostgreSQL 호환 — SQL 그대로 사용
  • JOIN 지원 — 메타데이터와 시계열 결합 가능
  • 자동 파티셔닝 (chunk)
  • Continuous Aggregates — 자동 다운샘플링
  • 압축 — 11배 압축률

단점:

  • 다른 PG 부담 (동일 인스턴스)
  • 카디널리티 폭증에 민감

언제: PostgreSQL 사용 중이고 시계열 추가 필요 시.

2.2 InfluxDB

전용 시계열 DB. v2와 v3가 매우 다름.

InfluxDB v2 (Flux 언어):

from(bucket: "metrics")
  |> range(start: -1h)
  |> filter(fn: (r) => r._measurement == "cpu")
  |> aggregateWindow(every: 1m, fn: mean)

InfluxDB v3 (2024년):

  • Apache Arrow + DataFusion + Parquet 기반
  • SQL 지원
  • 컬럼 저장
  • Object storage 지원

장점:

  • 시계열 전용 → 최적화
  • TICK 스택 (Telegraf, InfluxDB, Chronograf, Kapacitor)

단점:

  • v1 → v2 → v3 마이그레이션 어려움
  • 카디널리티 한계 (v1/v2)

2.3 Prometheus

모니터링 전용 TSDB. Pull 모델.

scrape_configs:
  - job_name: 'app'
    static_configs:
      - targets: ['localhost:8080']
# 지난 5분 평균 응답 시간
rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m])

# CPU 사용률
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

장점:

  • CNCF 졸업 프로젝트 — Kubernetes 표준
  • Pull 모델 — 서비스 디스커버리
  • PromQL — 강력한 쿼리 언어
  • AlertManager 통합

단점:

  • 로컬 저장만 — 장기 보존 어려움 (Thanos, Cortex 필요)
  • 수평 확장 X — 단일 노드
  • 카디널리티 폭증에 매우 민감

언제: 모니터링 (특히 K8s)에 적합.

2.4 ClickHouse

OLAP 컬럼 DB지만 시계열에 매우 강함.

CREATE TABLE metrics (
  time DateTime,
  device_id UInt32,
  temperature Float32
) ENGINE = MergeTree()
ORDER BY (device_id, time);

장점:

  • 압도적 쿼리 성능 — 컬럼 저장 + 벡터화
  • 거대한 데이터 — 페타바이트
  • 실시간 INSERT — 초당 수백만 행
  • SQL + 강력한 함수

단점:

  • 실시간 단일 행 update 약함
  • 메모리 많이 사용
  • 운영 복잡

언제: 대규모 분석 + 시계열 동시 필요.

2.5 VictoriaMetrics

Prometheus 호환 + 수평 확장.

장점:

  • PromQL 호환 + 추가 함수
  • 수평 확장 — 클러스터 모드
  • 압도적 압축 (Gorilla + 추가 최적화)
  • InfluxDB, Graphite, OpenTSDB 호환
  • 메모리 효율 — Prometheus 대비 7배

단점:

  • 상대적으로 신생
  • 일부 PromQL 동작 차이

언제: Prometheus 확장이 필요할 때.

2.6 비교표

TimescaleDBInfluxDBPrometheusClickHouseVictoriaMetrics
유형PG 확장전용 TSDB모니터링 TSDBOLAP DB모니터링 TSDB
언어SQLFlux/SQLPromQLSQLPromQL+
모델PushPushPullPushPush/Pull
압축11x좋음보통매우 좋음최고
수평 확장제한적v3❌ (외부 도구)
카디널리티보통약함약함강함강함
JOIN제한적
사용 사례일반IoT/일반모니터링분석+TS모니터링

3. 카디널리티 — 시계열 DB의 가장 큰 적

3.1 카디널리티란?

시계열 = 메트릭 이름 + 레이블 조합:

http_requests_total{method="GET", path="/api/users", status="200"}
http_requests_total{method="POST", path="/api/users", status="200"}
http_requests_total{method="GET", path="/api/users", status="500"}
...

카디널리티 = 고유 시계열의 수.

3.2 폭증 시나리오

http_requests_total{
  method="GET",         # 5  path="/api/users",    # 100  status="200",         # 10  user_id="12345",      # 1000000개 ← 위험!
}

5 × 100 × 10 × 1,000,000 = 50억 시계열.

메모리 폭증, 인덱스 폭증, 쿼리 느려짐. 카디널리티 1억 넘으면 대부분의 TSDB가 죽습니다.

3.3 절대 레이블에 넣지 말 것

사용자 ID (수백만+) ❌ 요청 ID (무한) ❌ 이메일 (이메일마다 새 시계열) ❌ 타임스탬프 (값으로 사용해야 함) ❌ 자유 텍스트 (URL 파라미터 등) ❌ 세션 ID

HTTP 메서드 (5개) ✅ 상태 코드 (10개) ✅ 인스턴스 이름 (수백 개) ✅ 환경 (3개: dev/staging/prod) ✅ 리전 (10개)

3.4 카디널리티 방지 전략

1. 레이블 정규화:

path="/api/users/12345" → path="/api/users/{id}"

2. 사전 집계:

사용자별 → 사용자 그룹별로 집계

3. 별도 테이블/필드:

사용자 ID는 레이블 X, 이벤트 데이터로

4. 카디널리티 모니터링:

# Prometheus에서 가장 큰 메트릭
topk(10, count by (__name__)({__name__=~".+"}))

4. 압축 — Gorilla와 그 후예

4.1 시계열 데이터의 특성

관찰:

  • 타임스탬프는 균일 간격 (예: 매 10초)
  • 값은 점진적 변화 (예: 23.5 → 23.6 → 23.5)
  • 같은 metric은 같은 타입

이를 활용하면 극단적 압축 가능.

4.2 Gorilla — Facebook 2015

페이스북이 발표한 시계열 압축 알고리즘.

타임스탬프 압축:

  1. 첫 타임스탬프는 그대로
  2. 다음 타임스탬프는 델타 저장 (이전과의 차이)
  3. 그 다음은 delta-of-delta (변화의 변화)
T1=1000, T2=1010, T3=1020, T4=1030
1000, +10, +0, +0  (10은 한 번만, 나머지는 0)

값 압축:

  1. 첫 값 그대로 (8 bytes)
  2. 다음 값은 XOR 이전 값
  3. XOR 결과의 leading zero + 의미 있는 비트만 저장

결과: 16 bytes/포인트 → 평균 1.37 bytes/포인트. 12배 압축.

4.3 압축 효과

1000개 메트릭 × 초당 1개 × 1= 31,536,000,000 포인트

압축 전: 16 bytes × 31B = 504 GB
Gorilla: 1.37 bytes × 31B = 43 GB

11배 절약. 디스크 비용, 백업, 네트워크 전송 모두 절감.

4.4 후속 알고리즘

  • Chimp — Gorilla 후속 (2022)
  • TSXor — 더 단순한 변형
  • Zstd 추가 — 블록 단위 추가 압축

5. 다운샘플링 (Downsampling)

5.1 왜 필요한가?

1초 해상도 데이터를 1년 보관 = 매우 큰 데이터.

현실:

  • 최근 7일: 1초 해상도 (디버깅용)
  • 7일 ~ 30일: 1분 해상도
  • 30일 ~ 1년: 1시간 해상도
  • 1년 +: 1일 해상도

오래된 데이터는 자세한 정보 불필요. 요약으로 충분.

5.2 TimescaleDB Continuous Aggregates

-- 매 1시간마다 평균/최대/최소 미리 계산
CREATE MATERIALIZED VIEW metrics_hourly
WITH (timescaledb.continuous) AS
SELECT
  time_bucket('1 hour', time) AS hour,
  device_id,
  AVG(temperature) AS avg_temp,
  MAX(temperature) AS max_temp,
  MIN(temperature) AS min_temp,
  COUNT(*) AS sample_count
FROM metrics
GROUP BY hour, device_id;

-- 자동 갱신 정책
SELECT add_continuous_aggregate_policy('metrics_hourly',
  start_offset => INTERVAL '3 hours',
  end_offset => INTERVAL '1 hour',
  schedule_interval => INTERVAL '1 hour');

5.3 보존 정책 (Retention)

-- 30일 이상 raw 데이터 자동 삭제
SELECT add_retention_policy('metrics', INTERVAL '30 days');

-- 1년 이상 hourly 데이터 자동 삭제
SELECT add_retention_policy('metrics_hourly', INTERVAL '1 year');

5.4 Prometheus 다운샘플링

기본 Prometheus는 다운샘플링 없음. 외부 도구 필요:

  • Thanos — 장기 저장 + 다운샘플링
  • Cortex — 멀티테넌트
  • VictoriaMetrics — 내장

6. PromQL — Prometheus 쿼리 언어

6.1 기본 쿼리

# 인스턴스의 현재 CPU 사용률
node_cpu_seconds_total

# 특정 레이블 필터
node_cpu_seconds_total{instance="server-1", mode="user"}

# 여러 레이블
node_cpu_seconds_total{instance=~"server-.*", mode!="idle"}

6.2 Range Vector

# 지난 5분간의 데이터
node_cpu_seconds_total[5m]

6.3 함수

# 지난 5분 평균 변화율
rate(http_requests_total[5m])

# 지난 5분 최대값 - 최소값
max_over_time(temperature[5m]) - min_over_time(temperature[5m])

# 백분위수
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

6.4 집계

# 인스턴스별 합산
sum by (instance) (rate(http_requests_total[5m]))

# 상위 10개
topk(10, rate(http_requests_total[5m]))

# 평균
avg by (job) (cpu_usage_percent)

6.5 알림 쿼리

# 5분간 CPU 사용률 80% 초과
avg(cpu_usage_percent) > 80

# 1분간 5xx 비율 1% 초과
sum(rate(http_requests_total{status=~"5.."}[1m])) 
  / sum(rate(http_requests_total[1m])) > 0.01

7. 실전 — Prometheus + Grafana 스택

7.1 아키텍처

[Apps]/metrics endpoint
             (scrape)
       [Prometheus]
             (long-term)
        [Thanos/Cortex]
             (query)
         [Grafana]
        [User/Alert]

7.2 메트릭 노출 (Node.js)

const express = require('express')
const promClient = require('prom-client')

const register = new promClient.Registry()
promClient.collectDefaultMetrics({ register })

const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status'],
  buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
})
register.registerMetric(httpRequestDuration)

const app = express()
app.use((req, res, next) => {
  const end = httpRequestDuration.startTimer()
  res.on('finish', () => {
    end({ method: req.method, route: req.route?.path || 'unknown', status: res.statusCode })
  })
  next()
})

app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType)
  res.end(await register.metrics())
})

app.listen(8080)

7.3 Prometheus 설정

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'my-app'
    static_configs:
      - targets: ['app:8080']
    
  - job_name: 'kubernetes-pods'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true

7.4 Grafana 대시보드

핵심 메트릭:

  • RED Method: Rate, Errors, Duration
  • USE Method: Utilization, Saturation, Errors
# Rate
sum(rate(http_requests_total[5m])) by (route)

# Errors
sum(rate(http_requests_total{status=~"5.."}[5m])) by (route)

# Duration (p99)
histogram_quantile(0.99,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le, route)
)

7.5 알림

groups:
  - name: app
    rules:
      - alert: HighErrorRate
        expr: sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.01
        for: 5m
        annotations:
          summary: "High error rate on {{ $labels.instance }}"

8. IoT 시나리오 — 100만 디바이스

8.1 요구사항

  • 100만 IoT 디바이스
  • 디바이스당 초당 1개 데이터 포인트
  • 1년 보관
  • 실시간 대시보드
  • 알림

8.2 데이터량 계산

1,000,000 디바이스 × 1 point/s × 86,400 s/day = 86.4B/day
86.4B/day × 365 days = 31.5 trillion points/year

압축 (1.37 bytes): 43 TB

8.3 아키텍처

[100만 디바이스]
MQTT
[Mosquitto / EMQX]
[Telegraf / Vector]
[TimescaleDB Cluster] or [VictoriaMetrics Cluster]
[Grafana]

8.4 데이터 모델

CREATE TABLE sensors (
  time TIMESTAMPTZ NOT NULL,
  device_id BIGINT,
  metric_type SMALLINT,  -- enum (1=temp, 2=humidity, ...)
  value DOUBLE PRECISION
);

SELECT create_hypertable('sensors', 'time', chunk_time_interval => INTERVAL '1 day');

-- 인덱스
CREATE INDEX ON sensors(device_id, time DESC);

-- 압축
ALTER TABLE sensors SET (
  timescaledb.compress,
  timescaledb.compress_segmentby = 'device_id'
);

SELECT add_compression_policy('sensors', INTERVAL '7 days');

8.5 카디널리티 계산

  • device_id: 1,000,000 (높음 — 각 디바이스가 별도 시계열이면 죽음)
  • metric_type: 10

해결: device_id를 컬럼으로, 레이블/태그로 X.

TimescaleDB는 SQL 컬럼이라 OK. Prometheus였다면 죽음.


9. 금융 시나리오 — 주가 데이터

9.1 특성

  • 마이크로초 정확도
  • 미세한 가격 변화 추적
  • 주문장(order book) 깊이
  • 빠른 시간 범위 쿼리
  • 백테스팅 (과거 데이터 분석)

9.2 ClickHouse 적합

CREATE TABLE trades (
  symbol String,
  trade_time DateTime64(6, 'UTC'),
  price Float64,
  volume UInt32,
  side Enum8('buy' = 1, 'sell' = 2)
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(trade_time)
ORDER BY (symbol, trade_time);

-- 1분봉 집계
SELECT
  symbol,
  toStartOfMinute(trade_time) AS minute,
  argMin(price, trade_time) AS open,
  max(price) AS high,
  min(price) AS low,
  argMax(price, trade_time) AS close,
  sum(volume) AS volume
FROM trades
WHERE symbol = 'AAPL' AND trade_time >= now() - INTERVAL 1 DAY
GROUP BY symbol, minute
ORDER BY minute;

ClickHouse는 조ㅡㄴ단 행도 초 단위에 처리. 백테스팅에 이상적.


10. 선택 가이드

10.1 의사결정 트리

PostgreSQL을 이미 사용 중인가?
├─ YESTimescaleDB (시작점)
└─ NO
   ├─ 모니터링/메트릭 전용?
   │   ├─ 작은 규모 → Prometheus
   │   └─ 큰 규모 → VictoriaMetrics
   ├─ IoT/일반?
   │   └─ InfluxDB v3 또는 TimescaleDB
   └─ 분석 + 시계열?
       └─ ClickHouse

10.2 일반적 권장

  • 시작: TimescaleDB (PG 익숙) or Prometheus (모니터링)
  • 확장: VictoriaMetrics or ClickHouse
  • IoT: InfluxDB v3 or TimescaleDB
  • 금융/분석: ClickHouse

퀴즈

1. 시계열 DB가 일반 RDB보다 빠른 이유는?

: (1) 시간 기반 자동 파티셔닝 — chunk 단위로 분할 → 쿼리 시 필요한 chunk만 스캔, (2) 컬럼 저장 — 메트릭 값들이 연속 저장 → 압축 효율, 캐시 친화적, (3) 시계열 특화 압축 (Gorilla) — 12배 압축, (4) 시간 인덱스 최적화 — append-only 패턴에 최적, (5) 다운샘플링 — 오래된 데이터 자동 요약. RDB는 일반 목적 → 시계열에 비효율.

2. 카디널리티 폭증이란 무엇이고 어떻게 방지하나요?

: 메트릭의 고유 레이블 조합 수가 너무 많아져 메모리/인덱스가 폭발하는 현상. 예: user_id를 레이블로 하면 사용자 수만큼 시계열 생성 → 100만+. 방지: (1) 사용자 ID, 요청 ID, 이메일 같은 고유 식별자를 레이블에 넣지 말 것, (2) URL 경로를 정규화 (/api/users/12345/api/users/{id}), (3) 카디널리티 모니터링, (4) 사전 집계, (5) 별도 이벤트 저장소 사용.

3. Gorilla 압축이 어떻게 12배 압축을 달성하나요?

: 시계열 데이터의 두 가지 패턴을 활용합니다. (1) 타임스탬프: 균일 간격 → delta-of-delta 인코딩 (변화의 변화). 1초 간격이면 대부분 0. (2) : 비슷한 값이 연속 → XOR 이전 값 → leading zero + 의미 있는 비트만 저장. CPU 사용률 23% → 24%면 거의 같은 비트, 차이만 작게 저장. 결과: 16 bytes/포인트 → 평균 1.37 bytes/포인트.

4. Prometheus의 Pull 모델 vs InfluxDB의 Push 모델?

: Pull (Prometheus): Prometheus가 주기적으로 /metrics 엔드포인트를 긁어옴. 장점: 서비스 디스커버리, 동적 환경 적합, "건강한가" 자동 확인. 단점: 방화벽/NAT 어려움, 짧은 작업(cron) 캡처 어려움 (Pushgateway 필요). Push (InfluxDB): 클라이언트가 데이터를 보냄. 장점: 방화벽 통과, 짧은 작업 OK. 단점: 클라이언트 관리 복잡. 일반적으로 K8s 모니터링은 Pull, IoT는 Push가 적합.

5. TimescaleDB와 Prometheus의 차이는?

: TimescaleDB는 PostgreSQL 확장 → SQL 사용, JOIN 가능, 일반 데이터와 결합 가능. 카디널리티에 보다 강함. 장기 저장 쉬움. Prometheus는 모니터링 전용 → PromQL (강력하지만 학습 필요), 단일 노드, K8s 통합 표준, 장기 저장 어려움 (외부 도구 필요). 선택: 일반 시계열 + SQL 사용자는 TimescaleDB, 모니터링 + K8s는 Prometheus. 두 가지를 함께 쓰는 경우도 많음.


참고 자료