파티셔닝

큘피·2026년 5월 2일

데이터베이스

목록 보기
6/7

파티셔닝은 논리적으론 하나의 큰 테이블이지만, 물리적으론 여러 개의 작은 조각으로 나눠 저장하는 기법입니다.

크게 두 가지로 나뉩니다.

  • 수평 파티셔닝
    • 행 기준으로 나눔
  • 수직 파티셔닝
    • 열 기준으로 나눔

왜 파티셔닝이 필요할까?

  • 인덱스 크기 최적화: 테이블이 너무 커지면 인덱스조차 무거워져 검색 속도가 떨어집니다. 파티셔닝을 하면 각 조각마다 인덱스가 따로 생성되므로 매우 가벼워집니다.

  • 파티션 프루닝 (Partition Pruning): 데이터베이스가 쿼리를 분석해 "아, 700,001번 고객은 4번 파티션에만 있겠네!"라고 판단하고 나머지 파티션은 아예 쳐다보지도 않는 기능입니다.

  • 데이터 관리: 1년이 지난 로그 데이터를 지워야 한다면? 거대 테이블에서 DELETE를 실행하는 것보다, 해당 월의 파티션 하나를 통째로 'Drop'하는 것이 수백 배 빠르고 깔끔합니다.


파티셔닝 종류

Range Partitioning

가장 고전적이면서도 강력한 방법입니다. 주로 시간 흐름(Time-series)에 따른 데이터를 다룰 때 필수적입니다.

  • 관리의 편의성: DELETE 문으로 수백만 건을 지우는 대신, 그냥 오래된 파티션을 DROP 하거나 DETACH 하면 끝납니다. 시스템 부하가 거의 없죠.

List Partitioning

데이터가 명확한 '범주(Category)'를 가질 때 사용합니다.

Hash Partitioning

데이터를 '골고루' 뿌려주는 것이 목표일 때 사용합니다.

  • 특정 범위나 목록에 데이터가 몰리는 '데이터 쏠림(Skew)' 현상을 방지하고 싶을 때 사용합니다.

주의할 점

  • 조인(Join)의 비용: 여러 파티션에 걸쳐 있는 데이터를 조인할 때는 성능이 오히려 떨어질 수 있습니다.
  • 설계 복잡성: 파티션 키를 잘못 설정하면 특정 파티션에만 데이터가 몰리는 '데이터 쏠림(Skew)' 현상이 발생해 성능 이점이 사라집니다.

파티셔닝 vs 샤딩

구분파티셔닝 (Partitioning)샤딩 (Sharding)
물리적 위치동일한 데이터베이스 서버 내여러 대의 독립된 서버(호스트)
복잡도상대적으로 낮음 (DB가 관리)높음 (애플리케이션에서 분산 로직 필요)
목적단일 서버 내의 성능 및 관리 최적화하드웨어 한계를 넘는 무한 확장성

실습

create table grades_org (id serial not null, g int not null);

insert into grades_org(g) select floor(random() * 100) from generate_series (0, 1000000);

create index grades_org_index on grades_org(g);

explain analyze select count(*) from grades_org where g between 30 and 35;

파티셔닝 만들기

create table grades_parts (id serial not null, g int not null) partition by range(g);

create table g0035 (like grades_parts including indexes);

\d+ g0035

create table g3560 (like grades_parts including indexes);

create table g6080 (like grades_parts including indexes);

create table g80100 (like grades_parts including indexes);

alter table grades_parts attach partition g0035 for values from (0) to (35);
alter table grades_parts attach partition g3560 for values from (35) to (60);
alter table grades_parts attach partition g6080 for values from (60) to (80);
alter table grades_parts attach partition g80100 for values from (80) to (100);
insert into grades_parts select * from grades_org;


select max(g) from g0035; # result max: 34
select max(g) from g3560; # result max: 59


# posgtres 12 이후 부모 테이블에 인덱스를 한 번만 만들면 됨
create index grades_parts_idx on grades_parts(g);

\d g3560 등으로 확인하면 인덱스 생성되어있음

데이터가 RAM에 다 들어가는 규모라면 파티셔닝의 효과는 미미합니다.

만약 데이터가 5억 건이 되어 인덱스가 50GB가 되고, 서버 RAM은 32GB뿐이라면? 이때부터는 메모리에 다 들어가는 5GB짜리 파티션 인덱스와 계속 디스크를 읽어야 하는 50GB짜리 통 인덱스의 속도 차이는 수십, 수백 배로 벌어집니다.

인덱스 크기 확인

select pg_relation_size(oid), relname from pg_class order by pg_relation_size(oid) desc; 
  • 탐색 효율: 인덱스 크기가 작다는 것은, B-Tree의 높이(Height)가 낮아질 가능성이 크고 탐색 시 읽어야 할 페이지 수가 적다는 뜻입니다.
  • Maintenance 비용: 인덱스가 작으면 VACUUM이나 REINDEX 같은 관리 작업도 훨씬 빠르고 가볍게 끝납니다.

파티션 프루닝(Pruning)

show ENABLE_PARTITION_PRUNING;

set enable_partition_pruning = on;
  • Pruning OFF: DB는 모든 파티션을 뒤집니다. 1,000만 건을 한 번에 뒤지는 것보다, 250만 건씩 4번 나누어 뒤지고 결과를 합치는 게 오히려 CPU 오버헤드 때문에 더 느릴 수도 있습니다. 파티셔닝을 했는데 성능이 안 나온다면 가장 먼저 확인해야 할 스위치입니다.
  • Pruning ON: WHERE g = 30 조건문을 보고 "아, g0035 말고는 답이 없네"라고 판단하여 나머지 3개 파티션을 실행 계획에서 아예 삭제합니다.

벌크 로딩

서비스 운영 중에 수천만 건을 INSERT하면 락(Lock)이 걸려 서비스가 터질 수 있습니다. 대신 별도 테이블에 데이터를 다 담은 뒤, 파티션으로 슥 끼워넣는(Attach) 방식을 쓰면 안전합니다.


파티션 단점

행 이동(Row Migration):

  • 작동 원리: 단순한 데이터 수정을 넘어, 기존 파티션에서 데이터를 삭제(Delete)하고 새 파티션에 삽입(Insert)하는 과정을 거칩니다.
  • 성능 저하: 단일 테이블에서의 업데이트보다 훨씬 느리며, 잦은 이동은 스토리지(SSD)에 과도한 쓰기 부하를 줍니다.
  • 실무 팁: 웬만하면 변경이 거의 없는 컬럼(예: 생성일자)을 파티션 키로 잡는 이유가 바로 이것 때문입니다.

까다로운 운영과 스키마 변경:

서비스가 성장하면 테이블 구조를 바꿔야 할 때가 반드시 옵니다.

  • DDL의 복잡성: 인덱스를 추가하거나 컬럼을 변경할 때, 모든 파티션 자식 테이블이 동기화되어야 합니다. 일부 구형 DBMS나 특정 상황에서는 이 작업 중에 테이블이 잠겨(Lock) 서비스가 중단될 수도 있습니다.
  • DBMS 의존성: MySQL, PostgreSQL 등 각 엔진마다 파티셔닝을 다루는 디테일이 다릅니다. 우리 회사가 쓰는 DB의 특성을 완벽히 파악해야 사고를 막을 수 있습니다.
profile
취준생의 개발블로그

0개의 댓글