Kudu 에는 AUTO_INCREMENT · SEQUENCE · 값을 생성하는 DEFAULT 절이 없다. 기본 키 값은 쓰는 쪽에서 만들어 넣어야 한다. RDBMS 에서 옮겨 오는 테이블이 시퀀스 기반 대리 키를 쓰고 있으면 키 생성 책임을 적재 경로로 옮겨야 한다.
DEFAULT 절 자체는 존재한다. 다만 상수만 넣을 수 있고 함수 호출은 허용되지 않는다.
시퀀스가 꼭 연속일 필요가 없다면 UUID 나 시각 기반 키로 바꾸는 것이 가장 단순하다.
CREATE TABLE db.tbl (
id STRING,
payload STRING,
PRIMARY KEY (id)
)
PARTITION BY HASH (id) PARTITIONS 16
STORED AS KUDU;
UUID v4 는 값이 고르게 흩어져 해시 파티션과 잘 맞는다. 반대로 시각 접두사를 갖는 키(UUID v1 · Snowflake 계열)는 정렬성은 얻지만 레인지 파티션에서 최신 구간에 쓰기가 몰리므로 해시 파티션과 함께 써야 한다.
원본에 이미 유일성을 보장하는 컬럼 조합이 있으면 대리 키를 만들지 말고 그것을 복합 기본 키로 쓴다. 조인과 중복 판정이 모두 단순해진다.
연속 번호가 업무 요건이면 번호 발급을 외부에 맡긴다. 관계형 DB 의 시퀀스, ZooKeeper 의 원자적 카운터 등을 쓰고 적재 시 값을 받아 넣는다. 발급기가 단일 장애점이 되고 처리량 상한이 되므로, 블록 단위로 구간을 미리 받아 두고 소비하는 방식으로 완화한다.
from pyspark.sql.functions import monotonically_increasing_id
df = df.withColumn("id", monotonically_increasing_id())
monotonically_increasing_id() 는 유일하고 증가하지만 연속이 아니며 0에서 시작하지도 않는다. 파티션 번호를 상위 비트에 담기 때문에 값이 크게 벌어진다. 연속 번호가 필요하면 쓸 수 없다.
윈도 함수로 연속 번호를 만들 수도 있으나, 전체를 한 파티션으로 모으므로 대량 데이터에서는 병목이 된다.
from pyspark.sql.window import Window
from pyspark.sql.functions import row_number, lit
w = Window.orderBy(lit(1))
df = df.withColumn("seq", row_number().over(w))
Impala 로 일괄 적재하면서 번호를 붙이려면 윈도 함수를 쓴다.
INSERT INTO db.tbl
SELECT
CAST(ROW_NUMBER() OVER (ORDER BY src_key) AS BIGINT) + <기존 최대값>,
payload
FROM staging.src;
여러 세션이 동시에 돌면 겹친다. 기준값을 읽는 시점과 쓰는 시점 사이가 원자적이지 않기 때문이다. 적재를 직렬화하거나, 배치마다 겹치지 않는 구간을 미리 배정해 두어야 한다.
UpdateAttribute 로 UUID 를 붙이는 방식이 가장 부작용이 적다. 상태 기반 카운터(stateful 처리)는 노드 장애 시 값이 어긋날 수 있다.