EMR Serverless
EMR Serverless explained: how applications, job runs and automatic scaling work for Spark and Hive, with a worked example and notes on feeding tabular models.
Overview
EMR Serverless is a deployment option of Amazon EMR that lets you run Apache Spark and Apache Hive jobs without creating, sizing or patching a cluster. You define an application, submit job runs, and the service adds and removes workers as the work demands. When no job is running, there is nothing to manage and no idle cluster to pay for.
For teams that prepare large tables, this matters because most data preparation is bursty. A nightly feature build, a weekly backfill or an occasional re-processing of years of history all need a lot of compute for a short time. EMR Serverless fits that shape. It also fits the first stages of any pipeline that feeds a tabular foundation model, where raw files must be cleaned, joined and sampled into well-formed tables. See /model/architecture for how such models consume rows and columns.
What it is
EMR Serverless is a managed runtime for big data frameworks. It belongs to the Amazon EMR family, next to Amazon EMR on clusters and EMR on EKS on Kubernetes. The difference is who owns the capacity. With clusters you pick instance types, node counts and scaling rules. With EMR Serverless you describe the work, and the service picks and manages the compute.
The main objects
- Application: a named, versioned environment for one framework, either Spark or Hive. It carries a release label, optional network settings, limits and optional pre-warmed capacity.
- Job run: one submission of a script, jar or query to an application. Each run has its own execution role, configuration and logs.
- Workers: the units of compute that do the work. A Spark job has a driver and executors, each running as a worker of a chosen size.
- Execution role: an IAM role the job assumes to read and write data, such as objects in Amazon S3.
- Studio and APIs: you can submit work from the console, the command line, SDKs, workflow tools such as Apache Airflow, or notebooks.
An everyday analogy
Think of a classic cluster as renting a whole restaurant kitchen by the month. You hire cooks, buy stoves and keep the lights on even when no one orders. EMR Serverless is a catering service. You send the recipe and the guest count. The caterer brings as many cooks as needed, cooks the meal and leaves. You never see the kitchen.
Where it came from
Amazon EMR launched in 2009 as a way to run Hadoop on rented machines. Over the years it added Spark, Hive, Presto, HBase and other engines, and it added new ways to run them: on EC2 clusters, on Kubernetes through EMR on EKS, and on dedicated hardware in a customer site. EMR Serverless arrived later as the option for people who wanted the engines without the cluster. It generally became available in the early 2020s.
The idea follows a wider trend. Query engines such as Amazon Athena had already shown that many users prefer to pay for work done rather than for machines kept ready. Serverless job runners extended the same idea to long-running batch programs written in Spark code. The exact release dates and feature order are best checked in the official release notes, so this article avoids pinning them.
Why Spark and Hive
Spark became the common engine for distributed data transformation because it keeps data in memory between steps and offers rich APIs in Python, Scala, SQL and R. Hive brought SQL to Hadoop and still powers many warehouse jobs. EMR Serverless supports both, so existing pipelines can move with few code changes. For the Spark side, see Apache Spark and PySpark.
How it works
This section walks through the life of a job, step by step.
Step 1: Create an application
You create an application and pick a framework and a release label. A release label bundles specific versions of Spark or Hive and related libraries. You can leave network settings empty for jobs that only touch public AWS endpoints, or attach the application to a VPC when data sits in private databases.
Analogy: this is like registering a food truck. You say what cuisine it serves and where it may park. You have not cooked anything yet.
Step 2: Choose capacity behaviour
An application can start and stop itself. By default it starts when a job arrives and stops after a quiet period. You can also set a maximum capacity so a runaway job cannot consume unlimited resources. Optionally you can keep a pool of pre-initialized workers ready, which cuts start time for latency-sensitive jobs at the cost of paying for those warm workers.
Step 3: Submit a job run
You submit a Spark script path, entry-point arguments and Spark properties, or a Hive query file. You also attach an execution role and a place for logs. The service validates the request and queues the run.
Step 4: Provision and run
The service starts a driver worker, and then executors as the job needs them. With dynamic resource allocation, Spark asks for more executors when many tasks are waiting and releases them when the stage is done. Each worker has a fixed size in virtual CPUs and memory, picked from supported combinations.
Step 5: Read and write data
Workers read from S3 or other sources through the role. A common setup uses the AWS Glue Data Catalog as the metastore, so that tables created by one job can be queried by Amazon Athena or other engines. Table formats such as Apache Iceberg and Delta Lake can be used when the chosen release supports them.
Step 6: Logs, metrics and cleanup
Driver and executor logs go to S3 or CloudWatch. Job metrics show state, duration and resource use. When the run ends, workers are released. When the application is idle past its timeout, it stops.
What "serverless" really means here
Servers still exist. They are simply not yours to manage. This has consequences:
- You do not choose instance families, so you cannot tune for a specific hardware trait such as local NVMe disks.
- You cannot log in to nodes. Debugging relies on logs, the Spark UI and metrics.
- Startup is fast but not instant, unless you keep pre-initialized capacity.
- Capacity is bounded by quotas and by your configured maximum.
Key concepts and terms
| Term | Meaning |
|---|---|
| Application | A reusable runtime definition for Spark or Hive with a release label and limits. |
| Job run | One execution of code inside an application. |
| Release label | A version bundle of the framework and supporting libraries. |
| Worker | A unit of compute with set vCPU, memory and disk. |
| Driver | The Spark process that plans the job and coordinates executors. |
| Executor | A Spark process that runs tasks on slices of data. |
| Dynamic allocation | Spark behaviour that adds and removes executors as load changes. |
| Pre-initialized capacity | Workers kept warm so jobs start faster. |
| Maximum capacity | A cap on total vCPU, memory and disk the application can use. |
| Execution role | The IAM role a job assumes to access data and services. |
| Metastore | A catalog of table definitions, often the Glue Data Catalog. |
| Custom image | A container image with extra libraries used by workers. |
A worked example
Suppose a team has raw order files in S3 and wants a clean, one-row-per-customer table for model training. The steps below use fake names.
Create the application
aws emr-serverless create-application \
--name "customer-features" \
--type SPARK \
--release-label emr-7.0.0 \
--maximum-capacity cpu="200 vCPU",memory="800 GB" \
--auto-stop-configuration enabled=true,idleTimeoutMinutes=15
The release label above is an example. Use a current one from the documentation.
Write the Spark script
# build_features.py
import sys
from pyspark.sql import SparkSession, functions as F
spark = SparkSession.builder.appName("customer-features").getOrCreate()
src, dst = sys.argv[1], sys.argv[2]
orders = spark.read.parquet(src)
features = (
orders
.groupBy("customer_id")
.agg(
F.count("*").alias("order_count"),
F.sum("amount").alias("total_amount"),
F.max("order_ts").alias("last_order_ts"),
F.countDistinct("product_id").alias("distinct_products"),
)
.withColumn("avg_amount", F.col("total_amount") / F.col("order_count"))
)
features.write.mode("overwrite").parquet(dst)
spark.stop()
Submit the job run
aws emr-serverless start-job-run \
--application-id 00example000000 \
--execution-role-arn arn:aws:iam::111122223333:role/example-emr-job-role \
--name "features-nightly" \
--job-driver '{
"sparkSubmit": {
"entryPoint": "s3://example-code-bucket/build_features.py",
"entryPointArguments": [
"s3://example-raw-bucket/orders/",
"s3://example-curated-bucket/customer_features/"
],
"sparkSubmitParameters": "--conf spark.executor.cores=4 --conf spark.executor.memory=16g"
}
}' \
--configuration-overrides '{
"monitoringConfiguration": {
"s3MonitoringConfiguration": {"logUri": "s3://example-logs-bucket/emr/"}
}
}'
Check the result
aws emr-serverless get-job-run \
--application-id 00example000000 \
--job-run-id 00examplejob0000
The state moves from SUBMITTED to PENDING, SCHEDULED, RUNNING and then SUCCESS or FAILED. When it finishes, the curated bucket holds Parquet files. A downstream step can read them with Amazon Athena to spot check values, or pass them to a training or in-context prediction job.
Reading the numbers
Imagine the job read 400 million order rows and produced 6 million customer rows. If Spark used on average 80 executors for 10 minutes, the cost is driven by vCPU-seconds and memory-seconds used over that time. Doubling the maximum capacity would not double the bill if the job finishes in half the time, because you pay for resource time, not for the number of workers alone. This is the main economic argument for serverless batch: more parallelism can shorten the job without raising total work.
Variation: a Hive query job
Some teams keep SQL in Hive scripts. The submission differs only in the job driver.
aws emr-serverless start-job-run \
--application-id 00examplehive000 \
--execution-role-arn arn:aws:iam::111122223333:role/example-emr-job-role \
--job-driver '{
"hive": {
"query": "s3://example-code-bucket/queries/daily_rollup.sql",
"parameters": "--hiveconf out_path=s3://example-curated-bucket/rollup/"
}
}'
Variation: validating the output
A job that finishes is not always a job that is correct. Add a small check step that reads the output and asserts basic facts.
out = spark.read.parquet(dst)
assert out.count() > 0, "no rows written"
assert out.filter("customer_id IS NULL").count() == 0, "null keys"
null_share = out.filter("total_amount IS NULL").count() / out.count()
print("null share of total_amount:", round(null_share, 4))
Failing the job on a broken assumption is better than silently shipping a bad training table.
Variation: parameters from an orchestrator
An orchestrator can pass a date so the same script handles any day.
run_date = sys.argv[3]
orders = spark.read.parquet(src).where(F.col("order_date") == run_date)
This keeps the script small and makes reruns for a single day easy.
Strengths and limitations
Strengths
- No cluster to size, patch or terminate.
- Automatic scale up and down within a cap.
- Pay for resource time used by jobs, with no cost for an idle application that has stopped.
- Same Spark and Hive code as other EMR options, which eases migration.
- Isolation by job run, each with its own role and logs.
- Works with the Glue Data Catalog and common table formats.
- Good fit for orchestration from Apache Airflow or event triggers.
Limitations
- Less control over hardware, network placement and node-level tuning.
- Cold starts, unless pre-initialized capacity is paid for.
- Limited to the frameworks the service supports.
- Long-lived, always-on streaming services are better served elsewhere, such as Apache Flink or Spark streaming on a cluster, though some streaming-style jobs can run.
- Quotas and per-account limits apply, and you must request increases for very large workloads.
- Debugging relies on logs and UIs, not on shell access.
- Always-busy workloads may cost less on well-utilised reserved clusters.
Cost thinking in plain terms
Cost follows resource time. Picture a taxi meter that counts seconds of each seat used. A job that uses 100 seats for one minute costs the same seat-time as a job that uses 10 seats for ten minutes. Two things lower the bill. One is making the code do less work, through filters, columnar formats and fewer shuffles. The other is avoiding idle time, such as warm workers nobody uses.
Good habits:
- Filter early, so less data is read.
- Select only needed columns, which Parquet makes cheap.
- Avoid wide joins on skewed keys.
- Cache only data that is reused more than once.
- Check the Spark UI after the first run and look for one long task that holds up a stage.
Decision checklist
Ask these questions before choosing a runtime:
- Does the job run on a schedule or on events, rather than all day?
- Is the work code, not only SQL?
- Is the team small, with no wish to run clusters?
- Can the job live without special hardware?
- Is the data in S3 or reachable from a VPC?
If most answers are yes, EMR Serverless is a good first choice.
Fit with data lake layers
| Lake layer | Typical job in EMR Serverless |
|---|---|
| Raw or bronze | Light parsing, schema checks, file compaction |
| Cleaned or silver | Type casting, deduplication, joins, null handling |
| Curated or gold | Aggregates and feature tables for models and reports |
| Model inputs | Sampling, splitting, leakage checks, scoring outputs |
How it compares with alternatives
| Option | Who manages capacity | Best for | Trade-off |
|---|---|---|---|
| EMR Serverless | The service | Variable or occasional Spark and Hive batch jobs | Less hardware control |
| Amazon EMR on EC2 | You | Steady heavy workloads, custom tuning, many frameworks | You manage clusters |
| EMR on EKS | You, via Kubernetes | Teams that already run Kubernetes and want shared pools | Kubernetes operations |
| Amazon Athena | The service | Interactive SQL on files | Not for custom Python code |
| AWS Glue jobs | The service | Managed ETL with a catalog | Different job model and tooling |
| Databricks | Platform vendor | Full lakehouse workspace with notebooks | Separate platform |
| Snowflake | Platform vendor | Warehouse SQL and data sharing | Different programming model |
A simple rule of thumb: pick EMR Serverless when the job is code-heavy, runs a few times a day or less, and the team does not want to run clusters. Pick Athena when SQL is enough. Pick cluster-based EMR when the load is constant and you need to tune the hardware.
Role in tabular foundation models
Tabular foundation models learn from raw tables. They read rows and columns of mixed types, cope with missing values, often see no column names in a clean form, and predict from examples given in context. They are usually pre-trained on large collections of synthetic or real tables. See /model/research and /model/benchmarks for how such models are studied and measured.
EMR Serverless does not train these models. Its role is upstream and around them. It is the muscle that turns large, messy data into tables the model can use.
Preparing the context tables
In-context learning needs a clean table of labelled example rows. Building that table from raw logs, orders or sensor files is a batch job. EMR Serverless can:
- join many source tables into one wide table keyed by entity;
- cast types so that numbers are numbers and dates are dates;
- sample rows, for example a stratified sample, so the context fits the model's limit;
- write the result as Parquet to S3 where a serving job can read it.
Handling missing values
Models for tables must handle blanks. Some can impute internally. Even so, a pipeline often needs to record why a value is missing. A Spark job can add indicator columns, count nulls per column and write data quality statistics. This supports imputation work, where a model fills gaps, and lets teams compare the model's fills with simple baselines.
Pre-training corpus assembly
When tables are collected for pre-training, they must be deduplicated, filtered for quality, and checked for leakage with test sets. A Spark job over millions of tables is a natural task for serverless batch. It can also generate synthetic tables at scale if the generator is written as a Spark or Python program. See Synthetic data generation.
Anomaly detection and sector or semantic classification
Large scoring runs fit the batch model well. Examples:
- Score every row of a daily table for anomalies and write the scores back.
- Run a semantic classifier over column samples to tag columns as dates, identifiers, currencies or sectors.
- Pull features for continual learning, where the model is updated as new data arrives.
For these, EMR Serverless often handles data movement and preparation, while the model itself runs on GPU infrastructure such as Amazon SageMaker.
Governance
Because every job run uses an execution role, access to sensitive columns can be limited by role. Combined with AWS Lake Formation and lineage tooling such as Data lineage, teams can show which sources fed a given training table. Techniques such as Data tokenization and Spark UDF de-identification can run inside the job before data reaches a model.
Sizing the pipeline around a model
A tabular model that predicts in context has a limit on how many example rows it can take at once. A batch job should therefore produce:
- a context table of labelled examples, small enough to fit the limit;
- a query table of rows to score, which can be very large and is processed in chunks;
- a reference table of statistics, such as column ranges, used to check that new data looks like old data.
Each of these is a separate Spark output. Keeping them separate makes the model call simple and the audit trail clear.
Drift and freshness
New data changes. A batch job can compare today's column distributions with last month's and write a drift report. If a column suddenly has many more nulls or a new category, the report flags it before the model sees it. See Schema drift detection and Data contract validation for related ideas.
Column names and meaning
Real tables often arrive with cryptic or missing names. A preparation job can store a mapping from raw names to cleaned names and write simple profiles such as data type, distinct count and sample values. A model that works without names can still use these profiles to decide how to treat each column, and a catalog such as Data catalog can keep the human description.
Practical guidance and best practices
Right-size workers
- Start with moderate worker sizes and look at executor memory use in the Spark UI.
- Avoid very large executors, which can cause long garbage collection pauses.
- Prefer a modest number of cores per executor and let dynamic allocation add more executors.
Control cost and risk
- Always set a maximum capacity on each application.
- Set an idle timeout so the application stops by itself.
- Use pre-initialized capacity only for workloads where start time is critical.
- Tag applications and job runs so spend can be attributed.
Design data layout for Spark
- Store data as Parquet or ORC, not CSV, when possible.
- Aim for files in the hundreds of megabytes, not thousands of tiny files.
- Partition by columns used in filters, but avoid too many partitions.
- Consider Iceberg compaction when using Iceberg tables.
Security
- Give each job the least privilege role it needs.
- Use VPC settings when reading from private data sources.
- Encrypt S3 data and logs.
- Keep secrets in a secrets manager, never in job arguments.
Operations
- Orchestrate with Apache Airflow or a similar scheduler, with retries.
- Send logs to a stable S3 prefix and set retention.
- Alert on failed states and on runs that exceed expected duration.
- Pin the release label in production and test upgrades on a copy of the pipeline.
Testing before production
Run the script locally with a small sample using a local Spark session. Then run the same script on a small slice in EMR Serverless. Only then run the full job. This catches schema and permission errors cheaply.
Failure handling and retries
Jobs fail for ordinary reasons: a missing file, a permission change, an out-of-memory executor. A good setup treats failure as normal.
- Make jobs idempotent. Running twice should give the same output, for example by overwriting a dated partition.
- Write to a temporary prefix, then promote on success.
- Let the orchestrator retry a limited number of times with a delay.
- Keep the failing log path in the alert message so the on-call person can open it quickly.
Monitoring signals
| Signal | Why it matters |
|---|---|
| Job duration versus history | Detects slow drift in data size or skew |
| Output row count | Detects empty or doubled outputs |
| Failed task count | Detects flaky nodes or bad input files |
| Peak memory per executor | Guides worker size |
| Shuffle bytes | Highlights costly joins |
Common mistakes
- No maximum capacity. A bug that creates a huge shuffle can scale far beyond what you intended.
- Too many small files. Thousands of tiny inputs slow planning and reading. Compact them first.
- Skewed keys. One customer with millions of rows can stall a stage. Salt keys or pre-aggregate.
- Reading CSV with schema inference on huge data. It scans data twice and can guess wrong types. Provide a schema.
- Forgetting role permissions. Jobs fail with access errors on S3 or the catalog. Test with a small job.
- Using it for always-on services. Serverless batch is not meant for permanent services.
- Ignoring release label changes. A new Spark version can change behaviour in date handling or null handling.
- Writing output to the same path being read. With overwrite mode, this can delete input before it is read.
-
Leaving pre-initialized workers on. Warm capacity costs money even when idle.
-
Treating it as a database. It processes files. It does not serve low latency lookups.
- Hard-coding bucket names and dates. Pass them as arguments so the same script runs in test and production.
- Skipping output checks. A clean exit code does not prove the table is right.
- Mixing time zones. Timestamps from different sources can shift days. Normalise to one zone early.
- Dropping rows with nulls by default. Missing values often carry signal for models. Keep them and mark them.
Quick reference: sizing a first run
| Question | Starting point |
|---|---|
| How many cores per executor? | A small number such as 4, then adjust |
| How much memory per core? | Begin with a few gigabytes per core and watch spills |
| How large should output files be? | Roughly hundreds of megabytes each |
| How many shuffle partitions? | Enough that each partition handles a modest slice of data |
| How long should the idle timeout be? | Short, such as 15 minutes, for scheduled work |
A short story to remember it
A small data team has a nightly job that used to need a six-node cluster running all day. Most of the day the nodes did nothing. After moving the job to EMR Serverless, the team defined one application, set a cap, and let the scheduler submit a run each night. The job takes the same time, but the team no longer patches nodes, and the application sits stopped for most of the day. The lesson is not that serverless is always cheaper. It is that paying for work, not for waiting, suits jobs that wait a lot.
What to log from every run
- The release label and Spark configuration used.
- The input paths and the number of input rows.
- The output path and the number of output rows.
- The start and end time and the job run id.
- A hash or version of the script, so a result can be traced to code.
Frequently asked questions
What is EMR Serverless used for?
EMR Serverless is used to run Spark and Hive jobs without managing a cluster. Typical uses are data transformation, feature building, backfills, report generation and large scale cleaning of files in S3. It suits workloads that are bursty or occasional, because the service adds and removes workers as needed and stops charging when jobs end.
Is EMR Serverless really serverless?
Servers exist underneath, but you do not provision, patch or scale them. You define an application with limits and submit job runs. The service allocates workers, runs your code and releases the resources. This is why it is called serverless, in the same sense as other managed services where capacity is handled by the provider.
How is EMR Serverless different from Amazon EMR on EC2?
With EMR on EC2 you create a cluster, choose instance types and manage scaling and termination. With EMR Serverless you choose a framework and a release, then submit jobs. You give up some hardware control and gain simpler operations. Steady, heavy workloads with custom tuning may still be better on clusters.
How is it different from Amazon Athena?
Athena runs SQL queries directly on files and returns results, with no code beyond SQL. EMR Serverless runs full Spark or Hive programs, including Python or Scala code, custom libraries and multi-step pipelines. Use Athena for ad hoc questions and EMR Serverless for programmatic data processing.
Does EMR Serverless support Python?
Yes, through PySpark. You submit a Python script as the entry point and can add libraries through packaged environments or custom images. Make sure the Python version and library versions match the chosen release label so that the driver and executors behave the same.
How do I reduce start-up time?
You can keep a small pool of pre-initialized workers so that jobs begin quickly. This reduces latency but costs money while the workers sit warm. For nightly jobs a short start delay is usually fine, so leave pre-initialized capacity off.
Can it read and write Iceberg or Delta tables?
Support depends on the release label and configuration. Many users run Iceberg or Delta tables on Spark with the right libraries and catalog settings. Check the documentation for your release and test with a small table. See Apache Iceberg and Delta Lake for the formats themselves.
How does it help with machine learning on tables?
It prepares the data. It cleans, joins, samples and aggregates source tables into the shape a model expects, and it can score large batches. It does not host GPU models itself, so teams usually pair it with a training or inference service for the model part.
How do I control who can see sensitive data in a job?
Each job runs under an execution role. Grant that role only the buckets, prefixes and catalog tables it needs. Add column and row controls through the catalog governance layer, and mask or tokenize sensitive fields in the job itself before writing outputs.
When should I avoid EMR Serverless?
Avoid it when you need node-level control, special hardware, long-lived interactive clusters for many users, or always-on streaming services. Also avoid it if a plain SQL query in Athena can do the job, because that is simpler.
Glossary
- Application: a named runtime definition in EMR Serverless.
- Auto start and auto stop: settings that start an application on demand and stop it when idle.
- Batch job: a program that processes a bounded set of data and ends.
- Dynamic allocation: Spark feature that adjusts executor count to load.
- Driver: the coordinating process of a Spark job.
- Executor: a worker process that runs Spark tasks.
- Execution role: IAM role assumed by a job run.
- Hive: a SQL engine and metastore from the Hadoop world.
- Job run: one submission of work.
- Metastore: storage of table definitions.
- Parquet: a columnar file format common in lakes.
- Pre-initialized capacity: warm workers kept ready.
- Release label: version bundle for a framework.
- Shuffle: movement of data between executors during joins and aggregations.
- Skew: uneven distribution of data across keys or partitions.
- VPC: a private network in the cloud.
Summary
EMR Serverless lets you run Spark and Hive jobs on demand without owning a cluster. You create an application, submit job runs with an execution role, and the service scales workers up and down within a cap you set. It shines for bursty batch work such as feature building, backfills and large cleaning jobs, and it works with S3, the Glue Data Catalog and modern table formats.
For tabular foundation models, its value is in preparing and governing the tables those models read: typing columns, joining sources, sampling context rows, marking missing values, assembling pre-training corpora and scoring large batches. Use caps, compact files, least privilege roles and an orchestrator, and choose cluster-based EMR or plain SQL when they fit better.
Related
It belongs to AWS data analytics in my stack, under Data engineering and lakehouse. Others in the same group: