LLM WikiAccess-protected knowledge portal

WIKI

TimescaleDB: PostgreSQL 위에서 시계열 처리

PostgreSQL을 버리지 않는 선택 새로운 시계열 데이터베이스를 도입할 때 가장 큰 장벽은 기존 PostgreSQL 생태계와의 단절 이다. JOIN, 외래 키, 복잡한 SQL, ORM, BI 도구, psycopg2/libpq 드라이버... 이 모든 것을 포기하고 새 도구의 쿼리 언어와 운영 방식을 익혀야 한다. TimescaleDB는 이 문제를 다르게 접근한다. PostgreSQL을 교체하는 대신 PostgreSQL 확장

경로human/study/content/time-series-databases/03-timescaledb-postgresql-time-series.md
카테고리Study
태그#databases #mysql #postgresql #series #study #time #timescaledb

PostgreSQL을 버리지 않는 선택

새로운 시계열 데이터베이스를 도입할 때 가장 큰 장벽은 기존 PostgreSQL 생태계와의 단절이다. JOIN, 외래 키, 복잡한 SQL, ORM, BI 도구, psycopg2/libpq 드라이버... 이 모든 것을 포기하고 새 도구의 쿼리 언어와 운영 방식을 익혀야 한다.

TimescaleDB는 이 문제를 다르게 접근한다. PostgreSQL을 교체하는 대신 PostgreSQL 확장(extension)으로 설치해, 기존 SQL 인터페이스를 그대로 유지하면서 시계열 워크로드 성능을 크게 높인다.

-- 일반 PostgreSQL 테이블처럼 만들고...
CREATE TABLE metrics (
  time        TIMESTAMPTZ NOT NULL,
  host        TEXT,
  cpu_percent DOUBLE PRECISION
);

-- 한 줄로 하이퍼테이블로 변환
SELECT create_hypertable('metrics', by_range('time'));

이후 INSERT, SELECT, JOIN, GROUP BY는 평범한 PostgreSQL SQL 그대로다. 쿼리 플래너가 내부적으로 파티션을 인식하고 최적화할 뿐이다.


핵심 개념: 하이퍼테이블과 청크

하이퍼테이블(Hypertable)은 TimescaleDB가 제공하는 가상 테이블이다. 외부에서 보면 단일 테이블처럼 보이지만, 내부적으로 시간 범위에 따라 여러 청크(Chunk)로 자동 분할된다.

각 청크는 실제 PostgreSQL 테이블이다. PostgreSQL의 파티션 테이블과 유사하지만, TimescaleDB가 파티션 생성·삭제·압축 등을 자동으로 관리한다는 점이 다르다.

TimescaleDB 하이퍼테이블 — 자동 시간 파티셔닝 하이퍼테이블: metrics 애플리케이션에는 단일 테이블처럼 보임 청크 1 (콜드) 2026-06-01 ~ 06-07 컬럼스토어 압축 읽기 전용 청크 2 (웜) 2026-07-01 ~ 07-07 Hypercore (행+열 혼합) 읽기/쓰기 가능 청크 3 (핫) 2026-07-08 ~ 현재 로우스토어 (행 기반) 활성 쓰기 PostgreSQL 힙 테이블 + zstd 압축 행 + 컬럼 혼합 (Hypercore) PostgreSQL 표준 힙 테이블 보존 만료 삭제 ← 과거 현재 청크 크기(기본 7일)는 create_hypertable 시 chunk_time_interval 파라미터로 설정
TimescaleDB 하이퍼테이블 구조

청크 크기 설정 기준: 메모리의 25% 정도가 하나의 청크에 들어오는 크기가 권장값이다. 청크 크기가 너무 작으면 Catalog 메타데이터가 폭발하고, 너무 크면 청크 압축·삭제 작업이 오래 걸린다.


Hypercore: 행-열 하이브리드 스토리지

TimescaleDB 2.18(2025년 12월)에서 정식 출시된 Hypercore는 하나의 하이퍼테이블이 로우스토어(Rowstore)컬럼스토어(Columnstore)를 동시에 유지하는 혼합 스토리지 엔진이다.

Rowstore (로우스토어)
• 새로 들어오는 데이터를 받음
• 표준 PostgreSQL 힙(heap) 테이블
• INSERT / UPDATE / DELETE 완전 지원
• 인덱스도 일반 B-Tree 그대로
• 쓰기 성능 최우선
Columnstore (컬럼스토어)
• 오래된 청크를 백그라운드에서 압축 변환
• 컬럼 단위 저장 → 집계 쿼리 I/O 대폭 감소
• 압축률 최대 98% (zstd + 특화 인코딩)
• 변환 후에도 일반 SELECT로 투명하게 조회
• 수정은 비동기 재압축으로 처리
⚙️ 자동 변환 정책: add_columnstore_policy('metrics', after => INTERVAL '7 days') — 7일 지난 청크는 자동으로 컬럼스토어로 전환. 애플리케이션 코드 변경 없음.
투명한 쿼리: SELECT avg(cpu_percent) FROM metrics WHERE time > now() - INTERVAL '30d'
→ 플래너가 자동으로 로우스토어와 컬럼스토어를 결합해 최적 계획 수립. 사용자는 스토리지 형태를 신경 쓸 필요 없음.
Hypercore: 쓰기와 분석을 동시에

압축 인코딩 방식:

데이터 타입인코딩 방법
타임스탬프, 정수Delta + Delta-of-Delta + Simple-8b
부동소수점XOR 기반(Gorilla 변형)
반복 값 많은 컬럼Run-Length Encoding (RLE)
문자열/고카디널리티Dictionary 압축
일반zstd 블록 압축

연속 집계(Continuous Aggregates)

시계열 데이터의 가장 일반적인 쿼리 패턴은 "지난 1시간의 평균 CPU를 1분 단위로"처럼 시간 버킷 집계다. 원본 테이블에 매번 쿼리하면 수십억 행을 스캔해야 한다. Continuous Aggregate는 이 집계 결과를 물질화(materialization)하고 증분 갱신하는 기능이다.

-- 1분 단위 CPU 평균을 사전 집계
CREATE MATERIALIZED VIEW metrics_1min
WITH (timescaledb.continuous) AS
SELECT
  time_bucket('1 minute', time) AS bucket,
  host,
  avg(cpu_percent)  AS avg_cpu,
  max(cpu_percent)  AS max_cpu
FROM metrics
GROUP BY bucket, host;

-- 자동 갱신 정책: 최근 1시간 데이터를 1분마다 갱신
SELECT add_continuous_aggregate_policy('metrics_1min',
  start_offset => INTERVAL '2 hours',
  end_offset   => INTERVAL '1 minute',
  schedule_interval => INTERVAL '1 minute');

갱신 정책의 핵심은 변경된 시간 버킷만 재계산한다는 점이다. 새 데이터가 2026-07-12 14:35에 들어오면 14:35 버킷만 다시 집계하고, 나머지는 그대로 유지한다.

계층적 연속 집계: Continuous Aggregate 위에 또 다른 Continuous Aggregate를 쌓아 1분 → 1시간 → 1일로 롤업 체인을 구성할 수 있다. 각 레벨은 이전 레벨의 결과만 읽어 갱신하므로 원본 테이블 접근을 최소화한다.


보존 정책과 데이터 계층화

-- 3개월 지난 청크 자동 삭제
SELECT add_retention_policy('metrics', INTERVAL '3 months');

-- 30일 지난 데이터를 S3(Tiered Storage)로 이동
SELECT add_tiering_policy('metrics', INTERVAL '30 days');

Timescale의 계층형 스토리지(Tiered Storage)는 오래된 청크를 S3 호환 오브젝트 스토리지로 이동하면서도 SQL SELECT로 투명하게 조회한다. 자주 쓰는 최근 데이터는 로컬 SSD에, 장기 보존 데이터는 저비용 오브젝트 스토리지에 유지하는 패턴이다.


공간 분할(Space Partitioning)

시간 파티셔닝만으로는 단일 노드의 쓰기 병목을 해결하기 어렵다. 공간 분할은 시간 차원 외에 추가 컬럼(예: host)으로도 청크를 나눠 쓰기를 여러 청크에 분산한다.

SELECT create_hypertable('metrics',
  by_range('time'),
  by_hash('host', 4)   -- host 값을 해시해서 4개 공간 파티션
);

공간 파티션을 늘리면 동시 쓰기 처리량이 높아지지만, 쿼리가 특정 host를 지정하지 않으면 모든 공간 파티션을 읽어야 해서 쿼리 성능이 떨어질 수 있다.


TimescaleDB의 PostgreSQL 활용 전략

TimescaleDB가 PostgreSQL을 사용한다는 것의 실질적인 장점을 세 가지로 정리한다.

관계형 기능 완전 활용
  • 시계열 테이블 ↔ 메타 테이블 JOIN
  • 외래 키, 유니크 제약
  • 윈도우 함수, CTEs
  • 트랜잭션 ACID 보장
기존 도구 재사용
  • psql, pgAdmin, DBeaver
  • pg_dump / pg_restore
  • SQLAlchemy, GORM, Diesel
  • Grafana PostgreSQL 데이터소스
PostgreSQL 확장 호환
  • PostGIS (지리 데이터)
  • pg_stat_statements
  • pgvector (임베딩)
  • Citus (분산 쿼리)
TimescaleDB가 PostgreSQL 생태계에서 얻는 것

JOIN의 실질적 가치: IoT 센서 메트릭 테이블과 sensors 메타 테이블(위치, 모델, 담당자)을 JOIN해 "XX 지역 센서의 어제 평균 온도"를 단일 쿼리로 구할 수 있다. InfluxDB 같은 순수 TSDB에서는 이 작업에 애플리케이션 레벨 조인이나 별도 데이터 파이프라인이 필요하다.


성능 특성과 한계

강점:

한계:


언제 TimescaleDB를 선택하는가

상황추천
시계열 + 관계형 데이터를 함께 다루고 JOIN이 필수TimescaleDB
기존 PostgreSQL 인프라·팀 역량 유지TimescaleDB
단일 노드 또는 소규모 클러스터로 충분TimescaleDB
수억 시리즈, 초당 수백만 쓰기 요구InfluxDB IOx / VictoriaMetrics
Prometheus 메트릭 장기 저장만 필요VictoriaMetrics / Thanos
SQL 없이 운영·개발 비용 최소화InfluxDB Cloud Serverless

핵심 판단 기준: 데이터 파이프라인에서 시계열 데이터가 관계형 데이터와 함께 쓰여야 한다면 TimescaleDB가 자연스러운 선택이다. 순수 메트릭 저장과 대규모 수평 확장이 목적이라면 InfluxDB나 VictoriaMetrics가 더 적합하다.


References