Impala 에 없는 함수를 UDF 로 채워 넣는 작업이다. 여기서 반복적으로 부딪히는 문제가 셋 있다 — 재시작하면 함수가 사라지는 것, TIMESTAMP 를 인자로 받을 수 없는 것, 그리고 빌드 의존성이다.
Impala 의 CREATE FUNCTION 에는 두 가지 형태가 있고, 영속성이 다르다.
옛 형태는 인자 타입과 반환 타입을 SQL 에 직접 적는다. 이 방식으로 만든 Java UDF 는 catalogd 메모리에만 남고 메타스토어에 기록되지 않는다. catalogd 나 Impala 를 재시작하면 함수가 사라진다.
-- 비영속: catalogd 재시작 시 소멸
CREATE FUNCTION db.fn(STRING) RETURNS STRING
LOCATION 'hdfs:///user/udf/myudf.jar'
SYMBOL='com.example.MyUdf';
새 형태는 시그니처를 적지 않는다. Impala 가 JAR 을 열어 클래스의 evaluate 메서드를 읽고, 오버로드된 모든 시그니처를 한 번에 등록한다. 이 방식은 Hive Metastore 에 저장되어 재시작 후에도 남고, Hive 와 메타데이터를 공유한다.
-- 영속: 메타스토어에 저장된다
CREATE FUNCTION db.fn
LOCATION 'hdfs:///user/udf/myudf.jar'
SYMBOL='com.example.MyUdf';
이미 옛 형태로 만들어 둔 함수가 있으면 먼저 지우고 다시 만든다.
SHOW FUNCTIONS IN db;
SHOW CREATE FUNCTION db.fn;
DROP FUNCTION db.fn(STRING);
CREATE FUNCTION db.fn LOCATION 'hdfs:///user/udf/myudf.jar' SYMBOL='com.example.MyUdf';
C++ UDF 는 두 형태 모두 메타스토어에 저장되므로 이 문제가 없다.
LOCATION 은 모든 impalad 가 읽을 수 있는 경로여야 한다. 로컬 파일 경로를 주면 그 노드에서만 동작하고 다른 노드의 fragment 에서 실패한다. HDFS 나 S3 같은 공유 저장소에 올린다.
hdfs dfs -mkdir -p /user/udf
hdfs dfs -put -f myudf.jar /user/udf/myudf.jar
hdfs dfs -chmod 644 /user/udf/myudf.jar
JAR 내용을 바꿔 같은 경로에 덮어썼다면 impalad 는 캐시된 것을 계속 쓴다. 함수를 DROP 후 다시 CREATE 하거나, 파일 이름에 버전을 붙여 새 경로로 올린다.
다른 코디네이터나 Hive 쪽에서 등록한 함수가 보이지 않으면 메타데이터를 새로 읽는다.
REFRESH FUNCTIONS db;
Java UDF 에 TIMESTAMP 를 주면 이 오류가 난다.
Type timestamp is not supported for Java UDFs.
Impala 의 Java UDF 인터페이스는 Hive 의 UDF 클래스를 재사용하는데, 타입 매핑이 기본형과 문자열 위주로 제한돼 있다. TIMESTAMP · DECIMAL · DATE 같은 타입은 직접 받을 수 없다. 해결은 둘 중 하나다.
iso_week(CAST(ts AS STRING)) 형태로 호출하고 UDF 안에서 파싱한다. 형식이 섞여 들어올 수 있으므로 길이나 구분자로 분기한다.iso_week(unix_timestamp(ts)) 로 호출한다. 파싱 비용이 없고 타임존 처리가 명확해 대량 처리에는 이쪽이 낫다. 다만 Impala 의 unix_timestamp() 는 인자를 UTC 로 해석하므로 로컬 타임존 기준이 필요하면 to_utc_timestamp 를 함께 쓴다.타입 제약이 걸리면 C++ UDF 로 가는 선택지도 있다. C++ UDF 는 TIMESTAMP · DECIMAL 을 직접 다루고 성능도 낫지만, 빌드 환경(Impala UDF 개발 헤더)을 갖춰야 한다.
Impala 에는 weekofyear() 가 있고 이 함수의 주차 계산은 ISO-8601 규칙(월요일 시작, 1월 4일이 포함된 주가 1주차)을 따른다. 따라서 주차 번호만 필요하면 UDF 가 필요 없다.
UDF 가 필요해지는 지점은 ISO 연도다. 12월 29~31일이 다음 해 1주차에 속하거나 1월 1~3일이 전년 52 · 53주차에 속하는 경계에서, year() 와 ISO 연도가 달라진다. 2026-W01 같은 문자열을 만들려면 ISO 연도를 따로 계산해야 한다.
순수 SQL 로도 가능하다. ISO 주의 목요일이 속한 해가 ISO 연도라는 성질을 쓴다.
SELECT
concat(
cast(year(days_add(d, 4 - pmod(dayofweek(d) + 5, 7) - 1)) AS STRING),
'-W',
lpad(cast(weekofyear(d) AS STRING), 2, '0')
) AS iso_week
FROM (SELECT CAST('2026-12-30' AS TIMESTAMP) AS d) t;
확인 필요 — Impala 의 dayofweek() 는 일요일이 1이다. 위 식은 그 전제로 월요일 기준 오프셋을 맞춘 것이므로, 도입 전에 연말연초 경계 날짜 몇 개로 직접 검증한다.
Hive 의 UDF 를 상속하고 evaluate 를 구현한다. 입력이 여러 형태로 들어올 수 있으면 오버로드로 받되, 새 문법으로 등록하면 오버로드가 모두 함께 등록된다.
package com.example.udf;
import org.apache.hadoop.hive.ql.exec.UDF;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.IsoFields;
import java.time.temporal.WeekFields;
public class IsoWeek extends UDF {
public String evaluate(String value) {
if (value == null || value.length() < 10) {
return null;
}
try {
LocalDate d = LocalDate.parse(value.substring(0, 10),
DateTimeFormatter.ISO_LOCAL_DATE);
int week = d.get(WeekFields.ISO.weekOfWeekBasedYear());
int year = d.get(IsoFields.WEEK_BASED_YEAR);
return String.format("%04d-W%02d", year, week);
} catch (Exception e) {
return null;
}
}
public String evaluate(Long epochSeconds) {
if (epochSeconds == null) {
return null;
}
return evaluate(java.time.Instant.ofEpochSecond(epochSeconds)
.atZone(java.time.ZoneOffset.UTC).toLocalDate().toString());
}
}
java.util.Calendar 대신 java.time 을 쓴다. Calendar 로 ISO 주차를 계산하려면 setFirstDayOfWeek(MONDAY) 와 setMinimalDaysInFirstWeek(4) 를 매번 지정해야 하고, SimpleDateFormat 은 스레드 안전하지 않아 정적 필드로 공유하면 UDF 가 동시 호출될 때 값이 깨진다. java.time 은 불변이라 그런 함정이 없다.
Maven 으로 빌드할 때 의존성은 hive-exec 하나면 충분하고, provided 로 잡아 JAR 에 포함하지 않는다. 포함하면 JAR 이 수십 MB 로 커지고 클러스터의 Hive 라이브러리와 충돌한다.
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>${hive.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
hive-exec 은 의존성이 매우 많은 아티팩트라 그대로 받으면 asm · javadoc 다운로드 실패 같은 잡음이 따라온다. 위처럼 전이 의존성을 모두 제외하면 컴파일에 필요한 클래스만 남는다. ClassNotFoundException: TimestampWritableV2 같은 오류가 빌드 시점에 나면 hive-exec 버전이 클러스터와 다른 것이므로 클러스터의 Hive 버전에 맞춘다.
maven-compiler-plugin 의 source · release 는 클러스터의 JVM 보다 높지 않게 맞춘다. JDK 17 로 빌드한 클래스를 JDK 11 런타임에 올리면 UnsupportedClassVersionError 가 난다.
mvn -q clean package
hdfs dfs -put -f target/myudf.jar /user/udf/myudf.jar
SHOW FUNCTIONS IN db;
SELECT db.iso_week('2026-12-30');
SHOW FUNCTIONS IN db 는 사용자 정의 함수만 보여 준다. 내장 함수가 안 보인다고 해서 문제가 있는 것이 아니다. 내장 함수 목록은 SHOW FUNCTIONS 를 데이터베이스 없이 실행하거나 SELECT now(), weekofyear(now()) 처럼 직접 호출해 확인한다.
REFRESH 와 INVALIDATE METADATA 의 차이.