Apache DataFusion 54.0.0: LATERAL 조인·SQL 람다 내장·arrow-avro·Parquet CDC로 Rust 쿼리 엔진의 SQL 경계를 다시 그은 방법
왜 지금 봐야 하나
Apache DataFusion은 사용자가 직접 만지는 데이터베이스가 아니다. Rust로 작성된 임베디드 SQL 쿼리 엔진으로, RisingWave·Ballista·GreptimeDB·LanceDB·Comet 등 수십 개 프로젝트가 실행 레이어로 채택하고 있다. 이 계층 하나를 올리면 그 위에 있는 모든 시스템이 동시에 새 SQL 기능을 얻는다.
54.0.0(2026년 6월 12일 출시)은 그런 의미에서 영향 반경이 넓은 릴리스다. LATERAL 조인과 SQL 람다 함수라는 두 가지 표현력 확장이 들어왔다. 이전까지는 "LATERAL이 없어서" 또는 "람다를 Rust UDF로 등록해야 해서" DataFusion 위에서 특정 쿼리 패턴을 포기했다면, 54부터는 그 이유가 사라진다.
스토리지 측에서는 arrow-avro 크레이트 기반으로 Avro 리더를 교체했고, Parquet 콘텐츠 정의 청킹(CDC)을 추가해 증분 저장과 중복 제거 효율을 높였다. 플래닝·조인·스캔 성능도 전반적으로 개선됐다. 11주간 740개 커밋, 139명의 기여자가 만든 이 릴리스를 운영자 관점에서 분해한다.
배경: DataFusion이 쿼리 엔진 생태계에서 차지하는 위치
DataFusion은 Apache Arrow를 데이터 교환 형식으로 삼고, 그 위에 SQL 파서·플래너·옵티마이저·실행 엔진을 쌓는다. 두 가지 방식으로 사용된다.
첫 번째는 라이브러리 모드다. 프로젝트가 DataFusion 크레이트를 의존성으로 추가하고 SQL 실행 기능을 내장한다. RisingWave의 배치 실행 경로, Ballista의 노드 로컬 플랜 실행, GreptimeDB의 쿼리 레이어가 이 방식을 쓴다.
두 번째는 독립 실행 모드다. datafusion-cli 또는 datafusion 바이너리로 CSV·Parquet·JSON·Avro를 직접 쿼리한다. 파이프라인 디버깅이나 데이터 탐색에 자주 사용한다.
버전 번호가 빠르게 올라가는 것은 SemVer 엄수 정책 때문이다. 공개 Rust API 변경이 생기면 메이저 버전을 올린다. 기능이 너무 크거나 너무 작아서가 아니라, API 계약을 지키기 위한 선택이다. 매 버전 Upgrade Guide 문서가 함께 출판된다.
LATERAL 조인: 앞 테이블 컬럼을 서브쿼리에서 참조하기
문제: 상관 서브쿼리의 구문적 표현 한계
표준 SQL의 FROM 절 서브쿼리는 앞에 나온 테이블을 참조할 수 없다. 다음 쿼리는 53까지는 실패했다.
SELECT u.id, top_events.event_type
FROM users u
CROSS JOIN LATERAL (
SELECT event_type
FROM events e
WHERE e.user_id = u.id
ORDER BY e.ts DESC
LIMIT 3
) top_events;e.user_id = u.id 조건이 외부 테이블(u)을 참조하기 때문이다. 같은 결과를 내려면 윈도 함수를 쓰거나 애플리케이션 레이어에서 루프를 돌아야 했다.
54의 해결: CROSS / INNER / LEFT JOIN LATERAL
DataFusion 54는 세 형태를 지원한다.
| 구문 | 매칭 없을 때 동작 |
|---|---|
CROSS JOIN LATERAL | 서브쿼리 결과 0행이면 조인 결과도 0행 |
INNER JOIN LATERAL | 동일 |
LEFT JOIN LATERAL | 서브쿼리 결과 0행이면 NULL 패딩한 1행 유지 |
-- 사용자별 최신 이벤트 3개 추출 (CROSS JOIN LATERAL)
SELECT u.name, e.event_type, e.ts
FROM users u
CROSS JOIN LATERAL (
SELECT event_type, ts
FROM events
WHERE user_id = u.id
ORDER BY ts DESC
LIMIT 3
) e;
-- 이벤트가 없는 사용자도 포함 (LEFT JOIN LATERAL)
SELECT u.name, COALESCE(e.event_type, 'no-event') AS last_event
FROM users u
LEFT JOIN LATERAL (
SELECT event_type
FROM events
WHERE user_id = u.id
ORDER BY ts DESC
LIMIT 1
) e ON true;옵티마이저의 비상관화 처리
LATERAL은 내부적으로 비상관화(decorrelation) 단계를 거친다. 가능한 경우 상관 서브쿼리를 조인으로 풀어낸다. 집계 없이 단순 필터·정렬·LIMIT만 있는 패턴은 대부분 효율적인 Nested Loop Join 또는 Hash Join으로 전환된다. 비상관화가 불가능한 복잡한 패턴은 행별 서브쿼리 실행으로 처리된다.
SQL 람다·고차 배열 함수: Rust UDF 없이 인라인 처리
이전 방식의 불편함
배열 원소마다 변환을 적용하려면 DataFusion에 Rust UDF를 등록해야 했다.
// 53까지: Rust 코드로 UDF 작성 후 컨텍스트에 등록
let double_fn = Arc::new(ScalarUDF::new(
"double_elem",
...
));
ctx.register_udf(double_fn);프로토타입이나 임시 분석에 UDF 빌드·등록 과정이 과했다.
54의 해결: x -> expr 람다 구문
-- array_transform: 원소 변환
SELECT array_transform([1, 2, 3, 4, 5], x -> x * 10);
-- 결과: [10, 20, 30, 40, 50]
-- array_filter: 조건 필터
SELECT array_filter([1, -2, 3, -4, 5], x -> x > 0);
-- 결과: [1, 3, 5]
-- array_any_match: 존재 확인
SELECT array_any_match(['apple', 'banana', 'cherry'], x -> x = 'banana');
-- 결과: true
-- 컬럼과 함께 사용
SELECT
user_id,
array_transform(scores, s -> s * 1.1) AS boosted_scores,
array_filter(tags, t -> t != 'deprecated') AS active_tags
FROM user_stats;람다 표현식 x -> expr에서 x는 현재 원소를 바인딩한다. 외부 컬럼도 캡처할 수 있다.
-- 외부 컬럼 캡처: threshold는 행의 컬럼 값
SELECT
id,
array_filter(values, v -> v >= threshold) AS above_threshold
FROM measurements;현재 구현은 단항 람다(x -> expr)를 지원하고, 중첩 람다는 이후 릴리스에서 확장 예정이다.
새 Avro 리더: arrow-avro 크레이트로 교체
DataFusion은 53까지 서드파티 avro-rs 크레이트를 써서 Avro를 읽었다. 두 가지 문제가 있었다. 하나는 Arrow 타입 시스템과 완전히 맞지 않는 변환 레이어가 필요하다는 것, 다른 하나는 Arrow 생태계가 독자적인 Avro 구현인 arrow-avro를 공식으로 내놓으면서 중복 유지 비용이 커진 것이다.
54에서 arrow-avro 크레이트로 교체했다. 세 가지가 달라진다.
첫째, Arrow 컬럼 타입 직접 매핑. Avro의 Union·Record·Enum·Fixed가 Arrow 타입 시스템으로 손실 없이 변환된다.
둘째, 논리 타입 지원 향상. Avro logical type인 timestamp-micros, date, decimal이 Arrow의 해당 타입(TimestampMicrosecond, Date32, Decimal128)으로 정확히 매핑된다.
셋째, 호환성 범위 확장. Avro 1.12 사양까지 커버한다.
기존 Avro 파일을 읽는 코드는 별도 변경 없이 그대로 동작한다. 변환 결과 타입이 달라질 수 있으므로 스키마 확인 후 운용하는 것이 안전하다.
Parquet 콘텐츠 정의 청킹(CDC): 증분 저장 효율 개선
고정 행 수 청킹의 한계
기존 Parquet 쓰기는 행 수 기준으로 페이지를 나눈다. 같은 데이터를 살짝 다른 삽입 순서로 쓰면 페이지 경계가 달라지고, 블록 레벨 중복 제거기가 같은 데이터를 새 데이터로 인식한다. 증분 백업이나 오브젝트 스토리지 중복 제거 효율이 나빠지는 이유다.
CDC의 접근: 데이터 내용으로 경계 결정
콘텐츠 정의 청킹(CDC)은 데이터 내용의 해시를 보고 경계를 결정한다. 같은 데이터 블록은 어느 테이블에서 어떤 순서로 왔든 같은 경계에서 잘린다. 결과적으로:
- 오브젝트 스토리지에 저장할 때 중복 청크 업로드를 건너뛸 수 있다
- 증분 백업 크기가 실제 변경분과 더 가깝게 수렴한다
- Parquet 파일 버전 간 diff가 페이지 단위로 의미있게 된다
CDC는 Parquet WriterProperties에서 활성화할 수 있다.
let props = WriterProperties::builder()
.set_content_defined_chunking(true)
.set_max_row_group_size(1_000_000)
.build();운영 주의사항: CDC는 쓰기 CPU를 약간 더 사용한다. 트랜잭션이 많고 파일 크기가 작은 OLTP 패턴보다, 대규모 배치 쓰기와 장기 보관이 많은 환경에서 효과가 크다.
성능 개선: 조인·스캔·플래닝
조인 성능
해시 조인의 빌드 단계(probe 테이블 해시 테이블 구성)와 프로브 단계 모두 내부 개선이 있었다. 정확한 수치는 워크로드에 따라 다르지만, 카디널리티가 높은 조인에서 두드러진다.
파티션드 해시 조인의 리파티셔닝 비용도 줄었다. 양쪽 테이블의 파티션 수가 맞지 않을 때 발생하는 오버헤드다.
스캔 성능
Parquet 스캔의 컬럼 프루닝과 로우 그룹 필터링이 개선됐다. 통계 기반 스킵이 더 적극적으로 적용된다. datafusion-cli로 빠른 효과를 확인하려면 EXPLAIN ANALYZE SELECT ...로 rows_pruned 수치를 보면 된다.
플래닝 성능
대형 스키마(컬럼 수 수백~수천)에서 플래닝 시간이 줄었다. 플래너가 타입 추론과 표현식 확인을 반복하는 과정에서 불필요한 복제를 줄였다.
아키텍처: DataFusion 54 쿼리 경로
LATERAL·람다 파싱
LATERAL 비상관화 ↓ Optimizer
람다 인라인 확장
Hash/NL Join 선택 ↓ Partition 결정
파티션드 해시조인
CDC 청킹, 로우그룹 프루닝 ↓ Avro 리더
arrow-avro 크레이트
호환성 경계와 주요 변경 사항
DataFusion은 Rust 공개 API 변경마다 메이저 버전을 올린다. 54에서 주의할 지점은 두 가지다.
AggregateFunctionExpr::human_display() 반환 타입 변경
// 53까지
fn human_display(&self) -> &str { ... }
// 54부터
fn human_display(&self) -> Option<&str> { ... }커스텀 실행 플랜을 작성하거나 집계 표현식을 직접 다루는 경우 None 분기를 추가해야 한다. 대부분은 name()으로 폴백한다.
let display = expr.human_display().unwrap_or_else(|| expr.name());create_aggregate_expr 함수 deprecated
create_aggregate_expr_with_name_and_maybe_filter와 create_aggregate_expr_and_maybe_filter가 deprecated됐다. 대체 API는 LoweredAggregateBuilder다.
// 54부터 권장 방식
let agg = LoweredAggregateBuilder::new(aggregate_function)
.with_filter(filter_expr)
.build()?;datafusion-spark 크레이트 함수 추가
Spark 호환 함수 크레이트(datafusion-spark)에 round, floor, ceil, soundex, xxhash64, array_contains, array_compact, 타임스탬프 캐스트, UTF-8 검증 함수가 추가됐다. DataFusion 위에서 Spark SQL 호환 레이어를 유지하는 프로젝트는 이 크레이트를 업데이트하면 별도 구현 없이 함수를 얻는다.
운영 체크리스트
DataFusion을 라이브러리로 사용하는 프로젝트의 54 마이그레이션 기준이다.
- [ ]
Cargo.toml에서datafusion의존성을 54로 올린 후cargo check로 컴파일 오류 확인 - [ ]
AggregateFunctionExpr::human_display()호출부를 검색해Option<&str>처리 추가 - [ ] deprecated
create_aggregate_expr_*함수 사용 여부 확인 →LoweredAggregateBuilder로 교체 - [ ] Avro 파일 읽는 경로가 있다면 변환 결과 컬럼 타입이 예상과 맞는지 스키마 단위로 검증
- [ ] Parquet CDC 활성화 여부 결정: 대규모 배치 쓰기 환경이면
set_content_defined_chunking(true)검토 - [ ] LATERAL 조인 도입 시
EXPLAIN으로 비상관화 결과 확인 (Nested Loop vs Hash Join 선택) - [ ] 람다 함수 사용 후
EXPLAIN ANALYZE로array_transform/array_filter실행 비용 확인 - [ ] 대형 스키마(컬럼 수 수백 이상)에서 플래닝 시간 프로파일링으로 개선 전후 비교
- [ ]
datafusion-spark크레이트 사용 중이면 새 함수 목록 확인 후 커스텀 구현 중복 제거 - [ ] Upgrade Guide(https://datafusion.apache.org/library-user-guide/upgrading/54.0.0.html) 전체 항목을 체크리스트로 변환해 팀 내 공유
한 문장으로
DataFusion 54는 LATERAL 조인과 SQL 람다로 표현력의 공백을 메우고, arrow-avro와 Parquet CDC로 스토리지 계층을 정비한 릴리스다 — 이 엔진을 내장한 모든 프로젝트가 API 한 줄 변경 없이 이 기능을 상속한다.
References
- Apache DataFusion 54.0.0 Released
- Upgrade Guide: DataFusion 54.0.0
- Release Issue #21080 · apache/datafusion
- Blog Post Issue #22392 · apache/datafusion
- DataFusion 54.1.0 Release Issue #22547
- DataFusion SELECT syntax documentation
- datafusion 54.0.0 on Docs.rs
- DataFusion Python documentation
- Apache DataFusion Ballista 54.0.0 Released
- DataFusion — ssp.sh Brain