증분 적재를 반복한 Parquet 테이블은 파티션마다 수십~수백 개의 작은 파일이 쌓인다. 파일이 잘게 쪼개지면 다음이 함께 나빠진다.
Impala 에는 Hive ACID 테이블의 ALTER TABLE ... COMPACT 같은 전용 병합 명령이 없다. 데이터를 다시 쓰는 것이 유일한 방법이다.
Impala 의 INSERT 는 데이터 파일 크기에 맞춘 HDFS 블록으로 쓰며, 파일 하나가 블록 하나가 되게 한다. 기본 목표 크기는 PARQUET_FILE_SIZE 쿼리 옵션으로 정해지고 기본값은 256MB 다. 문제는 쓰는 노드마다 파일을 따로 만든다는 점이다. 임팔라 데몬 10대가 참여하면 파티션 하나에 파일이 최소 10개 생긴다. 여기에 적재를 하루 24번 돌리면 파티션당 240개가 된다.
동적 파티션 INSERT 는 더 나쁘다. 노드 × 파티션 조합마다 파일이 생긴다.
가장 단순하고 확실한 방법이다. 대상 파티션을 스스로 읽어 스스로 덮어쓴다.
SET PARQUET_FILE_SIZE=256m;
SET NUM_NODES=1;
INSERT OVERWRITE TABLE db.tbl PARTITION (dt='2026-09-19')
SELECT c1, c2, c3 FROM db.tbl WHERE dt='2026-09-19';
SET NUM_NODES=0;
REFRESH db.tbl PARTITION (dt='2026-09-19');
COMPUTE INCREMENTAL STATS db.tbl PARTITION (dt='2026-09-19');
NUM_NODES=1 은 쓰기를 코디네이터 한 대로 몰아 파일을 하나로 만든다. 병합의 핵심이 이 옵션이다. 다만 한 대가 모든 데이터를 쓰므로 파티션이 수십 GB 면 그 노드가 병목이 되고 메모리 한도에 걸릴 수 있다. 작업이 끝나면 0(자동)으로 되돌린다.
PARTITION 절에 상수를 지정하는 정적 파티션 형태로 쓴다. 동적 파티션으로 전체를 한 번에 덮으면 파일 수가 다시 늘어난다.
주의할 점은 다음과 같다.
INSERT OVERWRITE 대상 파티션의 기존 파일을 쿼리가 끝난 뒤 지운다. 그래도 작업 중 장애가 나면 파티션이 비거나 중복될 수 있으므로, 중요한 테이블은 스테이징 테이블을 거치거나 사전에 파일을 백업한다PARTITION 절을 빼면 테이블 전체가 날아간다REFRESH 를 빠뜨리면 다른 코디네이터가 지워진 파일을 계속 참조해 File does not exist 로 실패한다원본을 직접 건드리지 않아 되돌리기 쉽다.
CREATE TABLE db.tbl_compact LIKE db.tbl STORED AS PARQUET;
SET NUM_NODES=1;
INSERT OVERWRITE TABLE db.tbl_compact PARTITION (dt)
SELECT c1, c2, c3, dt FROM db.tbl WHERE dt BETWEEN '2026-09-01' AND '2026-09-19';
SET NUM_NODES=0;
-- 검증 후 교체
SELECT count(*) FROM db.tbl WHERE dt BETWEEN '2026-09-01' AND '2026-09-19';
SELECT count(*) FROM db.tbl_compact;
행 수가 맞으면 원본 파티션을 INSERT OVERWRITE 로 되돌려 넣거나, HDFS 경로를 직접 교체한다. 경로를 교체한 경우 반드시 REFRESH 를 돌린다.
LIKE 로 만든 테이블은 파티션 정의만 복사하고 데이터와 통계는 가져오지 않는다. 교체 후 통계를 다시 계산한다.
파일 크기와 개수를 정확히 통제해야 하면 Spark 가 낫다. Impala 의 NUM_NODES=1 과 달리 병렬로 읽으면서 출력 파일 수만 줄일 수 있다.
df = spark.read.parquet("hdfs:///warehouse/db.db/tbl/dt=2026-09-19")
(df.coalesce(4)
.write.mode("overwrite")
.option("compression", "snappy")
.parquet("hdfs:///tmp/tbl_compact/dt=2026-09-19"))
coalesce(n) 는 셔플 없이 파티션을 합치므로 빠르지만 데이터가 고르게 나뉘지 않는다. 균등하게 나눠야 하면 repartition(n) 을 쓴다 — 셔플이 일어나 느린 대신 파일 크기가 고르다.
목표 파일 수는 파티션 크기 ÷ 256MB 로 잡는다. 압축 후 크기 기준이므로 실제 파일은 목표보다 작게 나온다.
Spark 가 쓴 파일을 Impala 가 읽으려면 REFRESH 가 필요하다. Spark 의 Parquet 타임스탬프 처리와 Impala 의 해석이 어긋나 시각이 틀어지는 사례가 있으므로, 교체 전 타임스탬프 컬럼을 표본 비교한다.
NiFi 의 MergeContent 는 바이트를 이어 붙이는 처리라 Parquet 파일을 행 단위로 병합하지 못한다. 이어 붙인 결과는 푸터가 여러 개인 깨진 파일이 되어 Impala · Hive 가 읽지 못한다.
NiFi 를 쓰려면 병합 자체가 아니라 병합 작업을 부르는 역할로 둔다.
ListHDFS 로 파티션의 파일 수를 세어 임계치를 넘으면 트리거ExecuteStreamCommand 로 spark-submit 실행, 또는 PutSQL · ExecuteSQL 로 Impala JDBC 에 INSERT OVERWRITE 전송REFRESH · COMPUTE INCREMENTAL STATS 를 이어서 실행애초에 작은 파일을 만들지 않는 편이 더 낫다. NiFi 로 적재한다면 MergeRecord 로 레코드를 모아 목표 크기에 도달했을 때 한 번에 PutParquet · PutHDFS 하도록 흐름을 짠다.
hive.merge.* 설정으로 처리하는 방법.