Amazon EMR Serverless の新しい 32vCPU ワーカーサイズで Spark ジョブを実行してみた
はじめに
2026年7月7日、Amazon EMR Serverlessがより大きなワーカーサイズをサポートしました。
従来の最大構成は16vCPU / 120GBメモリでしたが、今回のアップデートで32vCPU / 最大244GBメモリのワーカーが利用可能になっています。
| 項目 | 従来の最大構成 | 今回のアップデート |
|---|---|---|
| 最大 vCPU | 16 | 32 |
| 最大メモリ | 120 GB | 244 GB |
| メモリ構成 | 任意(8GB 刻み) | 60 / 120 / 244 GB の3択 |
公式ドキュメントのワーカー構成一覧は以下です。
公式アナウンスでは、32vCPUワーカーが有効なユースケースとして以下が挙げられています。
- シャッフルヘビーなワークロード — executor間のデータ転送を削減できる
- データスキューのあるジョブ — out-of-memory障害のリスクを軽減できる
- データキャッシュが必要なジョブ — より多くのデータをメモリ上に保持できる
本記事では、実際に32vCPUワーカーでSparkジョブを実行し、32コアが認識・動作することを確認します。
検証内容
検証環境
- リージョン:ap-northeast-1
- EMRリリースラベル:emr-7.8.0
- ランタイム:Spark(PySpark)
ベンチマーク設計
以下のベンチマークスクリプトを用意しました。
- 32パーティションのRDDを作成し、各パーティションで60秒間CPU-boundな計算を実行
- 各パーティションでは整数の累乗(
base ** exp)を計算し、桁数をmath.log10で取得して加算 - I/Oやシャッフルは一切なし。CPU演算(巨大整数の累乗計算)が主体のワークロード
- 時間ベースのループにより、全パーティションが均等に60秒間処理を行う設計
スクリプトの核となる部分は以下です。
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)
スクリプト全文(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)
アプリケーション作成とジョブ投入
EMR Serverlessアプリケーションを作成し、32vCPU executorでジョブを投入しました。
# アプリケーション作成
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
maximumCapacityの設定についての詳細は注意事項で後述します。
# ジョブ投入
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
spark.executor.memory=221gを指定しています。244GBワーカー構成に収めるための設定値で、計算の詳細は注意事項で後述します。
ジョブ実行結果
ジョブは正常に完了しました。get-job-run APIレスポンスの抜粋です。
{
"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"
}
}
ジョブは118秒で完了しました。
32コアの認識確認
executorのGCログから、32コア・244GBメモリが正しく認識されていることを確認しました。
[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
JVMが32 CPUsを認識し、ヒープ221GBが割り当てられています。GCのParallel Workersは23に設定されています(コア数に基づきJVMが決定した値です)。
32パーティション並列実行の結果
32パーティション全てが約60秒間処理を行い、合計約413億桁を計算しました。パーティション間の処理量のばらつきは1%未満(最小1,284M桁、最大1,297M桁)でした。この結果から、32 executor cores設定で32パーティションが並列に処理されていたことを確認できます。
32パーティション別結果(全文)
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
参考: 1vCPU ジョブの結果
同一スクリプトを1vCPU / 1パーティション構成で実行した結果です。
| 項目 | 1vCPU ジョブ | 32vCPU ジョブ |
|---|---|---|
| executor cores | 1 | 32 |
| executor memory | 4g | 221g |
| パーティション数 | 1 | 32 |
| 合計桁数 | 1,473,360,014 | 41,382,089,107 |
| totalExecutionDurationSeconds | 133s | 118s |
| vCPUHour | 0.064 | 0.753 |
| memoryGBHour | 0.321 | 5.655 |
※ totalExecutionDurationSecondsにはジョブの起動・終了処理を含みます。ベンチマーク処理自体は両ジョブとも60秒固定です。
注意事項
32vCPU ワーカーのメモリ構成は3択
32vCPUワーカーとして確保されるメモリサイズは任意の値にできず、以下の3つの離散値のいずれかになります。
- 60 GB
- 120 GB
- 244 GB
他のvCPUサイズ(1〜16vCPU)ではvCPUサイズごとに定められた範囲・刻み幅でメモリを設定できますが、32vCPUのみこの制約があります。
メモリオーバーヘッドの計算
EMR Serverlessでは、spark.executor.memoryに加えてexecutor memory overheadが加算されます。未指定の場合はspark.executor.memoryOverheadFactorに基づき、概ねexecutor memoryの10%程度(最小384MB)がオーバーヘッドとして扱われます。合計値が上記3つの構成値のいずれかに収まる必要があります。詳細は公式ドキュメントのワーカー構成を参照してください。
例えば244GB構成を使う場合、spark.executor.memoryは221g程度に設定します(221GB + 10% ≈ 243GB → 244GB構成に収まる)。合計が構成値から離れすぎるとジョブが拒否される場合があります。
maximumCapacity の設定
アプリケーションのmaximumCapacityには、driverとexecutorの合計リソースが収まるように設定する必要があります。32vCPU / 244GBのexecutorを使う場合、driver分(最低1vCPU / 4GB)を加算した値を設定してください。
まとめ
EMR Serverlessで32vCPUワーカーが利用可能になりました。実際に32vCPU executorを起動し、JVMが32 CPUs・244GBメモリを認識していること、32パーティションのCPU演算主体のジョブがほぼ同程度の処理量で完了することを確認しました。
従来の16vCPU構成よりも、executorあたりの並列度やメモリ容量を大きく取りたいワークロードに対して、32vCPU / 最大244GBという新しい選択肢が加わっています。メモリ構成が60 / 120 / 244GBの3択に限定される点は、ジョブ設定時に留意が必要です。










