🗄️ 데이터베이스

Partition

파티션

대용량 테이블을 논리적으로 분할하여 쿼리 성능과 관리 효율성을 높이는 기법. Partition Pruning으로 필요한 파티션만 스캔하여 성능을 최적화합니다.

📖 상세 설명

파티션(Partition)은 대용량 테이블을 물리적 또는 논리적으로 더 작은 조각으로 분할하는 기법입니다. 하나의 테이블을 여러 파티션으로 나누어 저장하지만, 애플리케이션에서는 하나의 테이블처럼 사용합니다. 쿼리 시 WHERE 조건에 따라 필요한 파티션만 스캔하는 Partition Pruning을 통해 성능이 크게 향상됩니다.

파티션 유형

Range Partition (범위 파티션)

연속된 값의 범위로 분할. 날짜, 시간, 숫자 기반 데이터에 적합.

  • 적합: 로그, 주문 이력, 시계열 데이터
  • 예: 월별, 분기별, 연도별 파티션

List Partition (목록 파티션)

특정 값 목록으로 분할. 이산적인 값 기반 분류에 적합.

  • 적합: 국가, 지역, 상태값 등
  • 예: 국가별 ('KR', 'US', 'JP') 파티션

Hash Partition (해시 파티션)

해시 함수로 균등 분배. 특정 패턴이 없는 데이터에 적합.

  • 적합: 균등한 부하 분산 필요 시
  • 예: user_id의 해시 값으로 4개 파티션 분배

파티션 vs 샤딩

특성 Partition Sharding
분할 위치 단일 DB 인스턴스 내 여러 DB 인스턴스
관리 복잡도 DB가 자동 관리 애플리케이션 레벨 관리
확장성 수직 확장 한계 수평 확장 가능
트랜잭션 전체 테이블 ACID 분산 트랜잭션 필요
사용 케이스 단일 DB로 충분한 규모 매우 대규모 데이터

💻 코드 예제

PostgreSQL - Range Partition (날짜 기반)
-- 부모 테이블 생성 (파티션 키 지정) CREATE TABLE orders ( id SERIAL, user_id INTEGER NOT NULL, amount DECIMAL(10, 2) NOT NULL, status VARCHAR(20) NOT NULL, created_at TIMESTAMP NOT NULL, PRIMARY KEY (id, created_at) -- 파티션 키 포함 필수 ) PARTITION BY RANGE (created_at); -- 월별 파티션 생성 CREATE TABLE orders_2024_01 PARTITION OF orders FOR VALUES FROM ('2024-01-01') TO ('2024-02-01'); CREATE TABLE orders_2024_02 PARTITION OF orders FOR VALUES FROM ('2024-02-01') TO ('2024-03-01'); CREATE TABLE orders_2024_03 PARTITION OF orders FOR VALUES FROM ('2024-03-01') TO ('2024-04-01'); -- 기본 파티션 (범위 밖 데이터) CREATE TABLE orders_default PARTITION OF orders DEFAULT; -- 인덱스 생성 (파티션별 자동 적용) CREATE INDEX idx_orders_user_id ON orders (user_id); CREATE INDEX idx_orders_status ON orders (status); -- Partition Pruning 동작 확인 EXPLAIN ANALYZE SELECT * FROM orders WHERE created_at >= '2024-02-01' AND created_at < '2024-03-01'; -- 결과: orders_2024_02만 스캔
PostgreSQL - List Partition (국가/지역 기반)
-- 국가별 List Partition CREATE TABLE customers ( id SERIAL, name VARCHAR(100) NOT NULL, country_code CHAR(2) NOT NULL, email VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id, country_code) ) PARTITION BY LIST (country_code); -- 국가별 파티션 생성 CREATE TABLE customers_kr PARTITION OF customers FOR VALUES IN ('KR'); CREATE TABLE customers_asia PARTITION OF customers FOR VALUES IN ('JP', 'CN', 'TW', 'SG'); CREATE TABLE customers_us PARTITION OF customers FOR VALUES IN ('US', 'CA'); CREATE TABLE customers_eu PARTITION OF customers FOR VALUES IN ('DE', 'FR', 'GB', 'IT', 'ES'); CREATE TABLE customers_other PARTITION OF customers DEFAULT; -- 한국 고객만 조회 (자동 Pruning) SELECT * FROM customers WHERE country_code = 'KR'; -- customers_kr 파티션만 스캔
PostgreSQL - Hash Partition (균등 분배)
-- 해시 기반 균등 분배 CREATE TABLE logs ( id BIGSERIAL, user_id INTEGER NOT NULL, action VARCHAR(50) NOT NULL, payload JSONB, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id, user_id) ) PARTITION BY HASH (user_id); -- 4개 파티션으로 균등 분배 CREATE TABLE logs_p0 PARTITION OF logs FOR VALUES WITH (MODULUS 4, REMAINDER 0); CREATE TABLE logs_p1 PARTITION OF logs FOR VALUES WITH (MODULUS 4, REMAINDER 1); CREATE TABLE logs_p2 PARTITION OF logs FOR VALUES WITH (MODULUS 4, REMAINDER 2); CREATE TABLE logs_p3 PARTITION OF logs FOR VALUES WITH (MODULUS 4, REMAINDER 3);
파티션 관리 - 오래된 데이터 아카이브
-- 1. 새 파티션 미리 생성 (자동화 권장) CREATE TABLE orders_2024_04 PARTITION OF orders FOR VALUES FROM ('2024-04-01') TO ('2024-05-01'); -- 2. 오래된 파티션 분리 (DETACH) ALTER TABLE orders DETACH PARTITION orders_2024_01; -- 3. 분리된 테이블 아카이브 또는 삭제 -- 옵션 A: 아카이브 테이블로 이동 ALTER TABLE orders_2024_01 RENAME TO orders_archive_2024_01; -- 옵션 B: 데이터 삭제 (전체 파티션 즉시 삭제) DROP TABLE orders_2024_01; -- Full Table Scan 없이 즉시 삭제 (DELETE보다 훨씬 빠름) -- 파티션 목록 확인 SELECT parent.relname AS parent_table, child.relname AS partition_name, pg_get_expr(child.relpartbound, child.oid) AS partition_expression 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';
MySQL - Range Partition
-- MySQL 파티션 생성 CREATE TABLE orders ( id INT AUTO_INCREMENT, user_id INT NOT NULL, amount DECIMAL(10, 2) NOT NULL, created_at DATE NOT NULL, PRIMARY KEY (id, created_at) ) PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) ( PARTITION p202401 VALUES LESS THAN (202402), PARTITION p202402 VALUES LESS THAN (202403), PARTITION p202403 VALUES LESS THAN (202404), PARTITION p_max VALUES LESS THAN MAXVALUE ); -- 파티션 추가 ALTER TABLE orders ADD PARTITION ( PARTITION p202404 VALUES LESS THAN (202405) ); -- 파티션 삭제 ALTER TABLE orders DROP PARTITION p202401; -- 파티션 정보 확인 SELECT TABLE_NAME, PARTITION_NAME, PARTITION_EXPRESSION, PARTITION_DESCRIPTION, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'orders';

💬 현업 대화 예시

백엔드 개발자
"주문 테이블이 10억 건이 넘어서 쿼리가 너무 느려요. 최근 데이터만 조회하는데도 30초씩 걸려요."
DBA
"월별 Range Partition 적용해. WHERE created_at 조건이 있으면 해당 월 파티션만 스캔하니까 수 초로 줄어들 거야. 오래된 파티션은 DROP으로 즉시 삭제 가능해서 관리도 편해."
주니어 개발자
"파티션을 나눴는데 쿼리가 여전히 느려요. EXPLAIN 보니까 모든 파티션을 스캔하고 있어요."
시니어 개발자
"WHERE 절에 파티션 키가 포함되어야 Pruning이 동작해. created_at으로 파티션 했으면 쿼리에 created_at 조건 넣어야지. 없으면 전체 스캔이야."
아키텍트
"멀티테넌트 서비스에서 테넌트별로 데이터 격리가 필요한데요."
팀리드
"tenant_id로 List Partition 하면 돼. 테넌트별로 파티션 분리되니까 한 테넌트 쿼리가 다른 테넌트 데이터는 아예 안 건드려. 보안과 성능 둘 다 해결."

⚠️ 주의사항

⚠️ 파티션 키 포함 필수

Primary Key와 Unique 제약조건에 파티션 키를 반드시 포함해야 합니다. PRIMARY KEY (id, created_at)처럼 복합 키로 정의하세요.

⚠️ Partition Pruning 조건

WHERE 절에 파티션 키 조건이 없으면 모든 파티션을 스캔합니다. EXPLAIN으로 실제 스캔되는 파티션 수를 확인하세요. 함수로 감싼 조건(예: YEAR(created_at) = 2024)은 Pruning이 동작하지 않을 수 있습니다.

⚠️ 파티션 개수

너무 많은 파티션은 오히려 성능을 저하시킵니다. PostgreSQL은 수천 개 파티션도 처리하지만, 실제 접근 패턴에 맞게 적절한 단위(월별, 분기별)로 설계하세요.

🔗 관련 용어

📚 더 배우기