PostgreSQL과 TimescaleDB

개발세발·2025년 6월 10일

기존 진행했던 프로젝트의 리팩토링을 하면서 개인 프로젝트를 진행하며 주가 데이터를 사용하게 돼서 이를 어디에 저장할까 고민을 했으며, PostgreSQL과 TimescaleDB를 사용해서 저장하기로 했는데, 이거를 어떻게 하면 내가 납득을 할까 고민하다가 이 흔적을 남기기 위해 블로그 들어왔음.

1. 아 어떤 DB 써야 되지

  1. 관계형 DB (RDBMS)
    PostgreSQL
  • 강력한 트랜잭션/정합성(ACID) 보장
  • 대량 데이터, 복잡한 쿼리(윈도우 함수/파티셔닝/CTE 등)에 최적
  • 오픈소스 자유도, 확장성, JSON 등 반정형 데이터 처리 지원
  • 금융/퀀트/통계 시스템에서 표준적으로 사용
  • TimescaleDB(시계열 확장 플러그인)와 완벽 호환
    MySQL
  • 운영·관리가 쉽고, 범용 서비스에 널리 사용
  • 단순 CRUD, 중소규모 프로젝트에 강점
  • 시계열·대용량 집계/배치에선 PostgreSQL 대비 다소 약점
  1. 시계열 특화 DB
  • TimescaleDB (PostgreSQL 확장 모듈)
    초고속 시계열 데이터 저장/조회/압축에 특화
    (Tick/분봉 등 증권 데이터에 매우 적합)
  • 하이퍼테이블, 자동 파티셔닝/압축, 시간 기반 집계 등 내장
  • SQL(표준 PostgreSQL 쿼리) 그대로 사용 가능
  • 복잡한 분석, 집계, 조건 검색, JOIN, 통계 연산 모두 지원
  • PostgreSQL 생태계/도구/백업 모두 그대로 사용
  • 독립형 시계열DB (InfluxDB 등) / 초대용량 시계열 수집/모니터링에는 강점
  • 복잡 집계/조인/관계형 연산, 고급 리포트에는 한계
  1. NoSQL
  • MongoDB, Cassandra 등 반정형/비정형, 초대용량 분산 저장에는 유리
  • 복잡한 관계·집계·정합성·트랜잭션 요구시 비권장
  • 주로 실시간 로그, 이벤트, 유저 데이터 등 특수 용도

📦 정리 – 내 DB 구조 선택

PostgreSQL을 기본 RDBMS로 사용하고,
시계열 데이터(주가, 체결, Tick, 분봉 등)는 TimescaleDB 모듈(하이퍼테이블)로 확장

아 PostgreSQL 확인, 근데 TimescaleDB 모듈? 하이퍼테이블?

1. PostgreSQL이란?

✅ 개요

  • 오픈소스 객체-관계형 데이터베이스 시스템(RDBMS) 중 대표적이고, "기업급" 기능을 지원하는 가장 진보된 DBMS.
  • 1986년 UC Berkeley “Ingres”에서 파생, 1996년 공식 PostgreSQL로 명명.
  • 관계형 모델 + 객체지향적 확장성(테이블 상속, 사용자 정의 타입/함수 등) 보유.

✅ 주요 특징

  • ACID 준수: 트랜잭션(Atomicity, Consistency, Isolation, Durability) 완벽 지원
  • MVCC: 동시성 제어, 락 없이 읽기/쓰기 가능 (대량 트랜잭션 환경에 강함)
  • 확장성: 함수, 자료형, 연산자, 인덱스 등 커스텀 가능
  • SQL 표준 준수: 최신 SQL 표준에 매우 충실
  • 강력한 JSON/JSONB 지원: NoSQL 느낌의 비정형/반정형 데이터 처리 가능
  • 엔터프라이즈 기능: 파티셔닝, 샤딩, 복제, 논리/물리 백업, WAL, 스트리밍 리플리케이션
  • 확장 모듈 기반: PostGIS(공간 DB), TimescaleDB(시계열 DB), Citus(분산 샤딩), pg_cron(스케줄링) 등 플러그인 무한 확장

2. TimescaleDB란?

✅ 개요

  • PostgreSQL 위에서 동작하는 "확장 모듈(Extension)" 형태의 시계열 데이터베이스
  • 대량의 시계열 데이터(센서, 로그, 금융, IoT, 모니터링 등)를 초고속 저장/조회/집계할 수 있게 저장·인덱싱 방식 최적화

✅ 구조 & 아키텍처

1) PostgreSQL Extension

  • 플러그인 형태로 설치
  • 기존 PostgreSQL의 테이블(= hypertable)을 수십~수백 개 chunk(청크)로 자동 분할(파티셔닝)
  • 파티션은 주로 시간 기준 + 옵션(디바이스 ID 등)으로 쪼갬
  • SQL 인터페이스 및 표준 도구(복제, 트랜잭션, 백업 등) 그대로 사용

2) Hypertable

  • 사용자는 “하나의 큰 테이블”처럼 사용
  • TimescaleDB가 내부적으로 적절한 청크로 나눠 관리 → 대용량 시계열 데이터에 최적화
  • 쿼리 시 자동으로 필요한 청크만 타겟팅해 IO/성능 극대화

3) Compression/Retention/Continuous Aggregates

  • 압축(Compression): 오래된 데이터는 columnar 방식으로 압축, 저장공간 최소화 및 빠른 조회
  • Continuous Aggregates: 실시간 집계(1분/10분/1시간 단위 평균, 최대, 합계 등)를 미리 계산해 저장 → 쿼리 응답 속도 향상
  • Retention Policy: 오래된 데이터 자동 삭제(모니터링/센서 데이터에 유용)

✅ TimescaleDB의 장점

  • PostgreSQL 모든 기능 + 시계열 최적화
    • JOIN, 윈도우 함수, JSON, 파티션, 인덱싱, 백업, 복제, 권한 관리 등 그대로 사용
  • 확장성: 단일 서버에서 수십억~수조 건의 시계열 데이터 쾌적하게 처리, 최신 버전은 클러스터 확장도 지원
  • 표준 SQL 호환: BI, ETL, 데이터 파이프라인, Python/R, Grafana 등과 연동 잘 됨
  • 운영 편의성: PostgreSQL 기반이라 익숙한 DBA/DevOps 환경에서 쉽게 관리

✅ 단점 또는 주의사항

  • 복잡한 쿼리 튜닝 필요 (청크, 파티션 설계에 따라 성능 차이 큼)
  • 클러스터 모드 제약: 고가용성/분산 확장(HA/분산클러스터)은 엔터프라이즈 버전에서 완벽 지원 (오픈소스는 일부 제약)
  • NoSQL처럼 무한확장/멀티마스터는 아님: 기본은 Single-master 기반

3. PostgreSQL vs TimescaleDB 비교 요약

항목PostgreSQLTimescaleDB
엔진범용 RDBMSPostgreSQL 확장, 시계열 특화
데이터 타입정형, 반정형, 시계열 모두대용량 시계열(센서, 로그, 주식 등)
파티셔닝/샤딩테이블 파티셔닝, 샤딩 지원내부 자동 파티셔닝/샤딩(청크 기반)
쿼리 성능OLTP/OLAP 모두 우수시계열 쿼리/집계에 특화(OLAP 성능↑)
백업/복구표준 도구 지원PostgreSQL 도구 그대로 사용
확장성고가용성/복제/파티셔닝 지원단일/분산(Distributed Hypertable) 지원
실무 적용 예시ERP, CRM, 서비스DB, BIIoT, 실시간 모니터링, 금융 시세, 로그분석

응? MVCC? 이게 뭐야 찾아보니까 MySQL도 되는데 이게 왜 무슨 차이점이 있는건데

🔍 MVCC(Multi-Version Concurrency Control)란?

1. 개념 한 줄 요약

  • MVCC는 “여러 버전의 데이터를 동시에 관리”하는 방식이야.
  • 동시에 여러 트랜잭션이 읽거나 쓸 때, 서로 간섭하지 않으면서 작업할 수 있도록 해주는 기술임.

2. 전통적인 락 방식(RDBMS 예시)과 비교

  • 옛날 RDBMS(예: MySQL MyISAM, Oracle의 일부 모드)에서는
    • 누군가 데이터 읽는 중이면, 쓰기 잠금(lock)이 걸려서,
    • 누군가 데이터 쓰는 중이면, 다른 사람은 읽거나 쓸 수 없음(락 대기)
  • 즉, 읽기/쓰기 요청이 동시에 오면, 락 대기(Blocking), 데드락(Deadlock) 등의 문제가 쉽게 생김
  • 동시성(Concurrency)이 떨어지고, TPS도 낮음

3. MVCC의 핵심 원리 (PostgreSQL 예시)

1) 데이터의 ‘버전’ 유지

  • PostgreSQL은 데이터를 “지우거나 덮어쓰지 않고”, 새 버전을 추가
    • 테이블의 각 행(row)은 ‘버전 정보’를 갖고 있음 (xmin, xmax 등)
    • 새로 쓰거나 수정하면, 기존 행은 그대로 두고, “새로운 행 버전”을 테이블에 기록함

2) 각 트랜잭션은 “자기만의 시점(snapshot)”에서만 데이터 조회

  • 트랜잭션이 시작될 때, ‘스냅샷(특정 시점의 데이터)’을 잡음
  • 이후에는, 다른 트랜잭션이 데이터를 바꿔도 “내 트랜잭션이 시작된 순간의 데이터”만 보여줌
  • 즉, 다른 트랜잭션이 업데이트/삭제해도 내 트랜잭션이 보게 되는 데이터에는 영향 없음

3) 락 없이 읽기/쓰기 동시처리가 가능한 이유

  • 누군가 데이터를 읽고 있을 때, 누군가가 수정하거나 삭제해도 기존 데이터는 남아있으므로 “락 걸 필요가 없음”
  • 쓰기 작업은 새 버전을 추가로 적을 뿐(기존 데이터 훼손X)
  • 읽는 입장에서는 “자기한테 맞는 버전”만 골라서 보면 됨

4) 정리

  • 읽기 작업은 락이 거의 필요 없음(Serializable 수준 제외) → 읽기-쓰기, 읽기-읽기, 쓰기-쓰기 동시성 극대화!
  • *쓰기 작업(UPDATE, DELETE)**도 ‘실제 삭제’가 아니라 “버전 종료”만 기록

4. PostgreSQL 내부 동작 예시

예를 들어, 이런 상황을 보자:

  • A 트랜잭션: SELECT * FROM orders; (읽기 전용, 1초간 유지)
  • B 트랜잭션: 그 사이에 UPDATE orders SET price = 20000 WHERE id=1; (쓰기)

이럴 때

  • A는 트랜잭션이 시작된 시점의 "orders" 테이블 데이터를 계속 본다.
  • B가 값을 수정해도, A에게는 영향 없음. (B는 새로운 버전의 row를 테이블에 추가할 뿐)
  • 락 때문에 A, B가 서로 기다리지 않음! (단, 같은 row를 동시에 UPDATE하는 경우 충돌 시엔 최종 커밋에서 예외 발생 가능)

5. “대량 트랜잭션 환경에 강하다”의 의미

  • 수천, 수만 명이 동시에 읽고/쓰는 환경(예: 금융, IoT, 실시간 로그 수집)
  • “읽기 때문에 쓰기가 막히지 않고, 쓰기 때문에 읽기가 막히지 않음”
  • 읽기 전용 쿼리가 수백, 수천 개 들어와도 TPS가 잘 나오는 이유!
  • OLTP(온라인 트랜잭션 처리) 환경에서 PostgreSQL이 성능이 잘 나오는 핵심 비결

6. “MVCC의 한계와 관리 이슈”

  • 오래된 데이터 버전(Dead Tuple)이 쌓인다
    • 그래서 PostgreSQL은 주기적으로 “VACUUM(정리)” 작업이 필요함
  • 대용량 환경에서 VACUUM 튜닝을 잘 해줘야 진짜 성능이 보장됨
  • 락이 완전히 없는 건 아니고,
    • 동시 “쓰기(같은 row)” 충돌, DDL(스키마 변경) 등에는 여전히 락 필요

하 Vaccum은 또 뭔데요... 그리고 결국엔 MySQL도 MVCC를 지원하는데 뭐가 큰 차이라고

💡 MySQL vs PostgreSQL의 MVCC - 핵심 비교

MySQL (InnoDB)PostgreSQL
MVCC 기본 구조Undo Log(언두 로그) 기반튜플 버전 관리(xmin/xmax 등) 기반
데이터 변경UPDATE/DELETE 시 “Undo Log” 기록 → 변경UPDATE/DELETE 시 “새로운 버전 튜플”을 테이블에 추가
커밋/롤백 처리Undo Log를 활용한 롤백·일관성 관리튜플의 유효성(xmin/xmax)으로 직접 일관성 관리
VACUUM/청소InnoDB가 내부적으로 Undo Log/History Cleanup명시적(VACUUM), 자동(autovacuum) 필요
Lock-Free Read지원(READ COMMITTED 이상)지원(READ COMMITTED 이상)
트랜잭션 격리REPEATABLE READ가 기본, Gap Lock 존재READ COMMITTED가 기본, 다양한 격리 수준 지원
Dead Tuple/Bloat 관리Undo 공간 관리 + Purge ThreadVACUUM/Autovacuum으로 관리

1. MVCC 구현 방식이 다름

  • MySQL(InnoDB):
    • 데이터 변경 전의 값을 Undo Log에 따로 저장해둠.
    • “지워진 버전”을 Undo Log에서 꺼내와서, 이전 시점의 데이터처럼 보여줌.
    • Undo Log가 커지면 Purge가 필요하고, 트랜잭션이 오래 가면 Undo 영역이 지저분해짐.
  • PostgreSQL:
    • 테이블 자체에 “행 버전”을 여러 개 남김(xmin, xmax)
    • 각 트랜잭션은 자신이 볼 수 있는 버전만 “스캔”함.
    • 오래된 튜플은 VACUUM/Autovacuum이 청소함.

2. PostgreSQL만의 MVCC 관련 장점/특징

  1. “Versioned Table” 아키텍처
    • “Undo Log” 따로 없이, 테이블 자체에서 버전 관리
    • 대규모 병렬 스캔, 복잡한 쿼리 플랜에 유리
    • 클러스터 인덱스 구조(Heap Table)라 덤프/복구 등에서 유연함
  2. 다양한 객체지향적 기능과 결합
    • MVCC + 테이블 상속, 복잡한 데이터 타입, Window Function 등 RDBMS 중 최강 수준의 SQL 지원
    • 이 구조 덕분에 “동시성 + 유연성” 모두 잡음
  3. 세밀한 트랜잭션 격리 & 동시성 제어
    • 각 트랜잭션이 볼 수 있는 Snapshot을 세밀하게 관리(Serializable Snapshot Isolation 등)
    • Statement-level/Transaction-level snapshot 구분
    • Long-running 쿼리, 대량 병렬 집계 등에서 매우 유리
  4. VACUUM 및 데이터 복구/아카이브 유연성
    • 오래된 튜플 정리도 DBA가 명시적으로 컨트롤 가능(autovacuum, manual vacuum, freeze 등)
    • WAL/Hot Standby/Logical Replication/Point-In-Time Recovery 등 고급 기능과 결합이 자연스러움
  5. SQL 표준과의 높은 호환성
    • 거의 모든 트랜잭션 격리 수준을 “정석대로” 지원
    • 서브쿼리/Window Function 등에서 MySQL보다 훨씬 강력한 기능
  6. 확장성/플러그인 아키텍처와 결합
    • MVCC 아키텍처 덕분에 TimescaleDB, PostGIS, Citus 등 강력한 확장 모듈들과 “무중단” 연동이 쉽다
    • 표준 SQL을 깨뜨리지 않고 동시성 최적화 모듈 추가 가능

3. 결론: “MVCC를 지원하는가”가 아니라, “어떻게 지원하고, 어떤 응용이 가능한가”가 차이

  • MySQL도 MVCC 지원 → 하지만 Undo Log 구조의 한계와 제약
    • 락 컨텐션 줄이려면 파라미터 튜닝, Purge Thread, Temp Table 등 신경쓸 게 많음
    • 일부 복잡한 트랜잭션/격리 수준에서 예외적 버그가 종종 있음
  • PostgreSQL은 ‘진짜 RDB의 교과서적 MVCC’ + 그 위의 다양한 응용/확장성
    • 고성능 OLTP/OLAP, 동시성, 고급 쿼리, 확장 플러그인, 고가용성, 오픈소스 생태계까지
    • “MVCC + 그 위의 PostgreSQL만의 생태계”가 진짜 강점

그러면 Vaccum은 뭔데 가비지 컬렉션 같은 거?

🧹 1. Vacuum의 정의 및 역할

  • Vacuum은 마치 진공청소기처럼, PostgreSQL의 Garbage Collector(GC) 역할을 수행합니다
  • 주요 임무는 다음 네 가지:
    1. Dead Tuple 정리 및 공간 회수 (Free Space Map으로 반환)
    2. Transaction ID Wraparound 방지 (Freeze)
    3. 통계 정보 갱신
    4. 인덱스 스캔 성능을 위한 visibility map 갱신

🧐 2. 왜 Vacuum이 필요한가? (Dead Tuple 문제)

  • PostgreSQL은 MVCC 아키텍처로 인해 UPDATE/DELETE 시, “기존 튜플은 그대로 두고 새 버전을 추가”하는데,
  • 시간이 지나면서 사용되지 않는 Dead Tuple이 쌓여 I/O 부하 증가와 성능 저하를 유발합니다
  • 실제 테스트: 테이블 1건 + 1,000만 건 삭제
    • Vacuum 전: 테이블 크기 약 1.9 GB, 쿼리 속도 매우 느림
    • 간단한 VACUUM: Dead Tuple 정리, 성능 정상화
    • VACUUM FULL: 디스크 공간까지 회수

🔄 3. AutoVacuum이 언제 실행되나?

자동 Vacuum은 다음 두 가지 조건이 충족될 때 실행됩니다

  1. *Dead Tuple 개수가 특정 임계치 초과 시**:

    threshold = autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × 전체 튜플 수

    기본값: 50 + 20% 초과 시 실행

  2. Transaction ID의 ‘age’가 임계치 도달 시:

    • autovacuum_freeze_max_age (기본 2억) 초과 시 실행
    • vacuum_freeze_min_age (5천만)와 vacuum_freeze_table_age 값 기준으로 실행 범위 조정

⚙️ 4. Vacuum 튜닝 주요 파라미터

  • Dead Tuple 정리 빈도 조정
    • autovacuum_vacuum_threshold, autovacuum_vacuum_scale_factor (기본 50, 0.2); 추천 비율은 0.1
  • Vacuum 속도/부하 제어
    • autovacuum_vacuum_cost_limit (기본 200), autovacuum_vacuum_cost_delay (기본 20ms)
    • 추천: 비용 한도 늘리고 지연 시간(short delay) 줄이기
  • AutoVacuum 주기 및 동시처리
    • autovacuum_naptime (기본 60초 → 필요 시 5초)
    • autovacuum_max_workers, autovacuum_work_mem, max_parallel_maintenance_workers 등으로 병렬 처리량 및 메모리 조절

⚠️ 5. Vacuum이 실패하는 경우

AutoVacuum이 실패하는 주된 원인은 다음과 같습니다:
1. 긴 실행 중인 트랜잭션(Long transaction)
- 오래된 xmin이 존재하면 Dead Tuple 정리가 지연됨.
2. Replica의 복제 슬롯 문제
- 사용되지 않는 replication slot이 있으면 vacuum이 멈춤.
3. hot_standby_feedback = ON
- 복제 서버가 오래된 트랜잭션 정보를 유지할 경우, 마스터에서 vacuum 정지됨.


요약

PostgreSQL의 MVCC 방식은 Dead Tuple 발생이 필연적이며, 이를 정리하는 Vacuum은 필수입니다.

AutoVacuum은 자동으로 수행되지만, 시스템 규모나 사용 패턴에 따라 튜닝 및 모니터링이 반드시 필요합니다.

적절한 튜닝을 통해 불필요한 디스크 사용과 쿼리 성능 저하, 그리고 시스템 위험 요소를 예방할 수 있습니다.

참고 블로그

0개의 댓글