feat(athena): add Apache Spark 3.5 support via Spark Connect - #1874
feat(athena): add Apache Spark 3.5 support via Spark Connect#1874dtaniwaki wants to merge 8 commits into
Conversation
07031bb to
1e4c82c
Compare
There was a problem hiding this comment.
Pull request overview
Adds an opt-in execution path for Athena Spark engine version 3.5 Python models by switching from the unsupported Calculations API to Spark Connect (GetSessionEndpoint), while updating engine configuration handling to match Spark 3.5 requirements.
Changes:
- Add Spark Connect submission path (endpoint polling + auth token refresh) when
spark_engine_version: "3.5". - Update Spark session EngineConfiguration generation to use
Classificationsfor Spark 3.5 and adjust expected keys. - Extend adapter response to include DPU execution timing, plus add/adjust unit tests and macros for Spark Connect DataFrame typing and Iceberg write behavior.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| dbt-athena/tests/unit/test_session.py | Updates tests for session IDs now being str instead of UUID. |
| dbt-athena/tests/unit/test_python_submissions.py | Adds routing and Spark Connect unit tests; adjusts expectations for statistics in results. |
| dbt-athena/tests/unit/test_config.py | Adds Spark 3.5-specific tests and expected keys (including Classifications). |
| dbt-athena/src/dbt/include/athena/macros/materializations/models/table/create_table_as.sql | Passes additional optional args to python materialization (incl. spark_engine_version) and gates Iceberg CTAS path. |
| dbt-athena/src/dbt/include/athena/macros/adapters/python_submissions.sql | Uses Spark Connect DataFrame type for 3.5 and adds optional Iceberg writeTo() materialization branch. |
| dbt-athena/src/dbt/adapters/athena/session.py | Switches global session tracking maps to use string session IDs; adds spark_managed_logging param. |
| dbt-athena/src/dbt/adapters/athena/python_submissions.py | Implements Spark Connect submission path, endpoint polling, and auth token refresh ChannelBuilder. |
| dbt-athena/src/dbt/adapters/athena/impl.py | Returns AthenaAdapterResponse for python submissions and attempts to surface DPU execution millis. |
| dbt-athena/src/dbt/adapters/athena/connections.py | Extends AthenaAdapterResponse with dpu_execution_in_millis. |
| dbt-athena/src/dbt/adapters/athena/config.py | Adds Spark 3.5 EngineConfiguration behavior (no DPU sizes/SparkProperties; use Classifications). |
| dbt-athena/pyproject.toml | Adds optional dependency group spark_connect for pyspark[connect]. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
1e4c82c to
cd75ca2
Compare
|
I'm making this PR a draft because it's broken by commits which I added recently 🙇 |
65efeb5 to
46085e1
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Bounded by ``min(self.timeout, _ENDPOINT_READY_TIMEOUT_SECONDS)`` so | ||
| slow endpoint provisioning cannot consume the full execution budget | ||
| reserved for user code. | ||
| """ | ||
| deadline_seconds = min(self.timeout, _ENDPOINT_READY_TIMEOUT_SECONDS) |
There was a problem hiding this comment.
_wait_for_endpoint() uses deadline_seconds = min(self.timeout, _ENDPOINT_READY_TIMEOUT_SECONDS), which ignores time already spent in the overall attempt (session acquisition, prior retries/backoff, etc.). This can cause the endpoint wait to exceed the remaining timeout budget, violating the contract that self.timeout covers endpoint readiness + execution. Consider passing the per-attempt remaining budget into _wait_for_endpoint() (or computing an absolute deadline from start_time) and using min(remaining, _ENDPOINT_READY_TIMEOUT_SECONDS) for the endpoint wait loop.
67a2a2f to
6721639
Compare
|
I made some refactoring, but the logic is not changed. |
90b2fe4 to
8c7541f
Compare
d1c0284 to
5fdd52c
Compare
7d1b6ea to
c891fdb
Compare
c891fdb to
fa4a1ec
Compare
|
I updated the code with many improvements and squashed the commits. I believe the PR is enough ready for other developer's review. |
7268255 to
32e2e00
Compare
32e2e00 to
b1f17fc
Compare
…ct pool Signed-off-by: Daisuke Taniwaki <daisuketaniwaki@gmail.com>
…instead of killing it under co-tenants
07e953f to
bc1e461
Compare
# Conflicts: # dbt-athena/src/dbt/adapters/athena/impl.py
|
Added support for |
…id s3 client double-injection
857544b to
d8a79b4
Compare
|
@dtaniwaki I'm no longer a maintainer/employee so can't help you here @aahel would you have bandwidth to take a look? |
|
Thanks for letting me know, and thanks for all your work! No problem at all. @aahel Thanks in advance if you have time to take a look! |
|
Thank you for taking on this task @dtaniwaki @aahel If it is relevant - it's actually a real and present issue for me currently. AWS have not deployed all Athena versions in all regions. We cannot use Python with DBT at all in some regions. e.g. London: Here's a sample of available versions London vs Frankfurt
|
|
Same issue for us in |
resolves #1854
docs N/A
Problem
Athena workgroups configured with Apache Spark engine version 3.5 cannot run dbt Python models because the Calculation API (
StartCalculationExecution) is not supported in Spark 3.5. Additionally, Spark 3.5 no longer acceptsCoordinatorDpuSize,DefaultExecutorDpuSize, orSparkPropertiesinEngineConfiguration, requiring a different configuration format (Classifications).Apache Spark 3.5 on Athena upgrades the runtime from Spark 3.2.1 to 3.5.6, bringing performance improvements such as avoiding unnecessary shuffles in Storage-Partitioned Joins when partition keys mismatch but join expressions are compatible (release notes).
Solution
Adds an alternative code execution path using Spark Connect via
GetSessionEndpoint, which is available in Spark 3.5. Users opt in by settingspark_engine_version: "3.5"in their model or profile config.Key changes:
ChannelBuilderthat auto-refreshes the Athena AuthToken before expiry.(invocation_id, fingerprint)that reuses sessions across models within an invocation. Required because Spark Connect binds a persistent gRPC channel to a single session, so the existingAthenaSparkSessionManagercan't be reused.Classifications(spark-defaults).spark_connect_max_retriestimes with exponential backoff, terminating the failed session each time.min(MaxConcurrentDpus, maxExecutors + 1)) against an account-wide budget (default 60, theL-E20AD6B8quota) so the pool throttles before AWS rejects withMaximum allowed sessions. Region-levelrequired capacity not being availableerrors fall into the same backoff path.How it works
dbt's own thread pool drives parallelism; the Spark Connect session pool just decides whether each thread gets a fresh session, a warm one, or has to wait.
There are two communication channels with Athena: a boto3 control plane that the pool uses to create / look up / terminate sessions, and a gRPC data plane that PySpark clients use to actually run code against the Spark runtime.
flowchart TB subgraph Client["dbt host process"] direction TB subgraph Threads["dbt thread pool (profile: threads: N)"] direction LR T1[dbt thread 1] T2[dbt thread 2] T3[dbt thread N] T1 ~~~ T2 ~~~ T3 end Pool["Spark Connect session pool"] subgraph Clients["PySpark clients (up to max_sessions per fingerprint)"] direction LR PS1["SparkSession 1"] PS2["SparkSession M"] end Threads -->|"acquire / release<br/>(≤ session_concurrency<br/>models per session)"| Pool Pool --> PS1 & PS2 end subgraph AWS["Athena (AWS-managed)"] direction TB Ctrl["Athena control plane"] subgraph WG["Athena Spark workgroup (engine version 3.5)"] direction TB subgraph S1["session 1"] direction TB SC1["Spark Connect server<br/>session 1 endpoint"] Spark1["Spark runtime<br/>Apache Spark 3.5"] SC1 --> Spark1 end subgraph S2["session M"] direction TB SC2["Spark Connect server<br/>session M endpoint"] Spark2["Spark runtime<br/>Apache Spark 3.5"] SC2 --> Spark2 end end end Pool -. "boto3<br/>(create session,<br/>fetch endpoint URL +<br/>initial auth token)" .-> Ctrl PS1 & PS2 -. "boto3<br/>(refresh auth token<br/>before expiry)" .-> Ctrl PS1 -->|gRPC + current auth token| SC1 PS2 -->|gRPC + current auth token| SC2Tuning rules of thumb:
max_sessions × session_concurrency ≥ dbt threadsfor the dominant fingerprint, otherwise dbt threads will sit idle waiting for the pool.session_concurrency: 1(and scalemax_sessionsup) when each model needs the full DPU budget to itself. Raisesession_concurrencywhen the per-model DPU footprint is small and you'd rather amortize session-startup cost across more models.L-E20AD6B8). Lowerspark_connect_dpu_budgetwhen multiple dbt processes share one AWS account so they don't fight for the same quota; raise it if AWS has raised your account quota.New options
spark_engine_version"3""3.5"to opt into Spark Connect execution.spark_connect_max_sessions4spark_connect_session_concurrency11isolates each model; larger values trade isolation for lower session-startup overhead.spark_connect_dpu_budget60Maximum allowed sessions.spark_connect_pool_acquire_timeout21600spark_connect_max_retries30disables retries (single attempt).The existing per-model
timeoutconfig (default 43200s = 12h) continues to apply on the Spark Connect path as the per-attempt Spark execution budget, so model-level execution timeouts do not need a new knob.Example configuration:
Optional dependency for Spark Connect:
pip install "dbt-athena[spark_connect]"Test infrastructure (Terraform)
The functional test (
test_spark_connect_python_submissions.py) is gated onDBT_TEST_ATHENA_SPARK_WORK_GROUPand requires a workgroup with engine versionApache Spark version 3.5plus a dedicated execution role. The Terraform below provisions the minimal AWS resources needed to run the test against a real Athena account.Terraform (click to expand)
Checklist