Spark 잡에서 파티션 테이블에 적재할 때 다음과 같이 끝난다.
org.apache.spark.SparkException: Job aborted.
Caused by: org.apache.hadoop.hive.ql.metadata.HiveException:
Number of dynamic partitions created is 3366, which is more than 1000.
To solve this try to set hive.exec.max.dynamic.partitions to at least 3366.
같은 SQL 을 Hive 나 beeline 에서 돌리면 통과하는데 Spark 에서만 막히는 경우가 많다. hive-site.xml 에 한도를 올려 두었는데도 그렇다.
동적 파티션 관련 속성은 이름이 서로 닮아 잘못 적기 쉽다. 잘못된 이름을 설정하면 Hive 는 조용히 무시하므로, 값을 바꿨는데 아무 변화가 없는 상태가 된다.
| 속성 | 기본값 | 뜻 |
|---|---|---|
hive.exec.dynamic.partition |
true | 동적 파티션 사용 여부 |
hive.exec.dynamic.partition.mode |
strict | strict 는 파티션 컬럼 중 하나 이상을 정적으로 지정하도록 요구한다. 전부 동적으로 쓰려면 nonstrict |
hive.exec.max.dynamic.partitions |
1000 | 한 문장이 만들 수 있는 파티션 총수 |
hive.exec.max.dynamic.partitions.pernode |
100 | 노드 하나가 만들 수 있는 파티션 수 |
hive.exec.dynamic.partition.max 라는 속성은 없다. 오류 메시지에 나오는 이름은 hive.exec.max.dynamic.partitions 이므로 그것을 그대로 쓴다. 총수만 올리고 pernode 를 그대로 두면 노드 쪽 한도에서 다시 막히므로 둘을 함께 본다.
Spark 는 실행할 때 클래스패스에서 hive-site.xml 을 찾는다. 찾지 못하면 Hive 기본값으로 동작하므로, Hive 쪽에서 아무리 값을 올려 두어도 반영되지 않는다. 확인할 자리는 셋이다.
# 1. Spark conf 에 파일이 있는가
ls -l $SPARK_HOME/conf/hive-site.xml
# 2. 드라이버가 실제로 무슨 값을 보고 있는가
spark-sql -e "SET hive.exec.max.dynamic.partitions;"
없으면 Hive 쪽 파일을 Spark conf 로 연결한다. 클러스터의 모든 노드에 같은 파일이 있어야 한다.
ln -s /etc/hive/conf/hive-site.xml $SPARK_HOME/conf/hive-site.xml
YARN 클러스터 모드에서는 드라이버가 컨테이너 안에서 뜨므로 제출 시점에 파일을 함께 올린다.
spark-submit --master yarn --deploy-mode cluster \
--files /etc/hive/conf/hive-site.xml \
job.py
Cloudera 배포판처럼 게이트웨이 역할이 배포돼 있는 환경이라면, 해당 호스트에 Hive 게이트웨이와 Spark 게이트웨이가 모두 배포돼 있는지부터 확인한다. 게이트웨이가 빠진 노드에서 제출하면 설정 파일 자체가 없다.
파일 배포를 손대기 어려우면 잡에서 값을 넣는다. 제출 시점에 넘길 때는 spark.hadoop. 접두사를 붙여야 Hadoop · Hive 설정으로 전달된다. spark.sql.hive. 로 시작하는 이름을 만들어 붙이면 Spark 가 모르는 설정이 되어 아무 일도 일어나지 않는다.
spark-submit \
--conf spark.hadoop.hive.exec.dynamic.partition=true \
--conf spark.hadoop.hive.exec.dynamic.partition.mode=nonstrict \
--conf spark.hadoop.hive.exec.max.dynamic.partitions=4000 \
--conf spark.hadoop.hive.exec.max.dynamic.partitions.pernode=1000 \
job.py
코드 안에서 세션 단위로 거는 방법도 있다. 세션이 살아 있는 동안만 유효하다.
spark.sql("SET hive.exec.dynamic.partition.mode=nonstrict")
spark.sql("SET hive.exec.max.dynamic.partitions=4000")
spark.sql("SET hive.exec.max.dynamic.partitions.pernode=1000")
3천 개가 넘는 파티션이 한 번에 만들어진다면 파티션 컬럼 선택이 잘못됐을 가능성이 높다. 한도를 올려 통과시키면 그 자리는 넘어가지만 메타스토어에 파티션이 계속 쌓이고, 나중에 조회할 때 파티션 목록을 읽는 것만으로 시간이 걸린다.
-- 파티션이 많은 테이블부터 확인한다 (메타스토어가 MySQL·MariaDB 인 경우)
SELECT d.NAME, t.TBL_NAME, COUNT(*) AS part_cnt
FROM PARTITIONS p
JOIN TBLS t ON p.TBL_ID = t.TBL_ID
JOIN DBS d ON t.DB_ID = d.DB_ID
GROUP BY d.NAME, t.TBL_NAME
ORDER BY part_cnt DESC
LIMIT 20;
카디널리티가 높은 컬럼을 파티션 키로 쓰고 있다면 파티션 대신 버킷이나 정렬로 바꾸는 편이 낫다. 날짜를 일 단위로 자르고 있는데 파티션이 수천 개라면 보존 기간을 다시 본다.
INSERT OVERWRITE 로 일부 파티션만 갈아 끼우려는 의도였다면 Spark 쪽 동작을 하나 더 확인한다. 기본값인 static 모드에서는 대상 테이블의 파티션 전체가 지워지고 새로 쓰인다.
spark.conf.set("spark.sql.sources.partitionOverwriteMode", "dynamic")
dynamic 으로 두면 이번 결과에 들어 있는 파티션만 교체한다. 재적재 배치에서 과거 파티션이 통째로 사라지는 사고는 대부분 이 설정에서 비롯된다.