대규모 PostgreSQL 테이블 선언적 파티셔닝 모범 사례
올바른 키, 범위/목록/해시 전략, 인덱스, 제약 조건 및 수명 주기 계획을 사용하여 대규모 PostgreSQL 테이블을 파티셔닝하세요.
대규모 PostgreSQL 테이블 선언적 파티셔닝 모범 사례
모든 쿼리, 인덱스 재구축 또는 데이터 보존 작업이 동일한 대규모 관계를 처리해야 할 때 대규모 PostgreSQL 테이블은 관리하기 어려워집니다. 선언적 파티셔닝을 사용하면 하나의 논리적 테이블을 더 작은 하위 테이블로 분할하여 PostgreSQL이 행을 라우팅하고 파티션 키를 사용하는 쿼리에 대해 파티션을 프루닝할 수 있습니다.
핵심은 계획입니다. 파티셔닝은 쿼리 필터 및 데이터 수명 주기와 일치할 때 가장 도움이 되며, 파티션 키가 거의 사용되지 않을 때 오버헤드가 추가될 수 있습니다.
선언적 파티셔닝 이해
선언적 파티셔닝을 사용하면 테이블을 파티셔닝된 것으로 정의하고 파티셔닝 키와 전략을 지정할 수 있습니다. 그러면 PostgreSQL은 파티셔닝 키의 값을 기반으로 데이터를 적절한 파티션으로 자동 라우팅합니다. 이렇게 하면 복잡한 트리거나 수동 데이터 관리가 필요하지 않아 이전 방법보다 훨씬 깔끔하고 효율적인 솔루션이 됩니다.
선언적 파티셔닝의 주요 이점:
- 쿼리 성능 향상: 파티셔닝 키로 필터링하는 쿼리는 관련 파티션만 스캔하여 처리되는 데이터 양을 줄일 수 있습니다.
- 더 빠른 데이터 로딩: 대량 로딩 작업을 특정 파티션으로 지정하여 효율성을 높일 수 있습니다.
- 간소화된 유지 관리: 보관, 오래된 데이터 삭제 또는 인덱스 재구성과 같은 작업을 전체 테이블에 영향을 주지 않고 개별 파티션에서 수행할 수 있습니다.
- 오버헤드 감소: 수동 파티셔닝 로직 및 관련 유지 관리가 필요하지 않습니다.
PostgreSQL의 파티셔닝 전략
PostgreSQL은 선언적 파티셔닝을 위한 세 가지 기본 전략을 제공하며, 각각 다른 사용 사례에 적합합니다.
1. 범위 파티셔닝
범위 파티셔닝은 파티셔닝 키의 연속적인 값 범위를 기준으로 데이터를 나눕니다. 이는 시계열 데이터, 순차적 ID 또는 값이 정의된 간격 내에 속하는 모든 데이터에 이상적입니다.
사용 시기:
- 시계열 데이터(예: 날짜/타임스탬프별 로그, 이벤트).
- 순차적으로 생성된 ID.
- 정렬된 연속 값이 있는 데이터.
예시: sale_date로 sales 테이블 파티셔닝.
-- 부모 파티셔닝된 테이블 생성
CREATE TABLE sales (
sale_id SERIAL,
product_id INT,
amount DECIMAL(10, 2),
sale_date DATE NOT NULL
)
PARTITION BY RANGE (sale_date);
-- 특정 날짜 범위에 대한 파티션 생성
CREATE TABLE sales_2023_q1 PARTITION OF sales
FOR VALUES FROM ('2023-01-01') TO ('2023-04-01');
CREATE TABLE sales_2023_q2 PARTITION OF sales
FOR VALUES FROM ('2023-04-01') TO ('2023-07-01');
CREATE TABLE sales_2023_q3 PARTITION OF sales
FOR VALUES FROM ('2023-07-01') TO ('2023-10-01');
CREATE TABLE sales_2023_q4 PARTITION OF sales
FOR VALUES FROM ('2023-10-01') TO ('2024-01-01');
-- 데이터 삽입은 자동으로 올바른 파티션으로 이동
INSERT INTO sales (product_id, amount, sale_date) VALUES (101, 150.50, '2023-02-15');
2. 목록 파티셔닝
목록 파티셔닝은 파티셔닝 키의 개별 값 목록을 기준으로 데이터를 나눕니다. 고정된 알려진 범주 또는 식별자 집합이 있을 때 유용합니다.
사용 시기:
- 지리적 지역(예:
country,state). - 제품 범주.
- 사용자 역할 또는 상태.
예시: country_code로 customers 테이블 파티셔닝.
-- 부모 파티셔닝된 테이블 생성
CREATE TABLE customers (
customer_id SERIAL,
name VARCHAR(100),
country_code CHAR(2) NOT NULL
)
PARTITION BY LIST (country_code);
-- 특정 국가 코드에 대한 파티션 생성
CREATE TABLE customers_us PARTITION OF customers
FOR VALUES IN ('US');
CREATE TABLE customers_ca PARTITION OF customers
FOR VALUES IN ('CA');
CREATE TABLE customers_uk PARTITION OF customers
FOR VALUES IN ('GB');
-- 데이터 삽입은 자동으로 올바른 파티션으로 이동
INSERT INTO customers (name, country_code) VALUES ('John Doe', 'US');
3. 해시 파티셔닝
해시 파티셔닝은 파티셔닝 키의 해시 값을 기준으로 데이터를 나눕니다. 자연스러운 범위나 목록이 없을 때 파티션 간에 데이터를 균등하게 분산하여 I/O 부하를 분산하는 데 유용합니다.
사용 시기:
- 다른 전략이 적합하지 않을 때 데이터를 균등하게 분산.
- I/O 핫스팟 방지.
- 균등한 분산이 중요한 대용량 트랜잭션 테이블.
예시: order_id로 orders 테이블 파티셔닝.
-- 부모 파티셔닝된 테이블 생성
CREATE TABLE orders (
order_id BIGSERIAL,
user_id INT,
order_total DECIMAL(10, 2)
)
PARTITION BY HASH (order_id);
-- 지정된 수의 파티션 생성(예: 4개)
CREATE TABLE orders_part_1 PARTITION OF orders FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE orders_part_2 PARTITION OF orders FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE orders_part_3 PARTITION OF orders FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE orders_part_4 PARTITION OF orders FOR VALUES WITH (MODULUS 4, REMAINDER 3);
-- 데이터 삽입은 자동으로 올바른 파티션으로 이동
INSERT INTO orders (user_id, order_total) VALUES (500, 250.75);
선언적 파티셔닝 구현 모범 사례
파티셔닝을 효과적으로 구현하려면 신중한 계획과 모범 사례 준수가 필요합니다.
1. 올바른 파티셔닝 키 선택
파티셔닝 키는 가장 중요한 결정입니다. 쿼리 성능과 유지 관리에 직접적인 영향을 미칩니다. 가장 일반적인 쿼리의 WHERE 절에서 자주 사용되는 키를 선택하세요.
- 시계열 데이터의 경우:
DATE,TIMESTAMP열은 범위 파티셔닝에 탁월한 후보입니다. - 범주형 데이터의 경우:
country_code,status,region과 같은 열은 목록 파티셔닝에 적합합니다. - 균등 분산의 경우: 쿼리에서 자주 사용되는 고유성 높은 열로, 해시 파티셔닝에 적합합니다.
팁: WHERE 절에서 거의 사용되지 않거나 파티션 간에 고유한 값이 없는 열을 파티셔닝하지 마세요. 이렇게 하면 쿼리가 모든 파티션을 스캔하게 될 수 있습니다.
2. 적절한 파티셔닝 전략 선택
앞서 논의한 대로 데이터 및 쿼리 패턴에 가장 잘 맞는 전략(범위, 목록, 해시)을 선택하세요.
- 범위: 정렬된 연속 데이터용.
- 목록: 개별적인 알려진 범주용.
- 해시: 균등한 데이터 분산 및 부하 분산용.
3. 파티션 크기 및 수 계획
파티션 크기에 대한 일률적인 정답은 없습니다. 그러나 다음 사항을 고려하세요.
- 너무 많은 작은 파티션: 플래너와 시스템에 오버헤드를 증가시킬 수 있습니다. 각 파티션에는 자체 메타데이터가 있습니다.
- 너무 적은 큰 파티션: 파티셔닝의 성능 이점을 무효화할 수 있습니다.
- 이상적인 크기: 성능 이점을 제공할 만큼 충분히 크지만 유지 관리 작업에 관리하기 쉬운 파티션을 목표로 하세요. 일반적인 시작점은 논리적 시간 단위(예: 시계열 데이터의 경우 일별, 주별, 월별) 또는 관리 가능한 데이터 볼륨에 파티션을 맞추는 것입니다.
팁: 파티션 크기를 모니터링하고 데이터가 증가함에 따라 파티셔닝 전략을 조정하세요. 필요한 경우 파티션을 분리하고 다시 연결하거나 다른 전략으로 파티션을 다시 생성할 수 있습니다.
4. 미래 데이터를 위한 파티셔닝 전략 정의
파티셔닝된 테이블을 생성할 때 기존 파티션에 속하지 않는 데이터를 처리하기 위해 기본 파티션이나 전략을 정의할 수도 있습니다. 그러나 예기치 않은 데이터 배치나 오류를 방지하려면 일반적으로 파티션을 명시적으로 생성하는 것이 좋습니다.
예시: 범위 파티셔닝에서 예기치 않은 값을 포착하기 위해 DEFAULT 파티션 사용.
CREATE TABLE events (
event_id BIGSERIAL,
created_at DATE NOT NULL,
payload JSONB
)
PARTITION BY RANGE (created_at);
CREATE TABLE events_2026_01 PARTITION OF events
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE events_default PARTITION OF events DEFAULT;
모범 사례: 명확성과 제어를 위해 예상되는 데이터 범위/목록에 대한 파티션을 수동으로 생성하세요. 특히 목록 또는 범위 파티셔닝의 경우 DEFAULT 파티션은 의도하지 않은 데이터가 축적될 수 있으므로 주의해서 사용하세요.
5. 파티션 수명 주기 관리(데이터 보관/삭제)
파티셔닝의 가장 큰 장점 중 하나는 간소화된 데이터 수명 주기 관리입니다. 시계열 데이터의 경우 오래된 데이터를 보관하거나 삭제하는 것이 일반적입니다.
파티션 분리: 다른 파티션에 영향을 주지 않고 데이터를 보관하거나 완전히 삭제하기 위해 파티션을 분리할 수 있습니다.
-- 파티션 분리 ALTER TABLE sales DETACH PARTITION sales_2023_q1; -- 선택적으로 분리된 파티션을 삭제하기 전에 보관 -- CREATE TABLE sales_archive_2023_q1 (LIKE sales INCLUDING ALL); -- INSERT INTO sales_archive_2023_q1 SELECT * FROM sales_2023_q1; -- 분리된 파티션 삭제 DROP TABLE sales_2023_q1;파티션 삭제: 더 이상 쿼리할 필요가 없는 매우 오래된 데이터의 경우.
-- 파티션 직접 삭제(먼저 분리하지 않은 경우 부모 테이블이 알아야 함) DROP TABLE sales_2023_q1;
팁: cron 작업이나 기타 스케줄링 도구를 사용하여 새 파티션 생성 및 오래된 파티션 분리/삭제를 자동화하고, 종종 스크립트와 결합하세요.
6. 파티션 인덱싱
파티셔닝된 테이블의 인덱스는 부모 테이블 수준 또는 개별 파티션 수준에서 관리할 수 있습니다.
- 부모에 대한 파티셔닝된 인덱스: 파티셔닝된 부모에 선언된 인덱스는 가상입니다. PostgreSQL은 파티션에 일치하는 인덱스를 생성하거나 연결합니다. 실제 인덱스 데이터는 하위 인덱스에 있습니다.
- 개별 파티션의 인덱스: 하나의 파티션에 다른 인덱스가 필요하거나 기존 테이블을 파티션으로 연결할 때 파티션별로 인덱스를 계속 관리할 수 있습니다.
모범 사례: 새 파티션이 의도된 인덱싱 패턴을 상속할 수 있도록 파티셔닝된 부모에 공통 인덱스를 생성하세요. 예외 및 대규모 유지 관리 작업에는 파티션별 인덱스 관리를 사용하세요.
-- 예시: 파티션에 로컬 인덱스 생성
CREATE INDEX ON sales_2023_q2 (product_id);
7. 일관되게 선언적 구문 사용
선언적 파티셔닝을 위해 부모 테이블에 PARTITION BY를 사용하고 하위 테이블에 PARTITION OF ... FOR VALUES를 사용하세요. 이전 상속 기반 파티셔닝 패턴은 레거시 시스템에 여전히 존재하지만 더 많은 수동 라우팅 및 유지 관리가 필요합니다.
8. 쿼리 계획 모니터링 및 분석
파티셔닝을 구현한 후에는 쿼리 성능을 모니터링하는 것이 중요합니다. EXPLAIN ANALYZE를 사용하여 쿼리가 파티션을 올바르게 프루닝하는지(즉, 관련 파티션만 스캔하는지) 확인하세요.
EXPLAIN ANALYZE SELECT * FROM sales WHERE sale_date BETWEEN '2023-02-01' AND '2023-02-28';
EXPLAIN 출력에서 쿼리 플래너가 sales_2023_q1 파티션만 고려하고 있다는 표시를 찾으세요. 쿼리 계획이 여러 파티션이나 모든 파티션을 스캔해야 하는데 그렇지 않은 경우 파티셔닝 키 또는 쿼리를 조정해야 할 수 있습니다.
고급 고려 사항
외래 키 및 고유 제약 조건
- 외래 키: 최신 PostgreSQL은 파티셔닝된 테이블과 관련된 외래 키를 지원하지만 잠금 동작 및 성능은 버전과 스키마에 대해 테스트해야 합니다.
- 고유 제약 조건: 파티셔닝된 테이블의 기본 키 또는 고유 제약 조건은 모든 파티션 키 열을 포함해야 하며 파티션 키는 표현식이 될 수 없습니다. 이 제한을 통해 PostgreSQL은 파티션별 인덱스로 고유성을 적용할 수 있습니다.
팁: 논리적 테이블 전체에서 고유성을 위해 제약 조건에 파티셔닝 키를 포함하세요. 예를 들어 country_code에 대한 목록 파티셔닝의 경우 UNIQUE (country_code, customer_id)를 사용하세요.
INSERT 성능
파티셔닝은 일반적으로 SELECT 성능을 향상시키지만 INSERT 성능은 영향을 받을 수 있습니다. 파티셔닝 키가 균일하게 분포되지 않았거나 파티셔닝 로직이 복잡한 경우 PostgreSQL이 올바른 파티션을 결정할 때 삽입에 약간의 오버헤드가 발생할 수 있습니다. 해시 파티셔닝은 쓰기 부하를 분산하는 데 종종 좋습니다.
기존 대규모 테이블에 대한 파티셔닝 전략
기존의 매우 큰 테이블을 파티셔닝하는 것은 복잡한 작업이 될 수 있습니다. 여기에는 일반적으로 다음이 포함됩니다.
- 새 파티셔닝된 테이블 구조 생성.
- 기록 데이터에 대한 파티션 생성.
- 이전 테이블에서 새 파티셔닝된 테이블로 데이터 복사(잠재적으로 배치로).
- 애플리케이션 읽기/쓰기를 새 파티셔닝된 테이블로 전환.
- 이전 테이블 삭제.
이 프로세스는 신중하게 계획하고 스테이징 환경에서 테스트한 후 유지 관리 기간 동안 실행하여 가동 중지 시간을 최소화해야 합니다.
쿼리와 달력에 맞춰 파티셔닝하세요
선언적 파티셔닝은 파티션 키가 가장 중요한 필터에 나타나고 데이터를 보관하거나 보관하는 방식과 일치할 때 가장 잘 작동합니다. 쿼리 패턴으로 시작하여 거기서 범위, 목록 또는 해시 파티셔닝을 선택하고 EXPLAIN ANALYZE로 프루닝을 확인하세요. 그런 다음 파티션 생성 및 폐기를 자동화하여 첫 달 데이터가 도착한 후에도 설계가 계속 작동하도록 하세요.