적응 쿼리 실행 (AQE)은 쿼리 실행 중에 발생하는 쿼리의 재최적화입니다.
쿼리가 실행되기 전에, 옵티마이저는 데이터 크기의 추정치를 바탕으로 쿼리 계획을 선택합니다. 테이블 통계가 누락되었거나 오래되었거나, 데이터 분포가 치우쳐 있거나, 복잡한 쿼리의 후속 단계에서 추정치가 잘못될 수 있습니다. 잘못된 추정치에 기반한 계획은 느린 조인 전략을 사용하거나, 셔플 후에 너무 많은 작은 작업을 만들거나, 전체 단계를 지체시키는 크기가 지나치게 큰 작업 몇 개를 만들 수 있습니다. AQE는 쿼리가 실행되는 동안 실제 데이터 크기를 기반으로 계획을 재최적화하여 이러한 문제를 해결합니다.
각 셔플 또는 브로드캐스트 교환이 끝날 때마다 Azure Databricks는 지금까지 생성된 데이터에 대한 정확한 통계를 제공합니다. AQE는 이러한 통계를 이용해 더 나은 물리적 전략을 선택하거나, 셔플 후 파티션의 최적 수와 크기를 선택하거나, 그렇지 않으면 쿼리 힌트가 필요한 스큐 조인 처리와 같은 최적화를 적용합니다.
AQE는 기본적으로 사용하도록 설정됩니다. 쿼리를 바꿀 필요는 없어요. 느린 쿼리를 문제 해결할 때 AQE가 쿼리 계획을 변경했는지 확인하세요. 기본 설정이 작업 부하에 맞지 않을 때만 AQE 설정을 조정하세요. 쿼리 계획 및 구성을 참조하세요.
기능
AQE는 네 가지 주요 특징을 가지고 있습니다:
- 정렬 병합 조인을 브로드캐스트 해시 조인으로 동적으로 변경합니다.
- 셔플 교환 후 작은 파티션을 적절한 크기로 병합하여 파티션을 동적으로 결합합니다. 매우 작은 작업은 I/O 처리량이 더 낮고, 스케줄링 오버헤드 및 작업 설정 오버헤드로 인해 더 큰 영향을 받는 경향이 있습니다. 작은 작업을 결합하면 리소스가 절약되고 클러스터 처리량이 향상됩니다.
- 정렬 병합 조인과 셔플 해시 조인에서 왜곡된 작업을 필요에 따라 복제하여 대략적으로 균등한 크기의 작업으로 분할하여 동적으로 처리합니다.
- 빈 관계(empty relations)를 동적으로 감지하고 전파하는데, 이는 행을 반환하지 않는 쿼리의 일부입니다.
지원되는 쿼리
AQE는 다음 두 조건을 모두 충족하는 쿼리에 적용됩니다:
- 쿼리는 스트리밍 쿼리가 아닙니다.
- 쿼리는 최소 하나의 익스체인지(보통 조인, 집계 또는 윈도 함수에서 발생), 최소 하나의 서브쿼리 또는 둘 다를 포함합니다.
AQE는 지원되는 모든 쿼리를 재최적화하지만, 결과는 쿼리가 실행되기 전에 최적화기가 작성한 계획과 동일할 수 있습니다. AQE가 쿼리의 계획을 변경했는지 확인하려면 쿼리 계획(Query plans)을 참조하세요.
쿼리 계획
AQE 적용 쿼리에는 일반적으로 각 주 쿼리 또는 하위 쿼리의 루트 노드인 하나 이상의 AdaptiveSparkPlan 노드가 포함됩니다. 각 AdaptiveSparkPlan 노드에는 isFinalPlan 플래그가 있습니다. 플래그는 쿼리 실행 전과 실행 중에 false이고, 쿼리가 끝나면 true로 변경됩니다.
쿼리 계획을 보려면 Spark UI, DataFrame.explain() 또는 SQL EXPLAIN를 사용하세요. 각각은 AQE가 계획을 어떻게 바꾸는지에 대한 서로 다른 정보를 보여줍니다.
Spark 사용자 인터페이스 (UI)
Spark UI의 쿼리 계획 다이어그램은 가장 최신 계획을 보여주며 쿼리 실행 중에 업데이트됩니다. 이미 실행되고 메트릭이 있는 노드는 변경되지 않습니다. 아직 실행되지 않은 노드는 AQE가 계획을 재최적화하면서 변경될 수 있습니다.
이 예시는 쿼리 플랜 다이어그램을 보여줍니다:
DataFrame.explain()
DataFrame.explain() 각 AdaptiveSparkPlan 노드 아래에 두 개의 계획을 보여줍니다: AQE가 최적화를 적용하기 전의 초기 계획과 현재 계획입니다. 쿼리가 실행될수록 현재 계획이 변경되고, 쿼리가 끝나면 최종 계획이 됩니다.
출력물에는 각 셔플 및 브로드캐스트 단계별 데이터 통계가 포함되어 있습니다. 통계는 해당 스테이지가 진행되었는지에 따라 달라집니다:
- 스테이지 실행 전과 실행 도중에는
isRuntime는false이며 통계는 컴파일 타임 추정치입니다.Statistics(sizeInBytes=1024.0 KiB, rowCount=4, isRuntime=false)을 예로 들 수 있습니다. - 스테이지가 실행된 후
isRuntime는true이고, 통계는 실행 시간 측정값입니다.Statistics(sizeInBytes=658.1 KiB, rowCount=2.81E+4, isRuntime=true)을 예로 들 수 있습니다.
다음 스크린샷들은 DataFrame.explain() 실행 전, 실행 중, 실행 후 출력을 보여줍니다:
실행 전
실행 중
실행 후
실행 후

SQL EXPLAIN
SQL EXPLAIN는 쿼리를 실행하지 않으므로 AQE가 다시 최적화할 수 없습니다. 출력물은 초기 계획만 보여주며, 이는 AQE가 실행하는 계획과 다를 수 있습니다.
이 예시는 SQL EXPLAIN 출력 예를 보여줍니다:
쿼리 계획에서 적용된 최적화를 식별합니다
AQE가 최적화를 적용하면 쿼리 계획이 변경됩니다. 어떤 최적화가 적용되었는지 확인하기 위해 현재 또는 최종 계획과 초기 계획을 비교하세요. 각 최적화는 계획을 특정 방식으로 변경합니다:
전략 변경 참여 현재 또는 최종 계획은 초기 계획과 다른 물리적 조인 노드를 가지고 있습니다.
파티션 병합. 계획에는
CustomShuffleReader속성이 있는Coalesced노드가 포함되어 있습니다.
스큐 조인 처리 계획에는
true필드가SortMergeJoin로 설정된isSkew노드가 포함되어 있습니다.
빈 관계 전파. 관계 필드가 빈
LocalTableScan노드가 계획의 일부 또는 전부를 대체합니다.
구성
이 Apache Spark 구성 속성들은 AQE를 제어합니다. 대부분의 워크로드는 기본값을 변경할 필요가 없습니다.
AQE를 활성화하고 개별 최적화를 조정하세요
이러한 속성으로 AQE를 켜거나 끄고, 자동 최적화 셔플, 브로드캐스트 조인 전환, 공집합 전파를 조정할 수 있다:
| 재산 | Type | Default | Description |
|---|---|---|---|
spark.databricks.optimizer.adaptive.enabled |
Boolean |
true |
적응형 쿼리 실행을 켜거나 끌 수 있습니다. |
spark.sql.shuffle.partitions |
Integer |
200 |
조인 또는 집계에 대한 데이터를 섞을 때 사용할 기본 파티션 수입니다.
auto로 설정하여 자동 최적화 셔플(auto-optimized shuffle)을 활성화하세요. 이 셔플은 쿼리 계획과 쿼리 입력 데이터 크기를 기반으로 이 수치를 설정합니다.구조화된 스트리밍에서는 같은 체크포인트 위치에서 쿼리 재시작 간에 이 값을 변경할 수 없습니다. |
spark.databricks.adaptive.autoBroadcastJoinThreshold |
Byte String |
30MB |
런타임에 정렬 병합 조인을 브로드캐스트 해시 조인으로 전환하는 크기 임계값입니다. |
spark.databricks.adaptive.emptyRelationPropagation.enabled |
Boolean |
true |
동적 빈 관계 전파를 켜거나 끕니다. |
동적으로 파티션을 결합하다
| 재산 | Type | Default | Description |
|---|---|---|---|
spark.sql.adaptive.coalescePartitions.enabled |
Boolean |
true |
파티션 병합 기능을 켜거나 끕니다. |
spark.sql.adaptive.advisoryPartitionSizeInBytes |
Byte String |
64MB |
병합 후의 대상 크기입니다. 응집된 파티션은 이 크기에 가깝지만 이 크기를 넘지 않습니다. |
spark.sql.adaptive.coalescePartitions.minPartitionSize |
Byte String |
1MB |
병합 후 파티션의 최소 크기입니다. 응집된 파티션은 이 크기보다 작지 않습니다. |
spark.sql.adaptive.coalescePartitions.minPartitionNum |
Integer |
클러스터 코어 수의 두 배 | 병합 후의 최소 파티션 수입니다. 이 속성을 설정하지 마세요. 설정하면 spark.sql.adaptive.coalescePartitions.minPartitionSize을 덮어쓰게 됩니다. |
동적으로 편향된 조인 처리
| 재산 | Type | Default | Description |
|---|---|---|---|
spark.sql.adaptive.skewJoin.enabled |
Boolean |
true |
비스듬한 결합 처리를 켜거나 끕니다. |
spark.sql.adaptive.skewJoin.skewedPartitionFactor |
Integer |
5 |
파티션이 왜곡된 것으로 간주되려면 초과해야 하는 중앙값 파티션 크기의 배수입니다. |
spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes |
Byte String |
256MB |
파티션이 왜곡된 것으로 간주되기 위해 초과해야 하는 최소 크기입니다. |
AQE는 이 두 조건이 모두 참일 때만 분할을 치우친 것으로 간주합니다: (partition size > skewedPartitionFactor * median partition size) 와 (partition size > skewedPartitionThresholdInBytes).
FAQ(질문과 대답)
AQE에서 작은 조인 테이블을 브로드캐스트하지 않은 이유는 무엇인가요?
테이블이 spark.databricks.adaptive.autoBroadcastJoinThreshold보다 작지만 AQE가 방송하지 않는다면, 다음 원인들을 확인하세요:
- 조인 타입은 브로드캐스트를 지원하지 않습니다. 일부 조인 타입은 한쪽 면을 브로드캐스트할 수 없습니다. 예를 들어, AQE는
LEFT OUTER JOIN의 왼쪽을 브로드캐스트할 수 없습니다. - 테이블에는 빈 파티션이 많이 있습니다. 정렬 병합 조인의 대부분 작업은 빠르게 끝나거나, 스큐 조인 처리가 조인을 최적화합니다. AQE는 비어 있지 않은 파티션의 비율이
spark.sql.adaptive.nonEmptyPartitionRatioForBroadcastJoin보다 낮으면 정렬 병합 조인을 브로드캐스트 해시 조인으로 변경하지 않습니다.
AQE가 활성화된 상태에서도 브로드캐스트 조인 힌트를 사용해야 할까요?
네, 쿼리를 잘 알고 있다면 가능합니다. 쿼리가 실행되기 전에 옵티마이저가 계획하는 브로드캐스트 조인은 보통 런타임에 AQE가 계획하는 브로드캐스트 조인보다 더 좋은 성능을 냅니다. AQE는 조인의 양쪽을 셔플한 후에야 브로드캐스트 조인으로 전환하지 못할 수 있으며, 그때 실제 테이블 크기를 학습합니다.
AQE는 정적 최적화와 마찬가지로 쿼리 힌트를 존중하며, 힌트가 영향을 미치지 않는 동적 최적화도 적용합니다.
스큐 조인 힌트를 사용해야 할까요, 아니면 AQE 스큐 조인 핸들링을 사용해야 할까요?
AQE 스큐 조인트 핸들링을 사용하세요. 자동으로 작동하며 보통 스큐 조인 힌트보다 더 좋은 성능을 보입니다.
왜 AQE가 제 조인을 재정렬하지 않았나요?
AQE는 런타임에 조인을 재정렬하지 않습니다. 조인 순서를 개선하려면 비용 기반 최적화 도구가 더 나은 계획을 선택할 수 있도록 테이블 통계를 수집하세요. 비용 기반 옵티마이저를 참조하세요.
AQE에서 내 데이터 기울이기를 감지하지 못한 이유는 무엇인가요?
AQE는 이 두 조건이 모두 성립할 때만 분할을 왜곡된 것으로 간주합니다:
- 파티션은
256MB(기본값spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes)보다 큽니다. - 파티션이 중간 파티션 크기의
5배(기본값spark.sql.adaptive.skewJoin.skewedPartitionFactor배)보다 큽니다.
AQE는 또한 셔플 기반 조인(즉, 정렬 병합 조인과 셔플 해시 조인)에서만 스큐를 처리합니다. 브로드캐스트 조인에서 스큐를 최적화하지 않습니다. 셔플 기반 조인의 경우, 조인 타입이 AQE가 최적화할 수 있는 사이드를 결정합니다:
| 조인 유형 | 왼쪽으로 기울어짐이 최적화되어 있습니다 | 오른쪽 기울기 최적화 |
|---|---|---|
INNER |
Yes | Yes |
CROSS |
Yes | Yes |
LEFT OUTER |
Yes | 아니오 |
RIGHT OUTER |
아니오 | Yes |
LEFT SEMI |
Yes | 아니오 |
LEFT ANTI |
Yes | 아니오 |
FULL OUTER |
아니오 | 아니오 |
유산
Spark 1.6은 적응형 실행이라는 기능을 도입했지만, Spark 3.0의 AQE는 다른 기능입니다. Spark 1.6의 적응형 실행은 파티션을 동적으로 병합할 뿐입니다. AQE는 런타임 통계를 기반으로 쿼리를 계획하고 재계획하는 프레임워크로, 이 페이지의 모든 최적화를 지원합니다.