왜 파티셔닝인가: 이 글에서 다루는 것
SAP HANA에서 대용량 테이블을 운영하다 보면 어느 순간 "어제까지 잘 돌던 쿼리가 갑자기 느려졌다"는 상황을 만나게 됩니다. 원인은 다양하지만, 수억 건 이상 쌓인 컬럼 스토어 테이블을 파티셔닝 없이 방치한 경우가 대표적입니다. 이 글은 초급자를 대상으로 파티셔닝이 없을 때 실제로 어떤 성능 문제가 생기는지, 그리고 기본 적용법을 창작 시나리오와 SQL 예제로 설명합니다. 읽고 나면 다음을 할 수 있게 됩니다.
- 파티셔닝이 필요한 신호(행 수, 델타 머지 지연, 메모리 부하)를 스스로 진단
- RANGE/HASH 기반 기본 파티셔닝 DDL 작성
- 기존 무파티션 테이블을 안전하게 전환하는 절차 이해
- 모니터링 뷰로 파티션 상태를 점검하는 방법 습득
미리 갖추면 좋은 배경
기본적인 SQL(CREATE TABLE, SELECT, WHERE)을 작성할 수 있고, HANA의 컬럼 스토어와 로우 스토어의 차이를 대략 알고 있으면 충분합니다. 델타 스토어와 메인 스토어, 델타 머지라는 용어를 들어본 적이 있다면 이해가 더 빠르지만, 본문에서 필요한 만큼 다시 설명하므로 처음이어도 따라올 수 있습니다.
검증 환경과 버전 정보
이 글의 예제는 다음 환경을 기준으로 작성했습니다.
- SAP HANA Cloud(2024 QRC 이후 버전) 또는 SAP HANA Platform 2.0 SPS07 온프레미스
- SQL 실행 도구: SAP HANA Database Explorer 또는 hdbsql
- 권한: 대상 스키마에 대한 CREATE TABLE, ALTER 권한과 모니터링 뷰(M_TABLE_PARTITIONS 등) 조회 권한
문법 대부분은 두 에디션에서 동일하게 동작하지만, 동적 레인지 파티션(PARTITION OTHERS DYNAMIC) 같은 일부 기능은 버전에 따라 지원 범위가 다르므로, 운영 환경에서는 사용 중인 버전의 문서를 먼저 확인하는 것이 권장됩니다.
핵심 개념: 20억 행의 벽과 파티션 프루닝
파티셔닝은 하나의 논리 테이블을 여러 개의 물리 조각(파티션)으로 나누어 저장하는 기법입니다. 애플리케이션 입장에서는 여전히 테이블 하나로 보이지만, 데이터베이스 내부에서는 각 파티션이 독립적으로 로드되고, 머지되고, 스캔됩니다.
비유하자면 파티셔닝 없는 대용량 테이블은 10년 치 서류를 한 개의 거대한 캐비닛에 몰아넣은 상태입니다. 서류 한 장을 찾으려 해도 캐비닛 전체를 뒤져야 하고, 새 서류를 정리(델타 머지)할 때마다 캐비닛 전체를 재배열해야 합니다. 파티셔닝은 이 캐비닛을 "연도별 서랍"으로 나누는 작업입니다. 2026년 서류를 찾을 때 2016년 서랍은 열어볼 필요조차 없습니다.
파티셔닝 없이 겪는 대표적인 문제는 세 가지입니다.
- 행 수 한계 — HANA 컬럼 스토어 테이블(정확히는 파티션 하나)은 일반적으로 약 20억(2,147,483,648) 행까지만 담을 수 있습니다. 이 한계에 도달하면 INSERT 자체가 실패하므로, 대량 적재 테이블은 한계 도달 전에 반드시 분할 계획이 필요합니다.
- 델타 머지 비용 증가 — 델타 머지는 파티션 단위로 수행됩니다. 무파티션 테이블에서는 몇 건만 변경돼도 테이블 전체가 머지 대상이 되어 CPU와 메모리를 크게 소모하고, 머지가 밀리면 델타 스토어가 비대해져 조회 성능까지 함께 나빠집니다.
- 불필요한 전체 스캔과 메모리 로드 — WHERE 조건이 특정 기간만 겨냥해도, 파티션이 없으면 해당 컬럼 전체를 메모리에 올려 스캔해야 합니다. 파티셔닝을 적용하면 옵티마이저가 조건에 해당하지 않는 파티션을 아예 건너뛰는 파티션 프루닝(pruning)이 동작해 스캔 범위와 메모리 사용량이 함께 줄어듭니다.
파티셔닝 타입은 크게 HASH(지정 컬럼의 해시값으로 균등 분배), RANGE(값 구간으로 분할, 날짜 기반에 적합), ROUND ROBIN(순차 분배), 그리고 이들을 조합한 다단계(multi-level) 방식이 있습니다. 세 타입의 심층 비교는 별도 글에서 다루므로, 여기서는 "시계열 대용량 테이블이면 우선 날짜 RANGE부터 검토한다"는 실무 감각만 잡고 실전 적용으로 넘어가겠습니다.
실전 예제: 3단계로 적용하는 테이블 파티셔닝
가상의 물류 회사 시나리오를 사용합니다. 전국 허브를 오가는 배송 스캔 이벤트를 저장하는 ZLG_SCAN_EVT 테이블이 하루 500만 건씩 쌓여 3년 만에 50억 건을 바라보는 상황입니다.
1단계 — 기본 예제: 처음부터 파티션 테이블로 생성
신규 테이블이라면 생성 시점에 파티션을 정의하는 것이 가장 깔끔합니다. 스캔 일시 기준의 RANGE 파티션 예제입니다.
CREATE COLUMN TABLE ZLG_SCAN_EVT (
EVT_ID BIGINT NOT NULL,
PARCEL_NO NVARCHAR(20) NOT NULL,
HUB_CODE NVARCHAR(6) NOT NULL,
SCAN_TYPE NVARCHAR(2) NOT NULL, -- IN/OT/DL 등
SCAN_TS TIMESTAMP NOT NULL,
SCAN_YM NVARCHAR(6) NOT NULL, -- 'YYYYMM', 프루닝 키
PRIMARY KEY (EVT_ID, SCAN_YM)
)
PARTITION BY RANGE (SCAN_YM) (
PARTITION '202501' <= VALUES < '202507',
PARTITION '202507' <= VALUES < '202601',
PARTITION OTHERS
);
포인트는 두 가지입니다. 첫째, 파티션 키(SCAN_YM)는 조회 조건에 자주 등장하는 컬럼이어야 프루닝 효과를 봅니다. 둘째, PARTITION OTHERS는 정의된 구간을 벗어난 값을 받아주는 안전망으로, 이것이 없으면 범위 밖 INSERT가 실패합니다.
2단계 — 실무 시나리오: 이미 커져버린 테이블 전환과 검증
현실에서는 무파티션 상태로 이미 수억 건이 쌓인 뒤에 문제를 인지하는 경우가 많습니다. 먼저 현재 상태를 진단합니다.
-- 행 수와 메모리 사용량 확인
SELECT TABLE_NAME, RECORD_COUNT,
MEMORY_SIZE_IN_TOTAL / 1024 / 1024 AS MEM_MB,
RAW_RECORD_COUNT_IN_DELTA AS DELTA_ROWS
FROM M_CS_TABLES
WHERE SCHEMA_NAME = 'LOGISTICS'
AND TABLE_NAME = 'ZLG_SCAN_EVT';
RECORD_COUNT가 수억 단위이고 DELTA_ROWS가 계속 높게 유지된다면 전환 후보입니다. 기존 테이블은 ALTER 문으로 파티셔닝할 수 있습니다.
ALTER TABLE LOGISTICS.ZLG_SCAN_EVT
PARTITION BY RANGE (SCAN_YM) (
PARTITION '202401' <= VALUES < '202501',
PARTITION '202501' <= VALUES < '202601',
PARTITION OTHERS
);
-- 전환 결과 확인
SELECT PART_ID, RECORD_COUNT, RANGE
FROM M_TABLE_PARTITIONS
WHERE SCHEMA_NAME = 'LOGISTICS'
AND TABLE_NAME = 'ZLG_SCAN_EVT'
ORDER BY PART_ID;
주의할 점은 이 ALTER가 데이터 전체를 재배치하는 무거운 작업이라는 것입니다. 테이블 크기에 비례해 시간이 걸리고 실행 중 상당한 메모리를 사용하므로, 일반적으로 배치가 없는 시간대에 수행하고, 실패에 대비해 사전 백업과 디스크/메모리 여유 확인이 권장됩니다. 전환 후에는 대표 쿼리에 EXPLAIN PLAN을 걸어 실제로 일부 파티션만 읽는지 확인합니다.
EXPLAIN PLAN FOR
SELECT HUB_CODE, COUNT(*)
FROM LOGISTICS.ZLG_SCAN_EVT
WHERE SCAN_YM = '202508'
GROUP BY HUB_CODE;
-- EXPLAIN_PLAN_TABLE에서 접근 파티션 수가 1로 줄었는지 확인
3단계 — 프로덕션: 자동 확장과 다단계 설계
운영 단계의 고민은 "매달 파티션을 수동으로 추가해야 하나?"입니다. HANA Cloud와 최신 온프레미스 버전에서는 동적 레인지 파티션으로 OTHERS 파티션이 임계치를 넘으면 새 구간을 자동 분리하도록 구성할 수 있습니다.
ALTER TABLE LOGISTICS.ZLG_SCAN_EVT
ALTER PARTITION OTHERS DYNAMIC THRESHOLD 150000000;
또한 단일 파티션이 다시 20억 행에 근접하는 초대용량이라면, HASH와 RANGE를 조합한 다단계 파티셔닝으로 분산과 프루닝을 동시에 잡을 수 있습니다.
CREATE COLUMN TABLE ZLG_SCAN_EVT_V2 (
EVT_ID BIGINT NOT NULL,
PARCEL_NO NVARCHAR(20) NOT NULL,
HUB_CODE NVARCHAR(6) NOT NULL,
SCAN_TS TIMESTAMP NOT NULL,
SCAN_YM NVARCHAR(6) NOT NULL,
PRIMARY KEY (EVT_ID, SCAN_YM)
)
PARTITION BY HASH (EVT_ID) PARTITIONS 4,
RANGE (SCAN_YM) (
PARTITION '202601' <= VALUES < '202607',
PARTITION OTHERS
);
보안·품질 관점에서는 파티션 관리 DDL 권한을 운영 계정으로 제한하고, 월 단위로 M_TABLE_PARTITIONS의 파티션별 행 수 편차를 점검하는 모니터링 잡을 두는 구성이 일반적입니다. 오래된 구간은 파티션 단위로 DROP하거나 데이터 에이징 대상으로 넘겨 메모리를 회수합니다.
자주 만나는 함정 FAQ
- Q1. 파티셔닝했는데 쿼리가 하나도 안 빨라졌어요. — 가장 흔한 원인은 WHERE 조건에 파티션 키가 없는 경우입니다. SCAN_YM으로 파티셔닝해 놓고 SCAN_TS로만 조회하면 프루닝이 동작하지 않아 전 파티션을 스캔합니다. 쿼리 패턴에 맞춰 키를 정하거나, 조회 조건에 파티션 키를 함께 넣도록 애플리케이션을 수정해야 합니다.
- Q2. INSERT가 "no matching partition" 류의 오류로 실패합니다. — RANGE 정의 구간 밖의 값이 들어온 경우입니다.
PARTITION OTHERS를 추가하면 해결되지만, OTHERS에 데이터가 몰리지 않도록 주기적으로 새 구간을 분리해 주는 운영이 필요합니다. - Q3. 특정 파티션만 비정상적으로 큽니다(스큐). — 값 분포가 치우친 컬럼을 키로 잡았을 때 발생합니다. 예를 들어 HUB_CODE로 RANGE를 잡았는데 수도권 허브 물량이 70%라면 분할 의미가 없습니다. 카디널리티가 높고 분포가 고른 컬럼(ID의 HASH, 날짜 구간 등)으로 재설계합니다.
- Q4. 파티션을 너무 잘게 쪼갰더니 오히려 느려졌어요. — 파티션마다 관리 오버헤드(딕셔너리, 메타데이터, 머지 단위)가 붙습니다. 일 단위 파티션 수천 개보다는, 파티션당 수천만~수억 행 수준을 유지하는 월/분기 단위가 일반적으로 무난합니다.
- Q5. ALTER PARTITION 도중 메모리 부족으로 실패했습니다. — 재파티셔닝은 원본과 대상 데이터를 동시에 다루므로 피크 메모리가 큽니다. 업무 외 시간대 수행, 사전 델타 머지로 델타 스토어 축소, 필요 시 단계적 분할이 현실적인 대응입니다.
이어서 살펴볼 주제
기본 적용법을 익혔다면 다음 주제로 확장해 보세요. 첫째, HASH·RANGE·ROUND ROBIN 세 타입의 특성 비교와 선택 기준(별도 글에서 상세히 다룹니다). 둘째, 스케일아웃 환경에서 파티션을 노드별로 배치해 부하를 분산하는 테이블 분산 설계. 셋째, NSE(Native Storage Extension)와 파티셔닝을 결합해 콜드 데이터를 디스크 기반으로 내리는 비용 최적화. 넷째, 델타 머지 파라미터 튜닝과 파티션 단위 머지 전략입니다.
더 깊이 파고들기 위한 자료
댓글 0
아직 댓글이 없습니다.