CSV 기반 테이블에서 Kudu 로 넣을 때는 문제가 없는데, Parquet 테이블에서 같은 건수를 넣으면 일정 규모부터 Kudu 세션 버퍼가 넘쳤다는 오류가 난다. 건수를 줄이면 통과한다.
Impala 의 Kudu sink 는 행을 Kudu 클라이언트 세션의 mutation 버퍼에 쌓고, 버퍼가 차면 비동기로 flush 한다. 버퍼가 다 찼는데 이전 flush 가 끝나지 않으면 더 담을 수 없어 오류로 돌아온다. 즉 실패 지점은 Kudu 서버가 아니라 클라이언트 측 버퍼다.
Parquet 쪽이 먼저 걸리는 이유는 스캔 쪽 처리량 차이다. Parquet 은 컬럼 단위로 압축 저장되어 있어 같은 시간에 훨씬 많은 행을 풀어 낸다. 풀린 행은 Kudu 로 보내기 위해 행 단위로 다시 조립되고 곧바로 버퍼에 쌓이므로, 텍스트 파일을 한 줄씩 파싱할 때보다 버퍼가 빨리 찬다. 데이터 건수 자체가 문제가 아니라 유입 속도와 flush 속도의 차이가 문제다.
여기에 쓰기가 한쪽으로 몰리면 flush 가 더 느려진다. 특정 tablet server 가 모든 쓰기를 받으면 그 노드의 처리 속도가 전체 속도가 되기 때문이다.
Impala 쿼리 옵션으로 버퍼와 배치 크기를 조정한다.
SET KUDU_MUTATION_BUFFER_SIZE=100000000;
SET KUDU_SINK_MEM_REQUIRED=...;
KUDU_MUTATION_BUFFER_SIZE 는 Kudu sink 하나가 쓰는 버퍼 크기(바이트)이고 기본값은 10MB 수준이다. 넓은 행이나 큰 배치를 쓰면 이 값을 올린다. 다만 이 메모리는 쿼리 메모리 한도 안에서 계산되므로 무작정 키우면 admission control 에서 걸린다. (확인 필요 — 옵션의 정확한 기본값과 목록은 SET; 출력으로 확인한다.)
Kudu 클라이언트 쪽 연산 기한은 kudu_operation_timeout_ms 로 정해진다. flush 가 느려 기한을 넘기면 타임아웃으로 표면화되므로, 버퍼만 키우고 기한을 그대로 두면 오류 문구만 바뀐다.
Impala 는 Kudu 테이블에 INSERT 할 때 기본적으로 파티션 기준으로 정렬해 보낸다(/* +CLUSTERED */ 가 기본). 같은 태블릿으로 갈 행이 모여서 전달되므로 Kudu 쪽 쓰기가 순차에 가까워지고 flush 효율이 올라간다.
INSERT INTO kudu_tbl /* +CLUSTERED */
SELECT * FROM src;
/* +NOCLUSTERED */ 는 이 정렬을 끈다. 정렬 비용은 사라지지만 각 노드가 여러 태블릿에 흩어서 쓰게 되어 mutation 버퍼가 잘게 쪼개지고, 결과적으로 버퍼 압박이 커진다. 원본이 이미 파티션 키 순으로 정렬돼 있는 특수한 경우가 아니면 기본값을 유지한다.
/* +NOSHUFFLE */ 는 sink 앞의 재분배 단계를 없앤다. 스캔한 노드가 그대로 Kudu 에 쓰게 되므로 노드 간 전송은 줄지만, 원본 데이터 분포가 한쪽에 쏠려 있으면 그 노드에 쓰기가 집중된다. 반대로 /* +SHUFFLE */ 는 고르게 흩되 네트워크 전송이 늘어난다. 원본 분포를 확인하지 않은 채 NOSHUFFLE 을 붙이는 것이 가장 흔한 실수다.
가장 확실한 방법은 한 번에 넣는 범위를 쪼개는 것이다.
INSERT INTO kudu_tbl
SELECT * FROM src WHERE part_dt = '2026-09-20';
날짜나 키 범위로 끊어 넣고 사이에 여유를 주면 flush 가 따라잡는다. 적재 중에는 tablet server 의 메모리와 WAL 디스크를 함께 본다.
curl -s http://<tserver>:8050/mem-trackers | head -30
curl -s http://<tserver>:8050/maintenance-manager