Kafka 메시지를 NiFi 로 받아 Kudu 에 적재하는 파이프라인에서 Kudu 디스크가 부족해질 때의 두 가지 선택지를 비교하고, 선택한 방향의 구현 요점을 정리한다. 하나는 오래된 데이터를 Hive · Parquet 로 내려 Kudu 에는 최신 데이터만 두는 티어링이고, 다른 하나는 Kudu 를 빼고 처음부터 HDFS 에 적재하는 것이다.
갈림길은 "Kudu 의 장점을 실제로 쓰고 있는가"다. Kudu 는 실시간 upsert, 낮은 지연의 포인트 조회, 최신 데이터에 대한 실시간 분석에 강하지만 디스크를 많이 쓴다. HDFS + Parquet 은 저장 효율이 훨씬 좋지만 행 단위 갱신이 안 된다.
최신 데이터에 대해 실시간 조회나 upsert 가 필요하면 티어링이 맞고, Kudu 가 사실상 랜딩 존일 뿐이고 배치 분석만 한다면 Kudu 를 빼는 쪽이 저장 측면에서 최선이다. 후자를 택하면 upsert 와 중복 제거, 최신 데이터 실시간 조회를 배치나 애플리케이션 레벨에서 직접 처리해야 하는 비용이 생긴다.
upsert 때문에 Kudu 를 쓰고 있다면 답은 정해져 있다. 데이터를 Parquet 로 옮기는 순간 사실상 갱신이 불가능해지기 때문이다.
핵심은 날짜 기준 range 파티션을 걸고, 지난 데이터를 행 단위 DELETE 가 아니라 파티션 DROP 으로 제거하는 것이다. Kudu 에서 대량 레코드를 DELETE 하면 tombstone 과 compaction 부담이 커져 디스크가 바로 회수되지 않는다.
CREATE TABLE kudu_events (
event_date STRING,
id STRING,
event_ts TIMESTAMP,
payload STRING,
PRIMARY KEY (event_date, id, event_ts)
)
PARTITION BY HASH(id) PARTITIONS 4,
RANGE (event_date) (
PARTITION VALUE = '2026-07-27',
PARTITION VALUE = '2026-07-28'
)
STORED AS KUDU;
ALTER TABLE kudu_events ADD RANGE PARTITION VALUE = '2026-07-29';
미래 파티션을 미리 추가하지 않으면 그날 적재가 실패한다. 파티션 추가는 자동화한다.
핫과 콜드를 한 번에 조회하려면 뷰로 합친다.
CREATE VIEW events_all AS
SELECT id, event_ts, payload, event_date FROM kudu_events
UNION ALL
SELECT id, event_ts, payload, event_date FROM events_parquet;
이관의 전제는 "이 데이터에 더 이상 upsert 가 들어오지 않는다"이다. 자정에 전날 파티션을 바로 옮기고 드롭하면, 자정 이후에 들어오는 전날 데이터의 갱신을 잃는다.
따라서 기준은 "어제"가 아니라 "갱신이 끝났다고 안전하게 볼 수 있는 시점"이어야 한다. 핫 티어 보관 기간을 하루가 아니라 며칠로 잡고 그만큼의 여유를 둔다. 전날 처리가 대부분이고 늦어도 그다음 날 안에 끝난다면 2~3일치를 Kudu 에 유지하고 그보다 오래된 파티션을 대상으로 삼는다.
INSERT INTO events_parquet PARTITION (event_date='2026-07-25')
SELECT id, event_ts, payload
FROM kudu_events
WHERE event_date = '2026-07-25';
-- 건수 검증을 통과한 뒤에만 드롭한다
ALTER TABLE kudu_events DROP RANGE PARTITION VALUE = '2026-07-25';
보관 기간은 전날 데이터에 대한 갱신이 가장 늦게 들어오는 경우가 며칠까지 늘어지는지로 정한다.
늦게 도착하는 갱신을 완전히 배제할 수 없다면 두 가지 보완책이 있다. 하나는 재수화로, 해당 날짜의 Parquet 파티션을 Kudu 로 다시 불러와 upsert 한 뒤 처리가 끝나면 다시 내리는 방식이며 빈도가 낮을 때 쓴다. 다른 하나는 콜드 티어에서 INSERT OVERWRITE 로 그 파티션을 통째로 재작성하는 것이다. Parquet 은 행 단위 upsert 가 안 되지만 파티션 덮어쓰기는 가능하다.
NiFi 에서 Kudu 대신 HDFS 로 바로 내리는 흐름이며, 관건은 작은 메시지를 큰 파일로 병합한 뒤 날짜 디렉터리에 쓰는 것이다.
ConsumeKafkaRecord → MergeRecord(또는 MergeContent) → PutParquet(또는 PutHDFS)
MergeRecord 에 최소 건수, 최대 크기, 최대 대기 시간 조건을 걸어 파일 크기를 키운다. 이를 생략하면 초당 수많은 작은 파일이 생겨 NameNode 와 쿼리 성능을 함께 갉아먹는다. 출력 경로를 /data/events/event_date=${now():format('yyyy-MM-dd')}/ 형태의 파티션 디렉터리에 맞추고, 그 위에 external 테이블을 걸어 파티션을 인식시킨다. 병합을 해도 시간이 지나면 파일이 잘게 남으므로 하루 한 번 INSERT OVERWRITE 로 재작성하는 compaction 배치를 따로 둔다.