Submit Spark jobs on Snowflake¶
Run your Spark applications as batch jobs on Snowflake. You submit a job, Snowflake runs it on Snowflake compute, and you get back an ID that you use to track, monitor, and cancel the run.
Overview¶
A Spark job runs your packaged Spark application (Scala or Python) as a single batch run. When you submit a job, you provide:
- The application: a
.jar(Scala/Java) or.py(Python) file on a Snowflake stage. - The entrypoint: the main class or file to run.
- Arguments: optional command-line arguments passed to your application.
- A specification: the runtime version, language, dependencies, and other settings for the run.
Snowflake runs the job on warehouse compute and returns a job ID. You use that ID to monitor and manage the run.
You can submit a Spark job two ways:
- Submit a job with the REST API: set
asyncExec=trueto submit asynchronously and poll for status, or omit it to call the API synchronously and wait for the run to finish. This is a recommended approach for orchestrating from data pipelines and CI/CD systems such as Airflow, GitHub Actions, GitLab CI, and Jenkins, or from external control planes. - Submit a job with SQL: a synchronous SQL statement that submits the job and waits for it to finish. Because it’s SQL, it integrates natively with Snowflake tasks, so you can build scheduled Spark pipelines on Snowflake.
You provide the job definition inline with each submission. To reuse a stored job definition instead, you can persist it.
Note
Spark jobs run on warehouse compute. Set compute_type: warehouse in your job specification.
Supported languages and runtimes¶
| Language | language | Entrypoint |
|---|---|---|
| Scala (JVM) | scala (or java) | Fully-qualified main class (for example, com.example.MySparkApp) |
| Python | python | Python file name (for example, main.py) |
The runtime is selected with compute_options.runtime_version. The current runtime version is "1.29", which provides
Python 3.11 for Python jobs and Scala 2.12 or 2.13 for Scala jobs.
Prerequisites¶
- A Snowflake account enrolled in the private preview, and a warehouse to run your jobs on.
- A Snowflake stage where you can upload your application files and dependencies.
- A packaged Spark application. See Develop your Spark application.
Develop your Spark application¶
Develop your application with the standard Spark APIs (SparkSession and DataFrames). For how to develop, package, and
configure your application, see the Snowflake documentation for running Spark on Snowflake:
Once your application is packaged, upload it to a Snowflake stage so you can submit it. For a Scala or Java application, package it as a fat JAR and note the fully-qualified main class:
For a Python application, upload your entrypoint file (and any supporting files):
Submit a job with the REST API¶
Submit a Spark job by posting the job definition to the code bundle executions endpoint. This is the recommended option for orchestrating Spark job pipelines from external orchestrators such as Apache Airflow and other clients such as CI/CD and custom UIs.
Endpoint¶
By default the call is synchronous and returns when the run finishes. To submit asynchronously, set the asyncExec
query parameter to true; the call returns immediately with a job ID that you poll for status.
Request headers¶
Request headers follow the standard Snowflake REST API specification. Authenticate with a
key-pair JWT (shown here) or another supported method, such as
OAuth or a programmatic access token. The
X-Snowflake-Warehouse header selects the warehouse that your job runs on.
Idempotent submission¶
requestId is an optional query parameter (a client-generated UUID) that makes submission idempotent. If a submit call
times out or you retry with the same requestId, Snowflake doesn’t start a duplicate run, which prevents orchestrator
retries (Airflow, CI/CD) from starting the same batch twice. Idempotency is keyed only on requestId: the request body
isn’t compared, so a retry that reuses a requestId with a different body is still treated as a duplicate and starts no
new run.
Note
A retry with an already-used requestId returns 200 OK with a body of {"status": null} and no job_id or
Location header. It doesn’t re-return the original run’s job ID, so capture the job_id from the first successful
submission if you need it later.
Request body¶
The body contains the stage location of your application (from_location), the entrypoint, an optional arguments
array, an optional execution_name, and the job specification: a JSON bundle object with the same structure and
fields as the YAML you use in the SQL WITH SPECIFICATION clause. In the REST API, pass the specification as a JSON
object, not a string. See Job specification reference for the fields.
The optional execution_name assigns a name to the run, the same as the SQL EXECUTION_NAME parameter: Snowflake
stores it in CODE_BUNDLE_HISTORY and renders it into the server-side EXECUTE CODE BUNDLE statement. If you omit it,
the run’s EXECUTION_NAME is empty.
Scala/Java:
Python:
Response¶
An asynchronous submission (asyncExec=true) returns 202 Accepted with a job ID in the body. Use the job ID to
monitor and manage the run.
Use job_id (equivalently, the ID in the Location header) as the run identifier for status and cancel calls.
Note
When you submit asynchronously, the API doesn’t validate the specification at submission time. An invalid
specification (for example, an unsupported field) still returns 202 Accepted, and the run then ends with status
FAILED_WITH_ERROR, which you see when you check its status. The synchronous SQL command, by
contrast, rejects an invalid specification when you submit it.
Submit a job with SQL¶
Submit a Spark job with the EXECUTE CODE BUNDLE command. The command takes the entrypoint, arguments, and an optional
execution name as SQL parameters; the rest of the job definition is passed inline as YAML in the WITH SPECIFICATION
clause. The job inherits the role that runs the statement and runs on the warehouse set in the session. The statement
is synchronous: it returns when the run finishes.
Set the session context¶
Set the warehouse, database, and schema before submitting. Your job runs on the session warehouse.
Submit a job¶
Define and submit the job in a single statement, with the application, entrypoint, and specification all passed inline.
Scala/Java:
Python:
Command parameters¶
| Parameter | Description |
|---|---|
FROM | Stage path to the main application file: a .jar (Scala/Java) or .py (Python). |
ENTRYPOINT | For Scala/Java, the fully-qualified main class. For Python, the main .py file name. |
ARGUMENTS | Optional. A parenthesized, comma-separated list of argument strings passed to your application’s main method. |
EXECUTION_NAME | Optional. A name for the run, recorded with the run so you can identify it later. |
WITH SPECIFICATION | The inline YAML specification. See Job specification reference. |
The statement runs synchronously and returns when the run finishes. Each run is identified by its query ID. This is the
same value the REST API returns as job_id and that you pass as the executionId to the status and cancel endpoints,
so you can use it to monitor and manage the run.
Persist a job definition¶
If you would like to reuse a stored job definition, you can register it once and then submit it by name (for example,
EXECUTE CODE BUNDLE my_spark_job ENTRYPOINT = 'com.example.MySparkApp'). The submission parameters are the same as for
inline submission. For details, see
Snowflake Code Bundles.
Schedule with a task¶
Because you submit the job with a SQL statement, you can wrap it in a Snowflake task to run it on a schedule. Tasks let you build and orchestrate Spark data pipelines natively in Snowflake: you can chain jobs into dependency graphs and run them on a schedule, similar to how teams orchestrate Spark workloads with Airflow, but without operating a separate scheduler.
Job specification reference¶
The job specification defines how your Spark job runs. In SQL it’s the YAML in the WITH SPECIFICATION clause; in the
REST API it’s the bundle object in the request body. The following fields apply to Spark jobs. For the full set of
generic configuration fields, see the code_bundle.yml reference.
| Field | Language | Description |
|---|---|---|
type | All | Must be spark for Spark jobs. |
compute_type | All | Must be warehouse. The job runs on the session’s warehouse. |
language | All | scala, java, or python. |
compute_options.runtime_version | All | Runtime version for the job (for example, "1.29"). |
compute_options.language_version | Scala/Java | Required for Scala/Java. The Scala binary version, for example "2.12" or "2.13". |
properties.spark_conf | All | Map of Spark configuration properties. |
properties.java_dependencies.jars | Scala/Java | List of dependency JARs on a stage. Added to the classpath automatically. |
properties.python_files | Python | List of Python files or archives placed on PYTHONPATH. Not installed (unlike python_dependencies). |
properties.python_dependencies.wheels | Python | List of Python wheels on a stage. |
properties.python_dependencies.requirements_files | Python | List of stage paths to requirements.txt files. |
properties.python_dependencies.packages | Python | List of PyPI packages to install (for example, numpy==1.26.4). |
artifact_repositories | Python | Artifact repository used to resolve PyPI dependencies. Defaults to snowflake.snowpark.pypi_shared_repository. |
secrets | All | List of Snowflake secrets to attach. |
external_access_integrations | All | List of external access integrations to attach. |
Note
PyPI dependencies in requirements_files and packages are resolved through an artifact repository. If you don’t set
artifact_repositories, Snowflake uses the account-level snowflake.snowpark.pypi_shared_repository, which proxies PyPI
and doesn’t require an external access integration. To use the Snowflake Anaconda channel, set artifact_repositories to
snowflake.snowpark.anaconda_shared_repository.
Monitor and manage jobs¶
Every Spark job has one identifier, and it’s the same value everywhere you refer to the run. The query ID returned by
EXECUTE CODE BUNDLE, the job_id returned by a REST submission, and the executionId you pass to the status and
cancel endpoints (returned as execution_id in the status response) are all the same ID (for example,
01c51743-c819-4261-0000-5349586311aa). Use it to check status, cancel the run, and find logs. You can monitor and
manage a run with SQL or the REST API, regardless of how you submitted it.
Check status¶
Check a run’s status by its job ID, using SQL or the REST API.
Using SQL:
Look up the run in the CODE_BUNDLE_HISTORY table function. Pass the job ID (returned at submission) as the
QUERY_ID argument, which filters server-side:
The query returns a single row for the run. For example:
For a job submitted inline from a stage, CODE_BUNDLE_NAME, DATABASE_NAME, and SCHEMA_NAME are null because
there’s no stored job definition. STATUS shows the run’s state, such as DONE or FAILED; when a run fails,
ERROR_MESSAGE carries the failure details.
If you assigned an execution_name when submitting (with SQL or REST), you can look the run up by that name instead of
the job ID: pass EXECUTION_NAME => '<execution_name>' to CODE_BUNDLE_HISTORY, or filter on the EXECUTION_NAME
column.
Using the REST API:
Get the status of a run by ID. The {executionId} is the job ID returned at submission. Pass the database and schema as
headers:
The response is an array with a single execution record. status comes from the account query history. A run is still
in progress while status is RUNNING, QUEUED, RESUMING_WAREHOUSE, or BLOCKED, and it has finished when status
is SUCCESS, FAILED_WITH_ERROR, or FAILED_WITH_INCIDENT:
Note
When you poll for status, use a poll interval of about 10 to 25 seconds. Polling more frequently (for example, every 5
seconds) can return HTTP 429 (LimitExceeded). If you get a 429, back off and retry.
Cancel a job¶
Cancel a running job by its job ID, using SQL or the REST API.
Using SQL:
Cancel the run’s query by ID with SYSTEM$CANCEL_QUERY:
Using the REST API:
View logs in the event table¶
Application logs are written to your account’s event table. Each record is tagged with the job’s query ID in
RESOURCE_ATTRIBUTES['snow.query.id']. Find the event table, then query it by the job ID (replace <job_id> with the ID
returned at submission):
Drop the RECORD_TYPE filter to also see the METRIC and SPAN records emitted for the run.
View a failed job’s stack trace¶
To find why a job failed, query the same event table and filter to ERROR and FATAL severity. The results include the
full stack trace, with source line numbers:
Snowflake also records structured exception attributes on these records, which you can select individually:
RECORD_ATTRIBUTES['exception.type'], RECORD_ATTRIBUTES['exception.message'], and
RECORD_ATTRIBUTES['exception.stacktrace'].
Spark Monitoring UI¶
You can browse your Spark runs in the Spark Monitoring UI in Snowsight. Sign in to Snowsight, then open the Spark run
history at https://app.snowflake.com/<organization>/<account>/#/compute/history/spark (replace <organization> and
<account> with your own). The page lists Spark runs over a selectable time range, such as the last 7 days. Each run
appears as a single entry identified by the same job ID returned at submission, so you can set the time range and locate
a run by its ID.