운영 중인 Kudu 테이블에서 "PK 6개를 전부 HASH 에 넣고, RANGE 도 PK 전체로 선언했는데 SHOW RANGE PARTITIONS 에는 UNBOUNDED 하나만 나오는" 구조가 발견됐다. 이런 구조가 왜 생기고 무엇이 문제인지, 2.8억 건 테이블을 기준으로 해시 컬럼·버킷 수·레인지 단위를 어떻게 정해야 하는지를 Kudu Schema Design 문서 기준으로 정리한다.
=, IN) 조건이 있어야만 버킷을 특정한다. HASH(c1..c6) 에서 c1, c2 만 조건에 오면 해시값을 계산할 수 없어 전 버킷을 스캔한다. 부등호·BETWEEN·LIKE 는 해시 컬럼에 있어도 절대 프루닝되지 않는다.(store, dept, grp, dt) 는 튜플 사전식 비교라 dt 조건만으로는 프루닝되지 않는다.HASH(a) 4, HASH(b) 4 처럼 나누면 각 레벨이 독립적으로 프루닝되지만 태블릿 수는 곱(16)이 된다.PROFILE 의 KUDU_SCAN_NODE → ScanRangesComplete(스캔한 태블릿 수)로 확인한다.레인지 파티션의 "컬럼" 과 "경계" 는 별개 메타데이터다. 경계를 하나도 주지 않으면 전체 키 공간을 덮는 파티션 하나가 만들어진다. Impala DDL 은 RANGE (...) 에 경계를 최소 하나 요구하므로, 이 구조는 대개 Spark·Java 클라이언트·NiFi 등 API 로 만든 테이블에서 나타난다. Java 클라이언트는 range 컬럼을 지정하지 않으면 PK 전체를 range 컬럼으로 잡는다.
kuduContext.createTable(name, schema, keys, new CreateTableOptions().addHashPartitions(pkCols, 8))
// range 를 건드리지 않음 → RANGE(PK 전체) + 경계 없음 = UNBOUNDED 1개
문제점은 (1) 태블릿 수가 버킷 수로 영구 고정돼 데이터가 쌓일수록 태블릿이 비대해지고, (2) UNBOUNDED 가 전체 키 공간을 점유해 ADD RANGE PARTITION 이 영원히 충돌하며, (3) 파티션 드롭으로 과거 데이터를 버릴 수 없어 행 단위 DELETE 와 컴팩션에만 의존한다는 것이다. 정적이고 작은 lookup 테이블이면 무해하지만 계속 쌓이는 팩트 테이블이면 재설계 대상이다. HASH 만 있고 RANGE 절이 없는 테이블에 SHOW RANGE PARTITIONS 를 하면 오류가 나므로, UNBOUNDED 가 나왔다는 것은 RANGE 절이 존재한다는 뜻이다.
202401 <= VALUES < 202402 는 하한 포함·상한 미포함이다. 월별로 나누려면 각 월마다 [해당월, 다음월) 을 주거나 PARTITION VALUE = 202401 단일값 파티션을 쓴다. 202401 <= VALUES < 202501 하나로 묶으면 1년치가 한 파티션에 들어가 세부 프루닝이 없다.
ALTER TABLE t ADD RANGE PARTITION VALUE = 202501;
ALTER TABLE t ADD RANGE PARTITION 202501 <= VALUES < 202502;
ALTER TABLE t DROP RANGE PARTITION VALUE = 202301;
PARTITIONS 1 은 만들 수 없다. "no hash" 는 HASH 절 생략이다.PK (gbm, section, salesid, item, siteid), gbm 3개(A 2억 / B 3,600만 / C 3,900만), tserver 6대.
| 항목 | 현행 | 제안 |
|---|---|---|
| HASH 컬럼 | PK 5개 전부 | salesid 하나 (고카디널리티, 항상 등치로 들어옴) |
| HASH 버킷 | 6 | 6 (총 18 태블릿, 노드당 3, RF=3 이면 레플리카 노드당 9) |
| RANGE | gbm 3개 |
정적 테이블이면 유지, 계속 쌓이면 (ym, gbm) 튜플 range 로 |
gbm 편중(73%)이 있으면 Kudu 1.17 이상의 range 별 커스텀 해시 스키마로 A=12, B=3, C=3 버킷을 줘 태블릿 크기를 균일하게 맞춘다. 균등 해시는 "버킷 개수" 를 공평하게 나누고 커스텀 해시는 "일의 양" 을 공평하게 나눈다.
CREATE TABLE sales_new (
gbm STRING NOT NULL, section STRING NOT NULL, salesid STRING NOT NULL,
item STRING NOT NULL, siteid STRING NOT NULL, amt DOUBLE,
PRIMARY KEY (gbm, section, salesid, item, siteid)
)
PARTITION BY HASH (salesid) PARTITIONS 6
RANGE (gbm)
(
PARTITION 'A' <= VALUES < 'B' HASH (salesid) PARTITIONS 12
PARTITION 'B' <= VALUES < 'C' HASH (salesid) PARTITIONS 3
PARTITION 'C' <= VALUES < 'D' HASH (salesid) PARTITIONS 3
)
STORED AS KUDU;
계속 쌓이는 원장이라면 PK 에 ym 을 추가하고 RANGE (ym) 또는 RANGE (ym, gbm) 으로 월별 롤링(선행 ADD, 보존기간 경과분 DROP)하는 것이 근본 해법이다.
파티셔닝·PK 변경은 in-place 가 불가능하므로 새 테이블 생성 → INSERT INTO ... SELECT(대용량은 gbm 단위로 나눠 적재) → GROUP BY gbm 건수·합계 대조 → RENAME 스왑 순서로 한다.
VALUE = 'A' 방식은 새 코드 D 가 들어오면 insert 가 거부된다. ADD RANGE PARTITION 운영 절차를 문서화한다.