Apache DataFusion Ballista 54: AQE·브로드캐스트 조인·REST 관측성으로 분산 쿼리 엔진을 운영 가능한 수준으로 끌어올린 릴리스
왜 지금 Ballista 54를 봐야 하나
분산 쿼리 엔진은 데모에서는 빨라 보여도, 운영에 올리면 다른 문제가 먼저 튀어나온다. 작은 조인을 굳이 셔플로 보내서 네트워크를 태우고, 실패한 잡이 남긴 셔플 파일이 디스크를 잡아먹고, 스케줄러는 무슨 결정을 내렸는지 잘 보이지 않는 식이다.
2026년 7월 12일 공개된 Apache DataFusion Ballista 54.0.0은 이 문제를 정면으로 다뤘다. 이번 릴리스의 핵심은 "새 SQL 문법"이 아니라 분산 실행 경로를 더 예측 가능하게 만들고, 스케줄러를 더 관찰 가능하게 만들고, 단일 노드 DataFusion과 클러스터 Ballista의 격차를 줄이는 것이다.
운영자 입장에서 특히 중요한 변화는 세 가지다.
- AQE(Adaptive Query Execution)가 한 단계 현실로 내려왔다. 아직 기본값은 아니지만, 런타임 통계 기반 브로드캐스트 조인과 원격 오브젝트 스토리지 지원이 들어왔다.
- AQE를 끄고 있어도 join/shuffle 동작이 바뀐다. 정적 플래너가 작은 build side를 기본적으로 브로드캐스트하기 시작했기 때문이다.
- 제어면이 눈에 보이기 시작했다. REST API, Web TUI, Prometheus 메트릭, 실패/취소 시 태스크 정리와 셔플 파일 삭제가 운영 비용을 직접 낮춘다.
한 장으로 보는 Ballista 54 실행·운영 경로
무엇이 바뀌었나: 이번 릴리스의 핵심만 먼저
| 영역 | 53.x까지의 문제 | 54.0.0에서 바뀐 점 | 운영자가 바로 확인할 것 |
|---|---|---|---|
| 적응형 실행 | AQE가 실험적이고, 원격 오브젝트 스토리지에서 실패 경로가 있었다 | stage 통계 기반 재최적화, adaptive broadcast join, empty join short-circuit, remote object store 지원 | AQE를 켰을 때 join plan과 실패 복구 경로가 안정적인지 |
| 정적 플래너 | 작은 join도 셔플 위주로 실행돼 네트워크와 디스크를 더 썼다 | 기본 플래너가 작은 build side를 broadcast로 바꾼다 | AQE를 끄고 있어도 shuffle 양이 줄었는지, join 결과가 같고 성능이 나아졌는지 |
| 운영 가시성 | 잡/태스크/플랜 상태를 한눈에 보기 어려웠다 | REST API, Web TUI, 실패 태스크 노출, job config 조회, /api/metrics | 모니터링과 장애 대응이 CLI·로그 의존에서 벗어났는지 |
| 실패 정리 | 취소·실패 후에도 stage/task와 shuffle 파일이 남기 쉬웠다 | running stage·task를 같이 취소하고 성공 시 shuffle 정리 | 디스크 잔여 파일과 취소 시간, 실패 후 리소스 회수가 줄었는지 |
| 언어 접근성 | Python 클라이언트 사용에 Rust 빌드 체인이 필요했다 | PyPI wheel 제공 | 분석/노트북 팀이 별도 빌드 없이 붙을 수 있는지 |
1. 실행 경로: AQE가 조금 더 현실이 됐지만, 진짜 중요한 건 "AQE를 꺼도 바뀐다"는 점이다
AQE는 아직 기본값이 아니다
Ballista 54의 AQE는 기본 활성화가 아니다. 릴리스 노트도 여전히 experimental이라고 못 박고 있다. 다만 53.0.0에서 "아이디어가 들어왔다" 수준이었다면, 54.0.0에서는 운영자가 시험해볼 만한 재료가 생겼다.
- 완료된 stage 통계를 바탕으로 scheduler가 DataFusion physical optimizer를 다시 돌린다.
- build side가 충분히 작다고 판단되면 런타임에 broadcast hash join으로 승격할 수 있다.
- 입력 stage가 비어 있으면 join 자체를 짧게 끊는다.
- 이전에는 원격 오브젝트 스토리지에서 AQE planning이
No suitable object store found로 실패했는데, 이번에는 그 경로가 고쳐졌다. - chaos-monkey 형태의 실패 주입 테스트 하네스가 추가돼 AQE 경로를 일부러 흔들어볼 수 있게 됐다.
핵심은 AQE가 "자동으로 빨라진다"가 아니라, 이제야 카나리 실험을 설계할 수 있는 수준으로 내려왔다는 점이다. 기본값이 꺼져 있다는 사실 자체가, upstream도 아직 모든 워크로드에서 결과를 장담하지 않는다는 신호다.
정적 플래너의 broadcast join 기본화는 더 즉각적인 변화다
운영 측면에서 더 큰 변화는 AQE가 아니라 정적 플래너다. 54.0.0에서는 작은 build side를 기본적으로 브로드캐스트하기 시작했다.
업그레이드 가이드는 다음 기본값 변화를 명시한다.
datafusion.optimizer.hash_join_single_partition_threshold = 10 MBhash_join_single_partition_threshold_rows = 1000000ballista.optimizer.broadcast_join_threshold_bytes = 10 MBballista.optimizer.broadcast_sort_merge_join_enabled = true
즉, AQE를 켜지 않아도 작은 join은 셔플 기반 SortMergeJoinExec에서 CollectLeft 기반 broadcast hash join으로 바뀔 수 있다. 이 변화는 대개 좋은 방향이다. 작은 디멘션 테이블 하나 때문에 대형 팩트 테이블 양쪽을 셔플하는 비용을 줄일 수 있기 때문이다.
하지만 운영자는 이 변화를 성능 개선 이야기로만 받아들이면 안 된다. join plan이 바뀌면 다음도 함께 바뀐다.
- 네트워크 패턴
- executor 메모리 사용량
- shuffle 디스크 사용량
- skew가 드러나는 위치
- cancellation 시 회수해야 할 중간 산출물의 양
릴리스 노트가 굳이 "AQE off에서도 join plan과 shuffle behavior가 바뀐다"고 강조한 이유가 여기에 있다.
이전 동작으로 되돌려야 할 때
카나리 결과가 나쁘면 53.x와 비슷한 보수적 동작으로 되돌릴 수 있다.
SET ballista.optimizer.broadcast_sort_merge_join_enabled = false;
SET ballista.optimizer.broadcast_join_threshold_bytes = 0;
SET datafusion.optimizer.hash_join_single_partition_threshold = 0;
SET hash_join_single_partition_threshold_rows = 0;운영 현실에서는 이 설정을 영구적으로 유지하기보다, 문제가 나는 워크로드가 어떤 join shape를 갖는지 먼저 확인한 뒤 임계값을 다시 잡는 편이 낫다.
2. DataFusion 54 기반 업그레이드가 왜 Ballista 운영에도 영향을 주나
Ballista 54는 이름만 맞춘 릴리스가 아니다. 코어 실행 엔진이 DataFusion 54로 올라간다. 이 말은 곧, 단일 노드에서 좋아진 실행기·플래너 변화가 분산 엔진의 하단부에도 스며든다는 뜻이다.
DataFusion 54 릴리스에서 운영자가 특히 눈여겨볼 항목은 세 가지다.
2-1. morsel-driven Parquet scan
Parquet 스캔을 고정 파티션 배정이 아니라 작은 작업 단위(morsel)를 idle thread가 당겨가는 방식으로 바꿨다. 데이터 skew가 있거나 오브젝트 스토리지 지연이 들쭉날쭉한 워크로드에서 느린 스레드 하나가 병목이 되는 문제를 줄인다.
분산 엔진에서는 이 변화가 더 중요하다. scheduler 차원에서 stage를 잘 나눠도, executor 내부 스캔이 skew에 잡히면 CPU가 놀기 쉽기 때문이다.
2-2. 통계와 카디널리티 추정 개선
NDV(number of distinct values) 추출, equality filter selectivity 개선, semi/anti join cardinality 개선이 들어갔다. 브로드캐스트 조인 여부나 repartition 비용은 결국 얼마나 정확히 작다고 추정하느냐에 달려 있으므로, 정적 플래너와 AQE 둘 다 이득을 본다.
2-3. plan에서 불필요한 작업을 덜 하게 됨
uncorrelated scalar subquery를 join으로 억지 변환하지 않고 한 번만 계산하는 physical operator가 생겼고, redundant sort key pruning 같은 개선도 들어갔다. 이건 "새 기능"보다 더 운영 친화적이다. 같은 SQL이 덜 비싼 plan으로 내려오기 때문이다.
중요한 단서도 있다. DataFusion 54의 단일 노드 개선이 Ballista의 병목을 자동으로 없애주지는 않는다. Ballista release note가 SF=1000 구간의 scheduler throughput, shuffle I/O, small-files handling을 여전히 추적 이슈로 남겨둔 이유다. 즉, 코어 엔진이 좋아져도 클러스터 스케줄링은 별도의 문제다.
3. 제어면: 이번 릴리스의 실제 가치가 드러나는 부분
REST API와 Web TUI가 같은 모델을 본다
Ballista 54의 Web TUI는 예쁜 장식이 아니다. release note에 따르면 browser TUI는 scheduler REST API를 그대로 백엔드로 사용한다. 운영자가 확인할 수 있는 뷰는 다음으로 수렴한다.
- executors
- jobs
- stages
- tasks
- logical / physical / graph plan
- metrics
- failed job / failed task 상태
- job configuration popup
공식 scheduler 문서는 REST API 엔드포인트를 다음처럼 정리한다.
GET /api/jobsGET /api/job/{job_id}GET /api/job/{job_id}/dotGET /api/job/{job_id}/dot_svgPATCH /api/job/{job_id}(cancel)GET /api/job/{job_id}/configGET /api/job/{job_id}/stage/{stage_id}/dotGET /api/metrics
즉, 대시보드가 따로 있고 API가 따로 있는 구조가 아니라, 운영자가 브라우저에서 본 상태와 자동화 스크립트가 읽는 상태가 같은 제어면에서 나온다는 점이 중요하다.
메트릭은 화려하지 않지만, 필요한 최소선을 넘었다
공식 metrics 문서에 따르면 scheduler는 기본적으로 아래 메트릭을 Prometheus로 노출할 수 있다.
job_exec_time_secondsplanning_time_msjob_failed_totaljob_cancelled_totaljob_completed_totaljob_submitted_totalpending_task_queue_size
이 세트가 대단히 풍부한 것은 아니다. stage별 세부 flame graph나 executor 내부 스톨 원인을 바로 보여주지도 않는다. 대신 "지금 잡이 어디서 막히는가"를 첫 번째 질문 수준에서는 답할 수 있는 최소선을 확보했다.
특히 planning_time_ms와 pending_task_queue_size는 Ballista 특유의 병목을 분리하는 데 도움이 된다.
- planning이 길면 scheduler가 밀리는 문제일 가능성
- pending queue가 길면 executor 공급이나 shuffle downstream이 막히는 가능성
- job execution time만 길고 planning이 짧으면 실제 task path 쪽 문제일 가능성
실패와 취소를 더 싸게 만든다
이번 릴리스에서 놓치기 쉬운 개선이지만, 운영 비용에는 꽤 직접적이다.
- 잡 성공 시 intermediate shuffle file 삭제
- 잡 실패/취소 시 running stage와 task까지 함께 취소
- executor panic / task error가 executor 프로세스 전체를 죽이지 않도록 처리
Finished task로그에 task execution duration 남김
분산 엔진에서 "실패를 빨리 치우는 능력"은 평균 성능만큼 중요하다. 특히 셔플 기반 워크로드는 디스크를 빨리 채운다. 실패한 잡이 잔여 파일을 남기는 엔진은 성능이 아니라 청소 비용으로 운영자를 괴롭힌다.
4. 업그레이드 경계: 54.0.0에서 실제로 조심할 것
mixed-version cluster 금지
공식 upgrade guide는 53.x와 54.0.0이 serialized plan을 안정적으로 주고받는다고 보지 말라고 말한다. 이유는 두 가지다.
- DataFusion 54 protobuf bump
- empty projection 직렬화 ambiguity 수정
즉, scheduler만 먼저 올리고 executor는 나중에 올리는 순차 롤링 업그레이드 전략은 이 릴리스에서 위험하다. 최소한 Ballista 클러스터는 scheduler/executor를 같은 버전으로 묶어 카나리하는 편이 안전하다.
CORS 설정면이 바뀌었다
scheduler REST API의 CORS 설정은 더 이상 환경변수에서 읽지 않는다.
- 제거:
BALLISTA_CORS_ALLOWED_ORIGINS,BALLISTA_CORS_ALLOWED_METHODS - 대체:
--cors-allowed-origins,--cors-allowed-methods
즉, Helm chart나 systemd unit에서 env 기반으로 넣던 값을 CLI flag로 옮겨야 한다. 그렇지 않으면 기본값(http://localhost:8080, https://nightlies.apache.org, GET/PATCH/OPTIONS)으로 돌아간다.
Python wheel 도입은 편리하지만, 배포 경로를 바꾼다
이번이 첫 PyPI wheel 릴리스다. 팀 입장에서는 좋은 변화지만, 운영 기준은 두 갈래가 된다.
- Python 사용자는 이제 Rust toolchain 없이 붙을 수 있다.
- 대신 wheel 대상 플랫폼(macOS Apple Silicon, Linux x86-64/ARM64, glibc 2.39+)과 Python 3.10+ 제약을 CI에 반영해야 한다.
즉, 진입 장벽은 낮아졌지만 "아무 환경에서나 설치된다"는 뜻은 아니다.
5. 운영 체크리스트
업그레이드 전
- [ ] scheduler와 executor를 동시에 54.0.0으로 올릴 수 있는 배포 단위를 준비한다.
- [ ] 현재 53.x 환경에서 작은 차원 테이블 join이 많은 쿼리를 골라 baseline을 잡는다.
- [ ] REST API CORS 설정이 환경변수 기반인지 확인하고, CLI flag로 옮긴다.
- [ ] Python client 사용 팀이 있다면 target OS / glibc / Python 버전을 점검한다.
카나리 중
- [ ] 같은 쿼리를 AQE off 상태로 먼저 돌려, 정적 broadcast join 기본화만의 영향을 본다.
- [ ]
job_exec_time_seconds,planning_time_ms,pending_task_queue_size를 같이 수집한다. - [ ] 대표 조인 쿼리에서 shuffle bytes / stage 수 / tail latency가 줄었는지 본다.
- [ ] 실패한 잡을 일부러 만들고 셔플 파일이 남지 않는지 확인한다.
- [ ] 잡 취소 시 running stage/task가 즉시 정리되는지 확인한다.
AQE 실험 시
- [ ]
ballista.planner.adaptive.enabled = true를 카나리 범위에서만 켠다. - [ ] 원격 오브젝트 스토리지 경로에서
No suitable object store found류 실패가 재현되지 않는지 확인한다. - [ ] adaptive broadcast join으로 바뀐 쿼리의 메모리 피크와 wall-clock time을 같이 본다.
- [ ] skew가 있는 셔플에서 결과가 좋아졌는지, 아니면 planner churn만 늘었는지 분리해서 본다.
롤백 기준
- [ ] join 메모리 피크가 올라가 executor OOM이 잦아지면 broadcast threshold를 0으로 되돌린다.
- [ ] planner time이 늘고 execution time이 줄지 않으면 AQE를 다시 끈다.
- [ ] mixed-version 상태가 의심되면 증상 분석보다 먼저 버전 정렬부터 맞춘다.
마무리
Ballista 54를 한 문장으로 요약하면 이렇다. 분산 쿼리 엔진을 더 똑똑하게 만들려는 시도와, 운영자가 덜 고생하게 만들려는 시도가 처음으로 같은 릴리스 안에서 균형을 잡았다.
AQE는 아직 "기본값으로 믿고 켜라" 단계는 아니다. 대신 정적 broadcast join 기본화, REST/TUI/metrics 정비, 실패 시 cleanup 개선은 지금 바로 운영 비용에 닿는 변화다. 이번 릴리스에서 가장 현실적인 접근은 전체 기능을 한꺼번에 받아들이는 것이 아니라, AQE는 제한된 카나리로, 정적 join 변화와 제어면 개선은 먼저 활용하는 것이다.
Ballista가 release note에서 스스로 말하듯 목표는 "novel feature"보다 boring to operate에 가깝다. 분산 쿼리 엔진에게 그 말은 꽤 큰 칭찬이다.
References
- Apache DataFusion Blog, "Apache DataFusion Ballista 54.0.0 Released" (2026-07-12): https://datafusion.apache.org/blog/2026/07/12/datafusion-ballista-54.0.0
- Apache DataFusion Ballista Docs, "Upgrade Guides — Ballista 54.0.0": https://datafusion.apache.org/ballista/upgrading/54.0.0.html
- Apache DataFusion Ballista Docs, "Ballista Scheduler": https://datafusion.apache.org/ballista/user-guide/scheduler.html
- Apache DataFusion Ballista Docs, "Ballista Scheduler Metrics": https://datafusion.apache.org/ballista/user-guide/metrics.html
- Apache DataFusion Ballista Docs, "Ballista Architecture": https://datafusion.apache.org/ballista/contributors-guide/architecture.html
- Apache DataFusion Blog, "Apache DataFusion 54.0.0 Released" (2026-06-12): https://datafusion.apache.org/blog/2026/06/12/datafusion-54.0.0