Trino 를 경유해 Oracle 원천 데이터를 Kudu 테이블로 옮길 때 부딪히는 것들을 정리한다. Oracle 전용 함수 변환, 환경 간 복사 순서, Kudu 파티션 DDL 이 대상이다. 타입 변환 실패와 배치 적용 실패 오류의 해석은 아래 트러블슈팅 절의 링크를 본다.
Trino 에는 DECODE 가 없으므로 CASE 로 바꾼다.
-- Oracle
DECODE(status, 'A', '활성', 'I', '비활성', '알수없음')
-- Trino
CASE status
WHEN 'A' THEN '활성'
WHEN 'I' THEN '비활성'
ELSE '알수없음'
END
주의할 차이가 둘 있다. Oracle DECODE 는 NULL = NULL 을 같다고 보지만 Trino 의 simple CASE 는 그렇지 않으므로, NULL 매칭이 있으면 searched CASE 를 쓴다. 또 DECODE 는 첫 번째 결과값 기준으로 타입을 암묵 변환하지만 Trino CASE 는 모든 THEN 결과 타입이 같아야 해서 CAST 가 필요할 수 있다.
-- Oracle: DECODE(col, NULL, 'N/A', col)
CASE WHEN col IS NULL THEN 'N/A' ELSE col END
-- 또는
COALESCE(col, 'N/A')
인자가 두 개뿐인 DECODE(expr, default) 는 search/result 쌍이 없어 항상 두 번째 인자를 돌려주므로 그 인자만 남기면 된다. 값 하나를 다른 값으로 합치는 형태는 그대로 CASE 로 옮긴다.
-- Oracle: decode(coalesce(a.col, b.col), 'NA_SEA', 'SEA', coalesce(a.col, b.col))
CASE coalesce(a.col, b.col)
WHEN 'NA_SEA' THEN 'SEA'
ELSE coalesce(a.col, b.col)
END AS col
이때 replace 로 줄여 쓰지 않는다. 대상 문자열을 부분 문자열로 포함하는 다른 값까지 바뀐다.
Oracle 전용 함수가 여럿 섞여 쿼리 변환이 부담스러우면 Oracle 커넥터의 query 테이블 함수로 원문을 그대로 넘길 수 있다. 커넥터에서 해당 함수 사용이 허용돼 있어야 한다.
INSERT INTO kudu.schema.target
SELECT * FROM TABLE(
oracle.system.query(
query => 'SELECT id, DECODE(status, ''A'', ''활성'', ''I'', ''비활성'', ''기타'') AS status_nm FROM owner.src'
)
);
QA 와 DEV 두 환경에 같은 데이터를 넣어야 한다면, Oracle 을 두 번 읽지 말고 QA Kudu 에서 DEV Kudu 로 복사한다.
INSERT INTO qa_kudu.schema.tbl SELECT ... FROM oracle... 을 실행한다.INSERT INTO dev_kudu.schema.tbl SELECT * FROM qa_kudu.schema.tbl 을 실행한다.Oracle 커넥터는 JDBC 라 기본적으로 단일 커넥션으로 읽어 대용량에서 병목이 되고, 조인과 함수 연산도 매번 다시 수행된다. Kudu → Kudu 는 태블릿 단위로 split 이 나뉘어 워커가 병렬로 읽고 쓰며 변환 연산도 없다. 운영에 가까운 원천에 두 번 부하를 주지 않아도 되고, 두 환경이 정확히 같은 스냅샷을 갖게 되어 비교·검증이 쉬우며, 타입 변환이 이미 끝난 상태라 새로운 변환 오류가 나지 않는다.
다만 QA 에 잘못 들어간 데이터가 있으면 그대로 복제되므로 QA 검증이 먼저다. DEV Trino 에 QA Kudu 카탈로그가 잡혀 있는지 SHOW CATALOGS 로 확인하고, 없으면 카탈로그 추가나 네트워크 개방이 선행돼야 한다. SELECT * 를 쓰려면 양쪽 컬럼 순서와 타입이 같아야 하며, 조금이라도 다르면 컬럼을 명시한다.
Trino Kudu 커넥터에서 range 파티션 컬럼은 반드시 PK 에 포함돼야 하고, 경계값은 range_partitions 에 JSON 문자열로 준다.
CREATE TABLE kudu.schema.tbl (
yr INTEGER WITH (primary_key = true),
id DECIMAL(22,4) WITH (primary_key = true),
amt DECIMAL(22,4)
)
WITH (
partition_by_hash_columns = ARRAY['id'],
partition_by_hash_buckets = 8,
partition_by_range_columns = ARRAY['yr'],
range_partitions = '[
{"lower": 2024, "upper": 2025},
{"lower": 2025, "upper": 2026},
{"lower": 2026, "upper": 2027}
]'
);
lower 는 포함, upper 는 미포함이며 null 은 열린 구간이다. 파티션 추가·삭제는 프로시저로 한다.
CALL kudu.system.add_range_partition('schema', 'tbl', '{"lower": 2027, "upper": 2028}');
CALL kudu.system.drop_range_partition('schema', 'tbl', '{"lower": null, "upper": 100000}');
DECIMAL 경계값에서 파싱 에러가 나면 "2026.0000" 처럼 문자열로 넣어 본다. 커넥터 버전에 따라 DECIMAL 은 문자열 쪽을 더 잘 받는다. 파티션이 없는 구간의 데이터를 INSERT 하면 에러가 나므로 이관 전에 SELECT DISTINCT yr 로 원천의 범위를 확인해 파티션을 미리 만든다.
SELECT col, count(*) ... GROUP BY col 로 분포를 비교한다.