1. Intro
이번에 AWS RDS에 적재되는 대량의 statistics 데이터를 좀 더 효율적으로 조회하기 위해서 CTO님과 함께 파티셔닝을 적용해보는 기회를 가질 수 있었습니다. 단순하고 확실한 방법은 당연하게도 RDS의 티어를 올리는 것일 테지만, 티어를 올렸을 때의 비용은 단연코 무시할 수 있는 수준이 아니었습니다. 때문에 AS-IS 환경에서 할 수 있는 효율을 극대화 하기 위해 파티셔닝을 해보았습니다.
실습에 활용한 예제는 아래의 북마크와 동일하며, 니즈에 따라 약간의 차이가 있기 때문에 적용 과정에서 수정한 부분만 짚어볼 예정입니다. 이번 포스트에서는 AWS RDS 중 Postgresql에서 파티셔닝을 했던 경험과 간단히 파티셔닝에 대해 알아본 내용을 정리해보도록 하겠습니다.
2. Partitioning
먼저, 파티셔닝을 적용한 내용에 앞서, 파티셔닝에 대해 간단히 알아보겠습니다. 데이터베이스 파티셔닝은 데이터베이스 테이블을 여러 개의 소규모 상위 테이블로 나누는 방법입니다. 이를 통해 데이터 관리 효율성을 높이고, 성능을 개선하며, 특정 쿼리에 대해 더 빠른 응답 시간을 제공할 수 있습니다. 파티셔닝은 일반적으로 대량의 데이터를 처리해야 하는 경우 유용합니다. 아래와 같이 도식화해서 본다면 좀 더 이해가 쉬울 것 같습니다.
+-----------------------+
| 전체 테이블 |
| (Sales) |
+-----------------------+
|
| (sale_date 기준에 따라 나누어짐)
V
+------------------------+
| Range Partitioning |
+------------------------+
| 2023 Partition |
| (sales_2023) |
| ---------------------- |
| | 2023-01-01 | Data1 | |
| | 2023-01-15 | Data2 | |
| | 2023-02-01 | Data3 | |
| ---------------------- |
+------------------------+
| 2024 Partition |
| (sales_2024) |
| ---------------------- |
| | 2024-01-01 | Data4 | |
| | 2024-06-01 | Data5 | |
| | 2024-12-01 | Data6 | |
| ---------------------- |
+------------------------+
| 2025 Partition |
| (sales_2025) |
| ---------------------- |
| | 2025-01-01 | Data7 | |
| | 2025-03-01 | Data8 | |
| | 2025-04-01 | Data9 | |
| ---------------------- |
+------------------------+
Plain Text
복사
위의 그림 예제는 Range Partitioning 을 도식화한 예제입니다. 설명은 다음과 같습니다.
•
전체 테이블은 판매 데이터가 저장되는 기본 테이블
•
Range Partitioning을 ‘sale_date’ 기준으로 연도별 파티션으로 나누었음
•
각 파티션 sales_202? 들은 특정 연도별 데이터를 포함하고 있음
•
특정 날짜를 검색할 때 필요한 파티션만 참조하기 때문에 효율적인 검색이 가능함
Range Partitioning
데이터를 범위에 따라 나누는 방법으로, 특정 값의 범위에 해당하는 데이터를 각각의 파티션에 저장합니다. 예를 들어, 위의 Intro 설명에서와 같이 날짜별로 데이터를 나눌 수 있습니다. 날짜별로 데이터를 나누고자 할 때 특정 년도, 월을 기준으로 각각의 파티션을 만드는 게 가능합니다.
아래의 예시 SQL에서는 ‘sales’ 테이블을 연도별로 나눈 두 개의 파티션을 생성하고, 쿼리를 실행할 때 ‘sale_date’가 해당 범위에 속하는 파티션만 검색하게 되어 성능 향상이 이루어질 수 있습니다.
CREATE TABLE sales (
id SERIAL PRIMARY KEY,
sale_date DATE,
amount NUMERIC
) PARTITION BY RANGE (sale_date);
CREATE TABLE sales_2023 PARTITION OF sales
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
CREATE TABLE sales_2024 PARTITION OF sales
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
SQL
복사
List Partitioning
데이터의 특정 속성이 갖는 여러 값 중에서 정의된 목록에 따라 데이터를 분류하는 방식입니다. 각 파티션은 특정 값 집합에 해당하는 데이터를 저장합니다. 아래의 예시처럼 일반적으로 특정 테이블의 카테고리나 지역 등으로 나누기도 합니다.
아래의 예시는 ‘products’ 테이블에서 category로 집합을 나누어 파티셔닝 하는 예제입니다. 카테고리에 따라 쿼리를 할 때 효율적으로 처리될 수 있습니다.
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
category TEXT
) PARTITION BY LIST (category);
CREATE TABLE products_electronics PARTITION OF products
FOR VALUES IN ('Electronics');
CREATE TABLE products_furniture PARTITION OF products
FOR VALUES IN ('Furniture');
CREATE TABLE products_clothing PARTITION OF products
FOR VALUES IN ('Clothing');
SQL
복사
Hash Partitioning
해시 함수를 사용하여 데이터를 다양한 파티션으로 분산시키는 방법입니다. 이 방법은 데이터가 여러 파티션에 균등하게 분포되도록 하는 데 유용합니다. 예를 들어 사용자의 ID나 기타 고유 식별자를 해시해서 데이터를 균형 있게 분산시키기도 합니다. 단, Hash Partitioning은 고르게 데이터를 분포시키고 Hash 알고리즘을 사용하는 특징 때문에 Range Partitioning, List Partitioning과 달리 많은 데이터를 한 번에 조회하기보다 특정 값을 Retrieve할 때 유리합니다.
아래의 예제에서는 ‘users’ 테이블을 ID에 따라 4개의 해시 파티션으로 나누었습니다.
•
MODULUS : 총 파티션 수
•
REMAINDER : 각 파티션의 인덱스를 나타내며, 해시 표현식을 통해 각 사용자가 어떤 파티션에 저장될지 결정됨
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username TEXT
) PARTITION BY HASH (id);
CREATE TABLE users_part_1 PARTITION OF users
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE users_part_2 PARTITION OF users
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE users_part_3 PARTITION OF users
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE users_part_4 PARTITION OF users
FOR VALUES WITH (MODULUS 4, REMAINDER 3;
SQL
복사
3. pg_partman extension 활용한 파티셔닝
전체적인 파티셔닝을 위해 수행한 방법은 다음과 같습니다.
1. 파티셔닝 관리를 위한 스키마(partman) 생성
2. pg_partman extension 추가
3. pg_extension 확인
1. pg_partman이 제대로 추가됐는지 확인
4. 기존 적재된 테이블을 복사
1. ORIGIN_TABLE_NAME에 `_parent` 를 붙여 새롭게 파티셔닝을 위한 테이블을 생성함(DDL 복사하여 약간 수정 후 실행)
2. 테이블 내 인덱스가 있었다면 미리 인덱스 생성해둠 (index 이름은 global로 관리되기 때문에 이름도 수정해줘야 함)
5. create_parent 함수를 사용하여 파티션 구성
6. run_maintenance_proc 함수를 사용하여 파티션 유지 관리 구성
Plain Text
복사
위의 과정을 요약하여 간단히 나열해보겠습니다.
partman 스키마 생성 및 pg_partman extension 추가
CREATE SCHEMA partman;
CREATE EXTENSION pg_partman WITH SCHEMA partman;
SQL
복사
pg_extension 확인
SELECT * FROM pg_extension;
SQL
복사
기존 테이블 복사 및 인덱스 생성
// DDL 복사하여 필요한 부분만 수정해주었음
CREATE TABLE [ORIGIN_TABLE_NAME]_parent (
id bigserial,
...
snapshot_date, // 스냅샷 날짜
CONSTRAINT pk_origin_table_name_parent PRIMARY KEY (id, snapshot_date)
) PARTITION BY RANGE (snapshot_date);
// GRANT or change OWNER
GRANT SELECT ON [ORIGIN_TABLE_NAME]_parent TO ...
// 기존에 있던 index와 동일한 설정으로 생성...
CREATE INDEX ???_index ON [ORIGIN_TABLE_NAME]_parent (snapshot_date)
SQL
복사
create_parent 함수를 사용하여 파티션 구성
앞서 range partitioning의 예제에서는 수동으로 파티션 테이블을 관리하는 방법을 보여주었지만, 아래의 예제는 파티션을 자동으로 나눠주고 관리해주기 위해서 create_parent 함수를 사용하여 파티션 유지 관리가 가능합니다. 이를 사용하기 위해서는 pg_partman extension이 활성화 돼 있어야 합니다.
아래와 같이 함수를 실행하면 snapshot_date 열을 기반으로 월별 파티셔닝을 설정하고, 기본 파티셔닝 기능(native)을 사용하여 36개의 파티션을 미리 생성합니다.
// 파티셔닝 설정 생성
SELECT partman.create_parent(
p_parent_table => '[schema].[table_name]', // ex> public.some_snapshot
p_control => 'snapshot_date',
p_type => 'native', // 기본 파티셔닝 (postgresql 10 이상 지원)
p_interval => '1 month', // 파티션이 생성되는 주기
p_premake => 36 // 36개월치 미리 생성
);
// 파티셔닝 생성 여부 확인
SELECT * FROM partman.part_config;
SQL
복사
run_maintenance_proc 함수를 사용하여 파티션 유지 관리 구성
아래와 같이 pg_cron을 추가하여 infinite_time_partitions를 true로 바꿔주고, cron 스케줄을 실행해줍니다. retension 조건의 경우 삭제되면 안 되기 때문에 retension 기능을 비활성화(null) 처리하고, 기본 속성값도 유지하도록 하였습니다.
•
retension : 최대 보존 기간(쿼리를 날릴 때 참조하는 기간)
◦
기간을 설정하면 해당 기간 동안만 보존됨
◦
이후 조회할 때 제외되거나, retension_keep_table이 False일 경우 삭제됨
•
retension_keep_table : True로 설정 시 보존 기간 만료 시 child_table이 삭제되지 않도록 함
// pg_cron 활성화 (AWS RDS에서는 parameter group 수정 필요)
CREATE EXTENSION pg_cron;
// 제한 없이 자동으로 새 파티션 생성되도록 조건 수정
UPDATE partman.part_config
SET infinite_time_partitions = true
// retention = '3 months', <-- 3개월 동안만 검색할 때 바라보는 설정(불필요하여 제거)
// retention_keep_table=true <-- 기본 True
WHERE parent_table = 'data_mart.events';
// cron 생성
SELECT cron.schedule('@hourly', $$CALL partman.run_maintenance_proc()$$);
SQL
복사
기존 테이블 데이터 복사
아래와 같이 데이터를 복사해주면, 나누어진 파티션에 데이터가 자동으로 나뉘어 들어가게 됩니다.
INSERT INTO ORIGIN_TABLE_NAME_parent SEELCT * FROM ORIGIN_TABLE_NAME;
SQL
복사
4. Conclusion
위의 과정을 마치게 되면 Range partitioning이 자동으로 관리되며, 데이터 역시 복사하는 과정에서 자동으로 child 테이블에 나뉘어 저장됩니다.
// child_table이 잘 생성됐는지 확인하는 쿼리
SELECT inhrelid::regclass AS child_table
FROM pg_inherits
WHERE inhparent = 'TABLE_NAME'::regclass;
// child_table에 데이터가 잘 들어갔는지 확인하는 쿼리
SELECT tableoid::regclass AS actual_table, COUNT(*)
FROM TABLE_NAME
GROUP BY tableoid
ORDER BY tableoid;
SQL
복사
이번 파티셔닝 작업은 Cloud 환경에서 Scale Up에 의존하지 않고, 좀 더 효율적으로 있는 자원을 활용해볼 수 있는 걸 알게 된 경험이었습니다. 파티셔닝은 IDC와 같은 곳에 서버가 내장돼 있을 때, 당장 Scale Up이 어렵거나 추가 비용을 지불할 수 없을 때, 혹은 온프레미스 환경에서 소프트웨어적으로 성능을 끌어올릴 수 있을 것 같습니다.