기존 진행했던 프로젝트의 리팩토링을 하면서 개인 프로젝트를 진행하며 주가 데이터를 사용하게 돼서 이를 어디에 저장할까 고민을 했으며, PostgreSQL과 TimescaleDB를 사용해서 저장하기로 했는데, 이거를 어떻게 하면 내가 납득을 할까 고민하다가 이 흔적을 남기기 위해 블로그 들어왔음.
1. 아 어떤 DB 써야 되지
- 관계형 DB (RDBMS)
PostgreSQL
- 강력한 트랜잭션/정합성(ACID) 보장
- 대량 데이터, 복잡한 쿼리(윈도우 함수/파티셔닝/CTE 등)에 최적
- 오픈소스 자유도, 확장성, JSON 등 반정형 데이터 처리 지원
- 금융/퀀트/통계 시스템에서 표준적으로 사용
- TimescaleDB(시계열 확장 플러그인)와 완벽 호환
MySQL
- 운영·관리가 쉽고, 범용 서비스에 널리 사용
- 단순 CRUD, 중소규모 프로젝트에 강점
- 시계열·대용량 집계/배치에선 PostgreSQL 대비 다소 약점
- 시계열 특화 DB
- TimescaleDB (PostgreSQL 확장 모듈)
초고속 시계열 데이터 저장/조회/압축에 특화
(Tick/분봉 등 증권 데이터에 매우 적합)
- 하이퍼테이블, 자동 파티셔닝/압축, 시간 기반 집계 등 내장
- SQL(표준 PostgreSQL 쿼리) 그대로 사용 가능
- 복잡한 분석, 집계, 조건 검색, JOIN, 통계 연산 모두 지원
- PostgreSQL 생태계/도구/백업 모두 그대로 사용
- 독립형 시계열DB (InfluxDB 등) / 초대용량 시계열 수집/모니터링에는 강점
- 복잡 집계/조인/관계형 연산, 고급 리포트에는 한계
- 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 비교 요약
| 항목 | PostgreSQL | TimescaleDB |
|---|
| 엔진 | 범용 RDBMS | PostgreSQL 확장, 시계열 특화 |
| 데이터 타입 | 정형, 반정형, 시계열 모두 | 대용량 시계열(센서, 로그, 주식 등) |
| 파티셔닝/샤딩 | 테이블 파티셔닝, 샤딩 지원 | 내부 자동 파티셔닝/샤딩(청크 기반) |
| 쿼리 성능 | OLTP/OLAP 모두 우수 | 시계열 쿼리/집계에 특화(OLAP 성능↑) |
| 백업/복구 | 표준 도구 지원 | PostgreSQL 도구 그대로 사용 |
| 확장성 | 고가용성/복제/파티셔닝 지원 | 단일/분산(Distributed Hypertable) 지원 |
| 실무 적용 예시 | ERP, CRM, 서비스DB, BI | IoT, 실시간 모니터링, 금융 시세, 로그분석 |
응? 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 Thread | VACUUM/Autovacuum으로 관리 |
1. MVCC 구현 방식이 다름
- MySQL(InnoDB):
- 데이터 변경 전의 값을 Undo Log에 따로 저장해둠.
- “지워진 버전”을 Undo Log에서 꺼내와서, 이전 시점의 데이터처럼 보여줌.
- Undo Log가 커지면 Purge가 필요하고, 트랜잭션이 오래 가면 Undo 영역이 지저분해짐.
- PostgreSQL:
- 테이블 자체에 “행 버전”을 여러 개 남김(xmin, xmax)
- 각 트랜잭션은 자신이 볼 수 있는 버전만 “스캔”함.
- 오래된 튜플은 VACUUM/Autovacuum이 청소함.
2. PostgreSQL만의 MVCC 관련 장점/특징
- “Versioned Table” 아키텍처
- “Undo Log” 따로 없이, 테이블 자체에서 버전 관리
- 대규모 병렬 스캔, 복잡한 쿼리 플랜에 유리
- 클러스터 인덱스 구조(Heap Table)라 덤프/복구 등에서 유연함
- 다양한 객체지향적 기능과 결합
- MVCC + 테이블 상속, 복잡한 데이터 타입, Window Function 등 RDBMS 중 최강 수준의 SQL 지원
- 이 구조 덕분에 “동시성 + 유연성” 모두 잡음
- 세밀한 트랜잭션 격리 & 동시성 제어
- 각 트랜잭션이 볼 수 있는 Snapshot을 세밀하게 관리(Serializable Snapshot Isolation 등)
- Statement-level/Transaction-level snapshot 구분
- Long-running 쿼리, 대량 병렬 집계 등에서 매우 유리
- VACUUM 및 데이터 복구/아카이브 유연성
- 오래된 튜플 정리도 DBA가 명시적으로 컨트롤 가능(autovacuum, manual vacuum, freeze 등)
- WAL/Hot Standby/Logical Replication/Point-In-Time Recovery 등 고급 기능과 결합이 자연스러움
- SQL 표준과의 높은 호환성
- 거의 모든 트랜잭션 격리 수준을 “정석대로” 지원
- 서브쿼리/Window Function 등에서 MySQL보다 훨씬 강력한 기능
- 확장성/플러그인 아키텍처와 결합
- 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) 역할을 수행합니다
- 주요 임무는 다음 네 가지:
- Dead Tuple 정리 및 공간 회수 (Free Space Map으로 반환)
- Transaction ID Wraparound 방지 (Freeze)
- 통계 정보 갱신
- 인덱스 스캔 성능을 위한 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은 다음 두 가지 조건이 충족될 때 실행됩니다
-
*Dead Tuple 개수가 특정 임계치 초과 시**:
threshold = autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × 전체 튜플 수
기본값: 50 + 20% 초과 시 실행
-
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은 자동으로 수행되지만, 시스템 규모나 사용 패턴에 따라 튜닝 및 모니터링이 반드시 필요합니다.
적절한 튜닝을 통해 불필요한 디스크 사용과 쿼리 성능 저하, 그리고 시스템 위험 요소를 예방할 수 있습니다.
참고 블로그