TSDB(시계열 데이터베이스) 소개: 왜 필요하고, 어떤 걸 써야 할까?

TSDB(시계열 데이터베이스) 소개: 왜 필요하고, 어떤 걸 써야 할까?


서론

서버 CPU 사용률, 요청 지연 시간, IoT 센서 온도 — 이런 데이터의 공통점은 시간 축을 기준으로 끊임없이 쌓인다는 것이다. 이런 데이터를 시계열 데이터(Time Series Data) 라고 하고, 이를 효율적으로 저장·조회하기 위해 만들어진 전용 데이터베이스가 TSDB(Time Series Database) 다.

“그냥 MySQL에 timestamp 컬럼 넣으면 안 되나?” — 소규모에서는 가능하지만, 초당 수만 건씩 쌓이는 메트릭을 다루기 시작하면 RDB는 한계에 부딪힌다.

쉽게 말하면, TSDB는 체온 기록표에 특화된 노트라고 생각하면 된다. 일반 노트(RDB)에도 체온을 기록할 수 있지만, 체온 기록 전용 노트는 시간별로 칸이 나뉘어 있고, 그래프를 바로 그릴 수 있고, 오래된 페이지는 자동으로 떼어내 버릴 수 있다. 데이터가 몇 줄일 때는 아무 노트나 써도 되지만, 매일 수만 건씩 쌓이면 전용 노트가 압도적으로 편하다.


1. 시계열 데이터의 특성

시계열 데이터는 일반적인 비즈니스 데이터와 성격이 다르다.

특성시계열 데이터일반 비즈니스 데이터
쓰기 패턴Append-only (삽입만)CRUD (삽입·수정·삭제)
읽기 패턴최근 데이터 위주, 시간 범위 집계랜덤 액세스, 개별 조회
데이터 양초당 수천~수만 건상대적으로 적음
수명오래된 데이터는 가치 감소영구 보관 필요
갱신거의 없음빈번함

핵심 키워드는 “대량 쓰기, 시간 범위 조회, 오래된 데이터 자동 정리” 다.


2. RDB로 하면 안 되는 이유

MySQL이나 PostgreSQL에 시계열 데이터를 넣으면 어떤 일이 생기는지 보자.

2.1 쓰기 성능 병목

RDB는 행(row)을 삽입할 때 인덱스를 업데이트하고, 트랜잭션 로그를 기록한다. 초당 1만 건씩 쌓이는 메트릭 데이터에서는 이 오버헤드가 치명적이다.

비유하자면, RDB에 데이터를 넣는 건 도서관에 책을 꽂으면서 매번 목록표도 고치는 것과 같다. 한두 권은 괜찮지만, 1초에 책 수만 권이 쏟아진다면? 목록표 고치느라 책을 꽂지도 못한다. TSDB는 책을 일단 순서대로 쌓아두고 목록은 나중에 한꺼번에 정리하는 방식이라 훨씬 빠르다.

# 서버 100대 × 메트릭 50개 × 10초 간격 = 초당 500건
# 서버 10,000대라면? 초당 50,000건
INSERT INTO metrics (timestamp, host, metric_name, value) VALUES (...)

TSDB는 배치 쓰기, 압축 저장에 최적화되어 이 수준의 쓰기를 쉽게 처리한다.

2.2 저장 공간 폭발

1초 간격으로 메트릭 1개를 1년간 저장하면:

365일 × 24시간 × 60분 × 60초 = 31,536,000 행 (메트릭 1개당)

메트릭이 100개라면 31억 행이다. 일반 RDB에서 이걸 감당하려면 디스크 비용이 어마어마하다.

TSDB는 시계열 데이터에 특화된 압축 알고리즘(delta encoding, gorilla compression 등)으로 일반 RDB 대비 10~20배 적은 공간을 사용한다.

2.3 집계 쿼리 성능

“지난 7일간 CPU 평균”을 구하려면 RDB에서는 수백만 행을 스캔해야 한다. TSDB는 시간 기반 파티셔닝과 사전 집계(downsampling) 로 이 쿼리를 밀리초 단위에 처리한다.

비유하자면, “작년 3월의 평균 기온”을 구하려고 365일치 일기장을 한 장씩 넘기는 것이 RDB 방식이다. TSDB는 미리 월별 요약 페이지를 만들어두기 때문에, 해당 페이지만 펼치면 바로 답이 나온다.


3. TSDB의 핵심 기능

대부분의 TSDB가 공통으로 제공하는 기능들이다.

3.1 자동 다운샘플링 (Downsampling)

1초 간격의 원본 데이터를 일정 기간이 지나면 자동으로 집계한다:

# 원본: 1초 간격
09:00:01 → cpu: 45.2%
09:00:02 → cpu: 46.1%
09:00:03 → cpu: 44.8%
...

# 7일 후 자동 다운샘플링: 1분 평균으로 압축
09:00 → cpu_avg: 45.4%
09:01 → cpu_avg: 47.2%

# 30일 후: 1시간 평균으로 추가 압축
09:00 → cpu_avg: 46.1%

최근 데이터는 높은 해상도로, 과거 데이터는 낮은 해상도로 유지하여 저장 공간을 절약한다.

스마트워치의 심박수 기록을 생각하면 이해가 쉽다. 오늘 데이터는 1초 단위로 상세하게 볼 수 있지만, 6개월 전 데이터는 “그날 평균 심박수”만 남아 있는 것과 같은 원리다. 상세한 과거 데이터가 필요한 경우는 드물기 때문에, 이렇게 하면 저장 공간을 10분의 1 이하로 줄일 수 있다.

3.2 자동 데이터 만료 (Retention Policy)

오래된 데이터를 자동으로 삭제한다. RDB에서는 DELETE 쿼리를 별도로 실행해야 하지만, TSDB는 설정만 해두면 된다.

# InfluxDB: 30일 후 자동 삭제
CREATE RETENTION POLICY "one_month" ON "mydb" DURATION 30d REPLICATION 1 DEFAULT

# Prometheus: 설정 파일에서 지정
--storage.tsdb.retention.time=30d

3.3 레이블 기반 조회

TSDB는 메트릭에 레이블(tag) 을 붙여서 다차원 조회를 지원한다.

# 메트릭: http_requests_total
# 레이블: method="GET", status="200", service="order-api"

# "order-api의 5xx 에러 비율"을 한 줄로 조회
rate(http_requests_total{service="order-api", status=~"5.."}[5m])

RDB에서 같은 조회를 하려면 복잡한 JOIN과 GROUP BY가 필요하다.

레이블은 해시태그와 비슷하다. 인스타그램에서 #서울 #맛집 #파스타로 검색하면 해당하는 게시물만 나오듯이, TSDB에서도 service="order-api", status="500" 같은 레이블을 조합하면 원하는 메트릭만 즉시 필터링할 수 있다.


4. 대표 TSDB 비교

4.1 Prometheus

# prometheus.yml
scrape_configs:
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['localhost:8080']
항목내용
유형Pull 기반 (서버가 대상을 주기적으로 스크래핑)
쿼리 언어PromQL
저장로컬 디스크 (자체 TSDB 엔진)
장점Kubernetes 생태계 표준, Grafana 연동, 알림 매니저 내장
단점장기 저장에 부적합 (단일 노드), 클러스터링 미지원
적합한 경우인프라/애플리케이션 모니터링, K8s 환경

Pull vs Push가 뭐야?

  • Pull 방식 (Prometheus): “내가 네 상태를 10초마다 확인할게” — 서버가 능동적으로 데이터를 가져온다.
  • Push 방식 (InfluxDB): “내 상태가 바뀌면 내가 알려줄게” — 클라이언트가 직접 데이터를 보낸다.

Prometheus가 Pull을 쓰는 이유는, 대상이 죽었는지 살았는지를 “확인하러 갔는데 응답이 없네?”로 자연스럽게 감지할 수 있기 때문이다.

Prometheus는 어떻게 Pull하는 걸까?

원리는 아주 단순하다. 대상 앱이 /metrics 엔드포인트를 HTTP로 열어두면, Prometheus가 주기적으로 GET 요청을 보내서 데이터를 가져간다.

[Spring Boot App]                          [Prometheus]
   :8080/actuator/prometheus                  :9090
         │                                      │
         │  ← GET /actuator/prometheus ────── │  (15초마다)
         │                                      │
         │  ── 텍스트 응답 ──────────────────→ │
         │                                      │
                                          파싱 → TSDB 저장

/metrics 응답은 이런 텍스트다:

# TYPE http_requests_total counter
http_requests_total{method="GET",status="200"} 1523
http_requests_total{method="POST",status="201"} 342

# TYPE process_cpu_usage gauge
process_cpu_usage 0.0423

JSON도 아니고, 특별한 프로토콜도 아니다. 그냥 key-value 텍스트다.

Spring Boot에서는 의존성 하나면 끝이다:

// build.gradle
implementation 'io.micrometer:micrometer-registry-prometheus'

이걸 추가하면 /actuator/prometheus 엔드포인트가 자동으로 생기고, JVM 메트릭, HTTP 요청 수, 응답 시간 등이 자동으로 노출된다. 앱 코드를 건드릴 필요가 없다.

Kubernetes 환경에서는 Prometheus가 서비스 디스커버리로 Pod를 자동 감지하기 때문에, 서버가 늘거나 줄어도 설정을 고칠 필요가 없다.

PromQL 예시:

# 최근 5분간 HTTP 요청 비율 (초당)
rate(http_server_requests_seconds_count[5m])

# CPU 사용률 상위 5개 서버
topk(5, 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100))

# 95퍼센타일 응답 시간
histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m]))

4.2 InfluxDB

# InfluxDB Line Protocol로 데이터 쓰기
curl -XPOST 'http://localhost:8086/write?db=mydb' \
  --data-binary 'cpu,host=server01,region=kr value=0.64 1742212800000000000'
항목내용
유형Push 기반 (클라이언트가 데이터를 전송)
쿼리 언어Flux / InfluxQL (SQL 유사)
저장자체 TSM 엔진
장점SQL과 유사한 쿼리, 다운샘플링 내장, 클라우드 서비스 제공
단점오픈소스 버전은 클러스터링 미지원 (Enterprise만 가능)
적합한 경우IoT, 비즈니스 메트릭, 독립적인 시계열 저장소가 필요한 경우

InfluxDB는 어떤 느낌이야?

“MySQL은 좀 아는데 Prometheus 쿼리는 어렵다” 하는 사람에게 적합하다. InfluxQL은 SQL과 매우 비슷해서 SELECT mean(cpu) FROM metrics WHERE time > now() - 1h GROUP BY time(5m) 같은 쿼리를 바로 쓸 수 있다.

InfluxDB의 Push 방식은 어떻게 동작할까?

InfluxDB는 자체적으로 HTTP API 포트(기본 8086) 를 열고 대기한다. 클라이언트(앱, IoT 디바이스 등)가 이 포트로 데이터를 POST 요청으로 보내면 InfluxDB가 받아서 저장한다.

[IoT 센서]  ──POST──→  [InfluxDB :8086]
[Spring App] ──POST──→  [InfluxDB :8086]
[Telegraf]   ──POST──→  [InfluxDB :8086]

Prometheus처럼 대상을 찾아다니는 게 아니라, 데이터가 알아서 들어오는 구조다. IoT 센서처럼 네트워크 안쪽에 있어서 외부에서 접근하기 어려운 디바이스에서 특히 유리하다.

데이터 전송 포맷은 Line Protocol이라는 간단한 텍스트 형식이다:

# 형식: 메트릭이름,태그1=값1,태그2=값2 필드=값 타임스탬프
cpu,host=server01,region=kr usage=0.64 1742212800000000000
temperature,sensor=A1,floor=3 value=24.5 1742212800000000000

한 줄에 메트릭 하나. 초당 수십만 건도 거뜬히 받아낸다.

실무에서는 InfluxDB에 직접 쏘기보다 Telegraf(InfluxDB의 공식 에이전트)를 중간에 두는 경우가 많다. Telegraf가 시스템 메트릭을 수집해서 InfluxDB로 전달하는 역할을 한다.

Flux 쿼리 예시:

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

4.3 TimescaleDB

-- PostgreSQL 확장이므로 일반 SQL 사용
CREATE TABLE metrics (
    time        TIMESTAMPTZ NOT NULL,
    host        TEXT,
    cpu_usage   DOUBLE PRECISION
);

-- 하이퍼테이블로 변환 (시계열 최적화)
SELECT create_hypertable('metrics', 'time');

-- 일반 SQL로 조회
SELECT time_bucket('5 minutes', time) AS interval,
       host,
       avg(cpu_usage) as avg_cpu
FROM metrics
WHERE time > now() - interval '1 hour'
GROUP BY interval, host
ORDER BY interval DESC;
항목내용
유형PostgreSQL 확장
쿼리 언어SQL (표준 PostgreSQL)
저장PostgreSQL의 테이블을 시간 기반으로 자동 파티셔닝
장점기존 PostgreSQL 지식 그대로 사용, JOIN 가능, 풀 SQL 지원
단점순수 TSDB 대비 쓰기 성능 낮음, 운영 복잡도
적합한 경우시계열 + 관계형 데이터를 함께 다뤄야 하는 경우

TimescaleDB는 어떤 상황에서 쓰면 좋을까?

예를 들어 “주문 테이블(orders)과 서버 응답 시간(metrics)을 JOIN해서, 응답이 느렸던 시간대의 주문 취소율을 분석하고 싶다”는 경우. Prometheus나 InfluxDB에서는 불가능하지만, TimescaleDB는 같은 PostgreSQL 안에서 SQL JOIN으로 바로 할 수 있다.

TimescaleDB는 정말 일반 SQL로 동작할까?

그렇다. TimescaleDB는 PostgreSQL의 확장(Extension) 이기 때문에, 기존 PostgreSQL에 설치만 하면 된다. 새로운 데이터베이스를 배우는 게 아니다.

-- 1. 확장 설치 (한 번만)
CREATE EXTENSION IF NOT EXISTS timescaledb;

-- 2. 일반 테이블 생성 (PostgreSQL과 동일)
CREATE TABLE sensor_data (
    time        TIMESTAMPTZ NOT NULL,
    sensor_id   TEXT,
    temperature DOUBLE PRECISION,
    humidity    DOUBLE PRECISION
);

-- 3. 하이퍼테이블로 변환 (이 한 줄이 마법)
SELECT create_hypertable('sensor_data', 'time');

create_hypertable을 실행하는 순간, 내부적으로 시간 기반 자동 파티셔닝이 적용된다. 하지만 사용하는 입장에서는 아무것도 달라지지 않는다:

-- INSERT도 똑같다
INSERT INTO sensor_data VALUES (now(), 'A1', 24.5, 60.2);

-- SELECT도 똑같다
SELECT * FROM sensor_data WHERE time > now() - interval '1 hour';

-- 다른 테이블과 JOIN도 된다 (Prometheus/InfluxDB에서는 불가능)
SELECT s.sensor_id, s.temperature, l.location_name
FROM sensor_data s
JOIN sensor_locations l ON s.sensor_id = l.sensor_id
WHERE s.time > now() - interval '1 hour';

time_bucket이라는 TimescaleDB 전용 함수만 빼면, 나머지는 100% 표준 PostgreSQL SQL이다. pg_dump, pg_restore, psql 같은 기존 도구도 전부 그대로 사용할 수 있다.

4.4 비교 요약

PrometheusInfluxDBTimescaleDB
쿼리 언어PromQLFlux / InfluxQLSQL
학습 곡선중간낮음낮음 (SQL)
쓰기 성능높음매우 높음중간
장기 저장△ (외부 연동 필요)
K8s 연동◎ (표준)
관계형 JOIN
라이선스Apache 2.0MIT (OSS) / 상용Apache 2.0 / 상용

5. 어떤 TSDB를 선택해야 할까?

Kubernetes 모니터링이 목적이라면 → Prometheus

Kubernetes와 Prometheus는 사실상 세트다. 서비스 디스커버리, kube-state-metrics, Grafana 대시보드까지 이미 생태계가 완성되어 있다.

장기 저장이 필요하면 ThanosCortex를 Prometheus 위에 얹으면 된다.

IoT나 비즈니스 메트릭이 목적이라면 → InfluxDB

센서 데이터, 주가, 사용자 행동 메트릭 등 Push 방식이 자연스러운 시나리오에 적합하다. SQL과 비슷한 쿼리를 제공해서 학습 곡선이 낮다.

기존 PostgreSQL을 활용하고 싶다면 → TimescaleDB

“시계열 데이터도 필요한데 별도 인프라를 띄우기 싫다”면 TimescaleDB가 답이다. 기존 PostgreSQL에 확장만 설치하면 되고, SQL을 그대로 쓸 수 있다.


6. 실무 아키텍처 예시

모니터링 시스템의 일반적인 구성을 보자:

[Spring Boot App]          [Node Exporter]          [IoT Device]
   /actuator/prometheus       :9100/metrics             MQTT
        │                         │                       │
        └──── Pull ───────────────┘                       │
                    │                                     │
              [Prometheus]                          [InfluxDB]
                    │                                     │
                    └─────────── [Grafana] ────────────────┘

                              [AlertManager]

                            Slack / PagerDuty
  • Prometheus: 인프라/앱 메트릭 수집 (Pull)
  • InfluxDB: IoT 센서 데이터 수집 (Push)
  • Grafana: 두 데이터 소스를 하나의 대시보드에서 시각화
  • AlertManager: 임계치 초과 시 알림

정리

핵심 포인트내용
TSDB란?시간 축 기준으로 쌓이는 데이터를 효율적으로 저장·조회하는 전용 DB
왜 필요한가?대량 쓰기, 시간 범위 집계, 자동 만료 — RDB로는 한계
핵심 기능다운샘플링, 데이터 만료, 레이블 기반 다차원 조회
선택 기준K8s 모니터링 → Prometheus, IoT/Push → InfluxDB, SQL 필요 → TimescaleDB

시계열 데이터를 다루게 되면 “그냥 RDB에 넣으면 되겠지”라는 생각이 들 수 있다. 소규모에서는 맞는 말이지만, 규모가 커지면 TSDB의 가치가 분명해진다. 특히 모니터링 시스템을 구축한다면 TSDB는 선택이 아니라 필수다.

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.