PostgreSQL 파티셔닝 — 1억 row 테이블 운영 가이드
1억 행 규모 테이블에서 파티셔닝은 왜 필요한가요?
파티셔닝(Partitioning)은 대규모 테이블을 물리적으로 분할하여 쿼리 성능을 개선하는 기법입니다. 1억 행 이상 테이블에서 파티셔닝 미적용 시 풀 테이블 스캔으로 인해 응답 시간이 510초 이상 지연되나, 파티셔닝 적용 시 관련 파티션만 스캔하여 100500ms 수준으로 단축됩니다. 또한 인덱스 크기 감소로 메모리 효율이 30~50% 개선되고, 개별 파티션 관리로 백업/복구 시간이 단축됩니다.
PostgreSQL 파티셔닝의 작동 메커니즘은 어떻게 되나요?
PostgreSQL은 선언적 파티셔닝(Declarative Partitioning)을 지원하며, 데이터 삽입/조회 시 쿼리 플래너가 자동으로 적절한 파티션을 선택합니다. 부모 테이블은 메타데이터만 보유하고, 실제 데이터는 자식 파티션(Child Partition)에 저장됩니다. 쿼리 실행 시 "Partition Pruning" 기능이 활성화되어 불필요한 파티션을 미리 제외하고, 해당 파티션의 인덱스만 참조합니다.
PostgreSQL 10.0(2017년 10월 출시) 이후 제공되는 파티셔닝 방식은 다음 세 가지입니다:
| 파티셔닝 유형 | 분할 기준 | 적용 사례 | 성능 특성 |
|---|---|---|---|
| Range | 날짜/숫자 범위 | 월별/년도별 데이터 | 시계열 데이터 최적화 |
| List | 이산 값 | 지역/카테고리별 데이터 | 명확한 카테고리 분류 시 |
| Hash | 해시 함수 | 균등 분산 필요 시 | 파티션 크기 균등 분배 |
Range 파티셔닝 구현 예시:
PostgreSQL 14.0 기준, 다음 SQL로 월별 파티션을 생성합니다:
CREATE TABLE events (
event_id BIGSERIAL,
user_id INTEGER,
event_date DATE NOT NULL,
event_type VARCHAR(50)
) PARTITION BY RANGE (event_date);
CREATE TABLE events_2024_01 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE events_2024_02 PARTITION OF events
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
この構造下で, 2024년 1월 데이터 조회 쿼리는 events_2024_01 파티션만 스캔합니다. PostgreSQL 쿼리 플래너의 파티션 프루닝 기능이 WHERE 절의 event_date 조건을 분석하여 해당 파티션을 자동 선택하므로, 1억 행 테이블에서도 해당 월의 데이터(일반적으로 100만~500만 행)만 처리합니다.
성능 개선 효과는 정량적으로 어느 정도인가요?
Databricks 및 AWS 사례 연구에 따르면, 1억 행 테이블에서 파티셔닝 적용 결과는 다음과 같습니다:
쿼리 응답 시간 비교 (PostgreSQL 14.0, Intel Xeon E5-2690, 128GB RAM):
| 작업 유형 | 파티셔닝 미적용 | 파티셔닝 적용 (Range) | 개선율 |
|---|---|---|---|
| 단일 월 데이터 조회 (1~5M 행) | 8.5초 | 0.15초 | 약 56배 |
| 특정 기간 범위 조회 (3개월) | 18.2초 | 0.42초 | 약 43배 |
| 전체 테이블 스캔 | 45.3초 | 42.8초 | 약 1.06배 (거의 동일) |
| 인덱스 크기 | 8.3GB | 2.1GB | 약 75% 감소 |
이 수치는 비용 기반 쿼리 최적화(Cost-based Query Optimization) 수행 시, 파티션 프루닝에 의해 스캔 대상 블록이 75~95% 감소하기 때문입니다.
메모리 효율성: 파티션별 독립 인덱스 구성으로 각 인덱스 크기가 감소하여, PostgreSQL의 공유 버퍼(Shared Buffer) 히트율이 70%에서 87%로 증가합니다. 이는 디스크 I/O 횟수 30% 감소로 이어집니다.
유지보수 측면: 월별 파티션 구조에서 이전 달 파티션만 백업하거나 정리할 수 있습니다. 1억 행 전체 백업(약 50GB)은 12시간 소요되나, 월별 파티션(약 4GB) 백업은 1시간 내에 완료됩니다.
파티셔닝 전략 설계 시 고려사항은 무엇인가요?
파티션 크기 결정: 개별 파티션은 1GB10GB 범위가 권장됩니다. 1억 행이 일반적으로 48GB 차지하므로, 월별(Range)로 분할하면 파티션당 3~5백만 행 수준이 됩니다. 파티션이 너무 작으면(100MB 이하) 메타데이터 오버헤드가 증가하고, 너무 크면(50GB 초과) 파티션 프루닝 효과가 감소합니다.
파티션 키 선택: 조회 쿼리에서 가장 자주 사용되는 필터 컬럼을 파티션 키로 지정합니다. 시계열 데이터(날짜)는 Range, 지역/카테고리는 List, 균등 분산이 필요한 경우 Hash를 선택합니다. 파티션 키는 변경 불가능한 값(NOT NULL)이어야 합니다.
오토 파티션 생성: PostgreSQL 11.0 이상에서는 pg_partman 확장(Extension)으로 자동 파티션 생성 스케줄을 설정할 수 있습니다. 다음 달 파티션을 미리 생성하고 만료된 파티션을 자동 삭제하여, 테이블 크기를 일정 범위 내에서 유지합니다.
구축 사례에서 어떤 구현 방식이 사용되나요?
의료 데이터 분석 플랫폼 (국내 대형병원 네트워크): 환자 진료 기록 테이블(약 1.5억 행)을 월별 Range 파티셔닝으로 구성했습니다. 매월 400만~600만 행의 신규 데이터가 추가되며, 각 파티션은 약 5GB 크기입니다. 진료 기록 조회(환자ID + 진료일 범위 필터)에서 응답 시간이 12초에서 0.3초로 단축되었고, 월별 데이터 아카이빙 시 해당 파티션만 압축 저장소(Cold Storage)로 이전하여 운영 비용을 40% 감소시켰습니다.
전자상거래 로그 플랫폼: 사용자 접속/구매 로그 테이블(약 2.8억 행)을 시간대별(Hourly) Range 파티셔닝 구조로 운영합니다. 각 시간 파티션은 약 200만 행(900MB)이며, 일자별 집계 쿼리 응답 시간이 45초에서 1.2초로 개선되었습니다. 90일 이상 경과 로그는 자동으로 압축 파티션으로 전환하여 스토리지 용량을 50% 절감했습니다.
금융거래 감시 시스템: 거래 내역 테이블(약 5억 행)을 거래 유형(List 파티셔닝: 입금/출금/이체)과 거래일(Range 파티셔닝)의 복합 파티셔닝으로 구성했습니다. 이상 거래 탐지 쿼리(특정 유형 + 기간)에서 스캔 대상이 전체의 5% 미만으로 제한되어, 실시간 모니터링 정확도를 유지하면서 응답 시간을 3초 이내에서 관리합니다.
파티셔닝 운영 중 주의사항은 어떤가요?
제약사항: 파티션된 테이블의 기본키(Primary Key) 또는 고유제약(Unique Constraint)은 파티션 키를 포함해야 합니다. 예를 들어 event_date를 파티션 키로 사용하면, 기본키는 (event_id, event_date) 형태여야 하며, event_id 단독으로는 고유성을 보장할 수 없습니다. 이는 애플리케이션 로직(중복 체크)에서 파티션 키를 함께 고려하도록 요구합니다.
UPDATE/DELETE 성능: 파티션 키 값을 변경하는 UPDATE는 행을 다른 파티션으로 이동시켜야 하므로 비용이 큽니다. PostgreSQL 11.0 이상의 "Partition-wise JOIN"과 "Partition-wise aggregate" 기능을 활성화하면 완화되나, 설계 단계에서 파티션 키 불변성을 보장하는 것이 최적입니다.
쿼리 패턴 검증: 파티션 프루닝이 제대로 작동하려면 WHERE 절에 파티션 키 조건이 명시적으로 포함되어야 합니다. SELECT * FROM events WHERE DATE_PART('month', event_date) = 1; 형태의 함수 기반 필터는 프루닝을 방해하므로, SELECT * FROM events WHERE event_date >= '2024-01-01' AND event_date < '2024-02-01'; 형태로 작성해야 합니다.
인덱스 전략: 파티션된 테이블의 글로벌 인덱스(Global Index)는 PostgreSQL에서 직접 지원되지 않습니다. 각 파티션에 독립적인 인덱스를 생성하며, 자동 인덱스 생성 설정(constraint_exclusion = partition)을 통해 쿼리 플래너가 파티션별 인덱스를 활용하도록 해야 합니다.
정리하면 파티셔닝의 핵심은 무엇인가요?
PostgreSQL 파티셔닝은 1억 행 이상 대규모 테이블에서 쿼리 응답 시간을 50100배 단축하고, 인덱스 메모리를 3075% 절감하는 기법입니다. Range/List/Hash 세 가지 방식 중 시계열 데이터는 Range, 카테고리 분류는 List를 선택하며, 파티션 키는 조회 필터에 자주 사용되는 컬럼으로 지정합니다. 파티션당 크기는 110GB, 행 수는 100만1,000만 행 범위가 권장됩니다. 운영 단계에서는 파티션 프루닝 조건(명시적 범위 필터)을 보장하고, 오토 파티션 생성 스케줄로 신규 파티션을 사전 생성하여 안정성을 확보합니다.
자주 묻는 질문
PostgreSQL 파티셔닝은 어느 버전부터 지원되나요?
PostgreSQL 10.0(2017년 10월 출시)부터 선언적 파티셔닝(Declarative Partitioning)을 공식 지원합니다. PostgreSQL 11.0에서는 Partition-wise JOIN 기능, PostgreSQL 13.0에서는 파티션별 제약 조건 배제(Constraint Exclusion) 최적화가 추가되었습니다. PostgreSQL 14.0 이상에서 파티션 프루닝 성능이 가장 안정적이므로, 신규 구축 시 14.0 이상 버전을 권장합니다.
기존 파티셔닝되지 않은 1억 행 테이블을 파티션으로 변환할 수 있나요?
직접 변환은 불가능하며, 다음 절차를 따릅니다: (1) 동일한 구조로 파티션 테이블을 신규 생성, (2) INSERT INTO new_table SELECT * FROM old_table;로 데이터 이관(약 1~2시간 소요), (3) 인덱스 재생성, (4) 구 테이블 삭제. 이관 중 원본 테이블의 읽기는 가능하나 쓰기는 차단되므로, 유지보수 시간대에 수행해야 합니다. PostgreSQL 공식 문서에서 상세 절차를 제공합니다.
파티션 프루닝이 작동하지 않을 때 확인할 점은 무엇인가요?
EXPLAIN (ANALYZE, BUFFERS) SELECT ... 명령으로 실행 계획을 확인합니다. 실행 계획에서 모든 파티션이 표시되면 프루닝이 실패한 상태입니다. 확인 사항: (1) WHERE 절에 파티션 키 조건이 명시되어 있는지, (2) 함수나 타입 캐스팅으로 인한 암묵적 변환이 없는지, (3) constraint_exclusion 파라미터가 partition으로 설정되어 있는지. 특히 WHERE DATE_PART('month', created_date) = 3 형태는 프루닝을 방해하므로 WHERE created_date >= '2024-03-01' AND created_date < '2024-04-01'로 변경해야 합니다.
파티션 테이블에 대한 백업/복구 전략은 어떻게 구성하나요?
논리적 백업 도구(pg_dump -Fc)로 파티션 테이블 전체를 백업하거나, 파티션별 개별 백업도 가능합니다. 물리적 백업(WAL 아카이빙)은 파티션 구조와 관계없이 전체 데이터베이스 수준에서 수행됩니다. 장기 보관용으로는 만료된 파티션(6개월 이상 경과)을 압축 형식으로 별도 저장소에 이전하는 전략이 일반적입니다. 복구 시 해당 파티션만 재첨부(ATTACH)하면 되므로, 전체 테이블 복구 시간이 단축됩니다.
파티션 개수가 너무 많으면(예: 1,000개 이상) 성능 저하가 발생하나요?
파티션 개수 자체보다는 각 쿼리가 스캔해야 하는 파티션 수가 중요합니다. 파티션 프루닝이 제대로 작동하면 1,000개 파티션 중 12개만 스캔하므로 성능 차이는 미미합니다. 다만 메타데이터 오버헤드로 인해 시스템 카탈로그 쿼리 성능이 510% 감소할 수 있습니다. 또한 파티션 생성/삭제 작업이 빈번하면 카탈로그 관리 비용이 증가하므로, 자동 파티션 관리 도구(pg_partman)로 스케줄링하는 것이 권장됩니다.