Skip to content

필사 모드: PostgreSQL Partitioning 실전 가이드

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

1. 파티셔닝이란?

파티셔닝은 하나의 큰 테이블을 여러 개의 물리적 파티션으로 분할하는 기법입니다. 테이블의 데이터가 수억 건 이상일 때 쿼리 성능을 크게 향상시킬 수 있습니다.

파티셔닝의 장점

- **쿼리 성능 향상**: Partition pruning으로 필요한 파티션만 스캔

- **대량 데이터 삭제**: DROP PARTITION으로 즉시 삭제 (DELETE보다 수백 배 빠름)

- **병렬 처리**: 파티션별 병렬 스캔 가능

- **관리 용이**: 파티션별 인덱스, VACUUM, 백업 가능

2. Range 파티셔닝

가장 많이 사용되는 방식으로, 날짜/숫자 범위로 분할합니다.

-- 부모 테이블 생성

CREATE TABLE orders (

id BIGSERIAL,

customer_id INTEGER NOT NULL,

order_date DATE NOT NULL,

amount DECIMAL(10, 2),

status VARCHAR(20),

created_at TIMESTAMP DEFAULT NOW()

) PARTITION BY RANGE (order_date);

-- 월별 파티션 생성

CREATE TABLE orders_2026_01 PARTITION OF orders

FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

CREATE TABLE orders_2026_02 PARTITION OF orders

FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');

CREATE TABLE orders_2026_03 PARTITION OF orders

FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

-- 파티션별 인덱스 (자동 상속됨)

CREATE INDEX idx_orders_customer ON orders (customer_id);

CREATE INDEX idx_orders_status ON orders (status, order_date);

-- 기본 파티션 (범위에 맞지 않는 데이터 수용)

CREATE TABLE orders_default PARTITION OF orders DEFAULT;

3. List 파티셔닝

특정 값 목록으로 분할합니다. 지역, 카테고리 등에 적합합니다.

CREATE TABLE events (

id BIGSERIAL,

event_type VARCHAR(50) NOT NULL,

payload JSONB,

created_at TIMESTAMP DEFAULT NOW()

) PARTITION BY LIST (event_type);

CREATE TABLE events_user PARTITION OF events

FOR VALUES IN ('user_signup', 'user_login', 'user_logout');

CREATE TABLE events_order PARTITION OF events

FOR VALUES IN ('order_created', 'order_paid', 'order_cancelled');

CREATE TABLE events_system PARTITION OF events

FOR VALUES IN ('health_check', 'deploy', 'config_change');

CREATE TABLE events_default PARTITION OF events DEFAULT;

4. Hash 파티셔닝

해시 함수로 균등 분배합니다. 특정 키의 분포가 고른 경우에 적합합니다.

CREATE TABLE user_sessions (

id BIGSERIAL,

user_id INTEGER NOT NULL,

session_id UUID NOT NULL,

data JSONB,

expires_at TIMESTAMP

) PARTITION BY HASH (user_id);

-- 4개 파티션으로 균등 분배

CREATE TABLE user_sessions_0 PARTITION OF user_sessions

FOR VALUES WITH (MODULUS 4, REMAINDER 0);

CREATE TABLE user_sessions_1 PARTITION OF user_sessions

FOR VALUES WITH (MODULUS 4, REMAINDER 1);

CREATE TABLE user_sessions_2 PARTITION OF user_sessions

FOR VALUES WITH (MODULUS 4, REMAINDER 2);

CREATE TABLE user_sessions_3 PARTITION OF user_sessions

FOR VALUES WITH (MODULUS 4, REMAINDER 3);

5. Partition Pruning 확인

-- Partition pruning 활성화 확인

SHOW enable_partition_pruning; -- on

-- EXPLAIN으로 pruning 확인

EXPLAIN (ANALYZE, COSTS, BUFFERS)

SELECT * FROM orders

WHERE order_date >= '2026-03-01'

AND order_date < '2026-04-01';

-- 결과 예시:

-- Append (actual rows=50000)

-- -> Seq Scan on orders_2026_03 (actual rows=50000)

-- Filter: (order_date >= '2026-03-01' AND order_date < '2026-04-01')

-- ✅ orders_2026_01, orders_2026_02는 스캔하지 않음!

6. 파티션 자동 생성 (pg_partman)

-- pg_partman 설치

CREATE EXTENSION pg_partman;

-- 자동 파티션 관리 설정

SELECT partman.create_parent(

p_parent_table => 'public.orders',

p_control => 'order_date',

p_type => 'native',

p_interval => 'monthly',

p_premake => 3 -- 3개월 미리 생성

);

-- 유지보수 함수 (cron으로 매일 실행)

SELECT partman.run_maintenance();

cron 설정

pg_partman 유지보수 (매일 새벽 2시)

0 2 * * * psql -U postgres -d mydb \

-c "SELECT partman.run_maintenance();" \

>> /var/log/pg_partman.log 2>&1

수동 자동화 스크립트

-- pg_partman 없이 직접 자동화

CREATE OR REPLACE FUNCTION create_monthly_partition(

p_table TEXT,

p_year INTEGER,

p_month INTEGER

) RETURNS VOID AS $$

DECLARE

partition_name TEXT;

start_date DATE;

end_date DATE;

BEGIN

partition_name := format('%s_%s_%s',

p_table,

p_year,

LPAD(p_month::TEXT, 2, '0')

);

start_date := make_date(p_year, p_month, 1);

end_date := start_date + INTERVAL '1 month';

EXECUTE format(

'CREATE TABLE IF NOT EXISTS %I PARTITION OF %I

FOR VALUES FROM (%L) TO (%L)',

partition_name, p_table, start_date, end_date

);

RAISE NOTICE 'Created partition: %', partition_name;

END;

$$ LANGUAGE plpgsql;

-- 사용 예

SELECT create_monthly_partition('orders', 2026, 4);

SELECT create_monthly_partition('orders', 2026, 5);

7. 오래된 파티션 삭제

-- 파티션 분리 (데이터 보존, 쿼리에서 제외)

ALTER TABLE orders DETACH PARTITION orders_2025_01;

-- 분리된 파티션을 별도 테이블로 유지하거나 삭제

DROP TABLE orders_2025_01; -- 즉시 삭제 (수억 건도 순간)

-- 대비: DELETE는 매우 느림

-- DELETE FROM orders WHERE order_date < '2025-02-01'; ← 이러지 마세요!

8. 운영 팁

파티션 상태 모니터링

-- 파티션별 행 수 확인

SELECT

schemaname || '.' || relname AS partition,

n_live_tup AS row_count,

pg_size_pretty(pg_relation_size(relid)) AS size

FROM pg_stat_user_tables

WHERE relname LIKE 'orders_%'

ORDER BY relname;

-- 파티션 목록 조회

SELECT

parent.relname AS parent,

child.relname AS partition,

pg_get_expr(child.relpartbound, child.oid) AS bounds

FROM pg_inherits

JOIN pg_class parent ON pg_inherits.inhparent = parent.oid

JOIN pg_class child ON pg_inherits.inhrelid = child.oid

WHERE parent.relname = 'orders'

ORDER BY child.relname;

주의사항

1. PRIMARY KEY에 파티션 키 포함 필수

CREATE TABLE orders (...) PARTITION BY RANGE (order_date);

→ PK는 (id, order_date) 형태여야 함

2. UNIQUE 제약조건도 파티션 키 포함 필요

3. 파티션 수가 너무 많으면 플래닝 오버헤드 증가

→ 1000개 이하 유지 권장

4. 크로스 파티션 UPDATE는 PostgreSQL 11+에서만 지원

9. 퀴즈

DEFAULT 파티션은 어떤 파티션의 범위에도 속하지 않는 데이터를 수용합니다. 예를 들어 2026년 파티션만 있는데 2027년 데이터가 INSERT되면 DEFAULT 파티션에 저장됩니다. DEFAULT 파티션이 없으면 범위 밖 데이터 INSERT 시 에러가 발생합니다.

DELETE는 각 행을 하나씩 삭제하면서 WAL 로그를 기록하고, dead tuple이 남아 VACUUM이 필요합니다. 반면 DROP TABLE (파티션 삭제)은 테이블의 데이터 파일 자체를 즉시 삭제하므로, 수억 건의 데이터도 순식간에 제거됩니다. 행 수에 관계없이 거의 일정한 시간이 소요됩니다.

PostgreSQL은 파티션 테이블의 유니크 제약조건을 각 파티션의 로컬 인덱스로 구현합니다. 파티션 키가 PK에 포함되지 않으면, 다른 파티션에 같은 id가 존재할 수 있어 전체 테이블 수준의 유니크성을 보장할 수 없습니다. 따라서 `(id, order_date)` 처럼 파티션 키를 PK에 포함해야 합니다.

현재 단락 (1/147)

파티셔닝은 하나의 큰 테이블을 여러 개의 물리적 파티션으로 분할하는 기법입니다. 테이블의 데이터가 수억 건 이상일 때 쿼리 성능을 크게 향상시킬 수 있습니다.

작성 글자: 0원문 글자: 4,734작성 단락: 0/147