Trying Out Spark Jobs on Amazon EMR Serverless with the New 32 vCPU Worker Size
This page has been translated by machine translation. View original
Introduction
On July 7, 2026, Amazon EMR Serverless added support for larger worker sizes.
The previous maximum configuration was 16 vCPU / 120 GB of memory, but with this update, workers with 32 vCPU / up to 244 GB of memory are now available.
| Item | Previous maximum configuration | This update |
|---|---|---|
| Max vCPU | 16 | 32 |
| Max memory | 120 GB | 244 GB |
| Memory configuration | Arbitrary (8 GB increments) | Choice of 3: 60 / 120 / 244 GB |
The list of worker configurations in the official documentation is below.
The official announcement lists the following use cases where 32 vCPU workers are effective.
- Shuffle-heavy workloads — can reduce data transfer between executors
- Jobs with data skew — can reduce the risk of out-of-memory failures
- Jobs that require data caching — can keep more data in memory
In this article, we actually run a Spark job on 32 vCPU workers and confirm that 32 cores are recognized and working.
Verification Details
Verification Environment
- Region: ap-northeast-1
- EMR release label: emr-7.8.0
- Runtime: Spark (PySpark)
Benchmark Design
We prepared the following benchmark script.
- Create an RDD with 32 partitions, and run a CPU-bound computation for 60 seconds in each partition
- In each partition, compute integer exponentiation (
base ** exp), get the number of digits withmath.log10, and add it up - No I/O or shuffle at all. A workload mainly consisting of CPU computation (exponentiation of huge integers)
- A time-based loop is used so that all partitions process evenly for 60 seconds
The core part of the script is below.
def cpu_burn_digits(partition_index, iterator, duration_seconds):
import time as t
import math
start = t.time()
total_digits = 0
iterations = 0
base = 2
exp = 1000
while True:
elapsed = t.time() - start
if elapsed >= duration_seconds:
break
result = base ** exp
digits = int(math.log10(result)) + 1 if result > 0 else 1
total_digits += digits
iterations += 1
exp += 100
if exp > 50000:
exp = 1000
base += 1
actual_elapsed = t.time() - start
yield (partition_index, total_digits, iterations, actual_elapsed)
Full script (cpu_bound_bench.py)
"""
CPU-bound均等分散ベンチマーク for EMR Serverless
設計:
- 各パーティションで60秒間ひたすら桁数計算(整数の累乗)を実行
- 60秒経過後に処理できた桁数を返し、全パーティションの合計を出す
- I/O・シャッフルなし → CPU使用率が均等に分散することが期待される
引数:
sys.argv[1] = duration_seconds (デフォルト: 60)
sys.argv[2] = num_partitions (デフォルト: 4)
"""
import subprocess
import sys
import time
from datetime import datetime
from pyspark.sql import SparkSession
def log(msg: str) -> None:
print(f"[{datetime.now().isoformat()}] {msg}")
def print_cpu_info() -> None:
"""Driver側でCPU情報をログに出す。"""
try:
out = subprocess.run(["lscpu"], capture_output=True, text=True, timeout=10)
log("=== lscpu (driver) ===")
print(out.stdout)
except Exception as e:
log(f"cpu info failed: {e}")
def cpu_burn_digits(partition_index, iterator, duration_seconds):
import time as t
import subprocess as sp
import math
if partition_index == 0:
try:
out = sp.run(["lscpu"], capture_output=True, text=True, timeout=10)
print(f"[partition-{partition_index}] === lscpu (executor) ===")
print(out.stdout)
except Exception:
pass
start = t.time()
total_digits = 0
iterations = 0
base = 2
exp = 1000
while True:
elapsed = t.time() - start
if elapsed >= duration_seconds:
break
result = base ** exp
digits = int(math.log10(result)) + 1 if result > 0 else 1
total_digits += digits
iterations += 1
exp += 100
if exp > 50000:
exp = 1000
base += 1
actual_elapsed = t.time() - start
yield (partition_index, total_digits, iterations, actual_elapsed)
def run(duration_seconds: int, num_partitions: int) -> None:
spark = SparkSession.builder.appName("CPUBoundBench").getOrCreate()
sc = spark.sparkContext
log("=== Job start ===")
log(f"duration_seconds={duration_seconds} num_partitions={num_partitions}")
log(f"defaultParallelism={sc.defaultParallelism}")
print_cpu_info()
start = time.time()
rdd = sc.parallelize(range(num_partitions), num_partitions)
results = rdd.mapPartitionsWithIndex(
lambda idx, it: cpu_burn_digits(idx, it, duration_seconds)
).collect()
total_wall_time = time.time() - start
log("=== Per-partition results ===")
grand_total_digits = 0
for partition_idx, digits, iters, elapsed in results:
log(f" partition[{partition_idx}]: digits={digits:,}, iterations={iters}, time={elapsed:.2f}s")
grand_total_digits += digits
log(f"=== TOTAL DIGITS (all partitions): {grand_total_digits:,} ===")
log(f"total_wall_time={total_wall_time:.2f}s")
log("=== Job end ===")
spark.stop()
if __name__ == "__main__":
duration = int(sys.argv[1]) if len(sys.argv) > 1 else 60
parts = int(sys.argv[2]) if len(sys.argv) > 2 else 4
run(duration, parts)
Creating the Application and Submitting the Job
We created an EMR Serverless application and submitted the job with a 32 vCPU executor.
# Create application
aws emr-serverless create-application \
--name cpu-bench-32vcpu \
--release-label emr-7.8.0 \
--type SPARK \
--maximum-capacity '{
"cpu": "36vCPU",
"memory": "248GB",
"disk": "200GB"
}' \
--region ap-northeast-1
Details about the maximumCapacity setting are described later in the notes.
# ジョブ投入
aws emr-serverless start-job-run \
--application-id <application-id> \
--execution-role-arn arn:aws:iam::<account-id>:role/emr-serverless-job-role \
--name cpu-bench-digits-32vcpu \
--job-driver '{
"sparkSubmit": {
"entryPoint": "s3://<bucket>/cpu_bound_bench.py",
"entryPointArguments": ["60", "32"],
"sparkSubmitParameters": "--conf spark.executor.cores=32 --conf spark.executor.memory=221g --conf spark.executor.instances=1 --conf spark.driver.cores=1 --conf spark.driver.memory=4g --conf spark.dynamicAllocation.enabled=false"
}
}' \
--configuration-overrides '{
"monitoringConfiguration": {
"managedPersistenceMonitoringConfiguration": {"enabled": true}
}
}' \
--region ap-northeast-1
We specified spark.executor.memory=221g. This is a setting value to fit within the 244 GB worker configuration, and the calculation details are described later in the notes.
Job Execution Results
The job completed successfully. Below is an excerpt of the get-job-run API response.
{
"jobRun": {
"name": "cpu-bench-digits-32vcpu",
"state": "SUCCESS",
"releaseLabel": "emr-7.8.0",
"jobDriver": {
"sparkSubmit": {
"sparkSubmitParameters": "--conf spark.executor.cores=32 --conf spark.executor.memory=221g --conf spark.executor.instances=1 --conf spark.driver.cores=1 --conf spark.driver.memory=4g --conf spark.dynamicAllocation.enabled=false"
}
},
"totalResourceUtilization": {
"vCPUHour": 0.753,
"memoryGBHour": 5.655,
"storageGBHour": 1.111
},
"totalExecutionDurationSeconds": 118,
"startedAt": "2026-07-09T00:34:46+09:00",
"endedAt": "2026-07-09T00:36:44+09:00"
}
}
The job completed in 118 seconds.
Confirming 32-Core Recognition
From the executor's GC log, we confirmed that 32 cores and 244 GB of memory are correctly recognized.
[0.268s][info][gc,init] CPUs: 32 total, 32 available
[0.268s][info][gc,init] Memory: 244G
[0.268s][info][gc,init] Heap Min Capacity: 221G
[0.268s][info][gc,init] Heap Initial Capacity: 221G
[0.268s][info][gc,init] Heap Max Capacity: 221G
[0.268s][info][gc,init] Parallel Workers: 23
The JVM recognizes 32 CPUs, and a 221 GB heap is allocated. The GC Parallel Workers is set to 23 (a value determined by the JVM based on the number of cores).
Results of 32-Partition Parallel Execution
All 32 partitions processed for about 60 seconds, computing about 41.3 billion digits in total. The variation in processing volume between partitions was less than 1% (minimum 1,284M digits, maximum 1,297M digits). From this result, we can confirm that 32 partitions were processed in parallel with the 32 executor cores setting.
Results by 32 partitions (full)
partition[0]: digits=1,295,403,119, iterations=34815, time=60.00s
partition[1]: digits=1,294,568,243, iterations=34805, time=60.00s
partition[2]: digits=1,296,170,388, iterations=34824, time=60.00s
partition[3]: digits=1,294,816,756, iterations=34808, time=60.00s
partition[4]: digits=1,292,021,453, iterations=34773, time=60.00s
partition[5]: digits=1,293,033,148, iterations=34786, time=60.00s
partition[6]: digits=1,284,219,297, iterations=34654, time=60.00s
partition[7]: digits=1,294,733,733, iterations=34807, time=60.00s
partition[8]: digits=1,294,816,756, iterations=34808, time=60.00s
partition[9]: digits=1,291,944,930, iterations=34772, time=60.00s
partition[10]: digits=1,294,321,402, iterations=34802, time=60.00s
partition[11]: digits=1,296,256,569, iterations=34825, time=60.00s
partition[12]: digits=1,295,066,940, iterations=34811, time=60.00s
partition[13]: digits=1,293,751,941, iterations=34795, time=60.00s
partition[14]: digits=1,294,485,777, iterations=34804, time=60.00s
partition[15]: digits=1,295,912,960, iterations=34821, time=60.00s
partition[16]: digits=1,295,912,960, iterations=34821, time=60.00s
partition[17]: digits=1,295,066,940, iterations=34811, time=60.00s
partition[18]: digits=1,294,076,233, iterations=34799, time=60.00s
partition[19]: digits=1,292,796,894, iterations=34783, time=60.00s
partition[20]: digits=1,295,657,204, iterations=34818, time=60.00s
partition[21]: digits=1,293,430,620, iterations=34791, time=60.00s
partition[22]: digits=1,293,350,754, iterations=34790, time=60.00s
partition[23]: digits=1,289,806,569, iterations=34743, time=60.00s
partition[24]: digits=1,294,983,360, iterations=34810, time=60.00s
partition[25]: digits=1,289,806,569, iterations=34743, time=60.00s
partition[26]: digits=1,291,189,920, iterations=34762, time=60.00s
partition[27]: digits=1,289,244,165, iterations=34735, time=60.00s
partition[28]: digits=1,296,777,553, iterations=34831, time=60.00s
partition[29]: digits=1,284,494,185, iterations=34659, time=60.00s
partition[30]: digits=1,290,380,860, iterations=34751, time=60.00s
partition[31]: digits=1,293,590,909, iterations=34793, time=60.00s
TOTAL DIGITS: 41,382,089,107
Reference: Results of the 1 vCPU Job
These are the results of running the same script in a 1 vCPU / 1 partition configuration.
| Item | 1 vCPU job | 32 vCPU job |
|---|---|---|
| executor cores | 1 | 32 |
| executor memory | 4g | 221g |
| Number of partitions | 1 | 32 |
| Total digits | 1,473,360,014 | 41,382,089,107 |
| totalExecutionDurationSeconds | 133s | 118s |
| vCPUHour | 0.064 | 0.753 |
| memoryGBHour | 0.321 | 5.655 |
※ totalExecutionDurationSeconds includes job startup and shutdown processing. The benchmark processing itself is fixed at 60 seconds for both jobs.
Notes
Memory Configuration for 32 vCPU Workers Is a Choice of 3
The memory size allocated for a 32 vCPU worker cannot be set to an arbitrary value, and must be one of the following three discrete values.
- 60 GB
- 120 GB
- 244 GB
For other vCPU sizes (1 to 16 vCPU), memory can be set within the range and increment defined for each vCPU size, but only 32 vCPU has this restriction.
Calculating Memory Overhead
In EMR Serverless, executor memory overhead is added on top of spark.executor.memory. If not specified, based on spark.executor.memoryOverheadFactor, roughly 10% of executor memory (minimum 384 MB) is treated as overhead. The total must fit within one of the three configuration values above. For details, refer to the worker configurations in the official documentation.
For example, when using the 244 GB configuration, set spark.executor.memory to around 221g (221 GB + 10% ≈ 243 GB → fits within the 244 GB configuration). If the total is too far from a configuration value, the job may be rejected.
Setting maximumCapacity
The application's maximumCapacity must be set so that the combined resources of the driver and executor fit. When using a 32 vCPU / 244 GB executor, set a value that adds the driver portion (minimum 1 vCPU / 4 GB).
Summary
32 vCPU workers are now available in EMR Serverless. We actually launched a 32 vCPU executor and confirmed that the JVM recognizes 32 CPUs and 244 GB of memory, and that a CPU-computation-heavy job with 32 partitions completes with nearly the same amount of processing in each partition.
Compared to the previous 16 vCPU configuration, 32 vCPU / up to 244 GB is a new option for workloads that need greater parallelism and memory capacity per executor. Note that the memory configuration is limited to a choice of 3 (60 / 120 / 244 GB), which needs to be kept in mind when configuring jobs.
