Amazon EMR Serverless の新しい 32vCPU ワーカーサイズで Spark ジョブを実行してみた

Amazon EMR Serverless の新しい 32vCPU ワーカーサイズで Spark ジョブを実行してみた

Amazon EMR Serverless が 32vCPU / 最大 244GB メモリのワーカーサイズをサポートしました。実際に 32vCPU executor で Spark ジョブを実行し、GC ログから 32 コアが認識されていること、32 パーティションがほぼ同程度の処理量で完了したことを確認しました。メモリ構成の制約や設定時の注意点もあわせて紹介します。
2026.07.09

はじめに

2026年7月7日、Amazon EMR Serverlessがより大きなワーカーサイズをサポートしました。

https://aws.amazon.com/jp/about-aws/whats-new/2026/07/amazon-emr-serverless/

従来の最大構成は16vCPU / 120GBメモリでしたが、今回のアップデートで32vCPU / 最大244GBメモリのワーカーが利用可能になっています。

項目 従来の最大構成 今回のアップデート
最大 vCPU 16 32
最大メモリ 120 GB 244 GB
メモリ構成 任意(8GB 刻み) 60 / 120 / 244 GB の3択

公式ドキュメントのワーカー構成一覧は以下です。

https://docs.aws.amazon.com/emr/latest/EMR-Serverless-UserGuide/app-behavior.html#worker-configs

公式アナウンスでは、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つの構成値のいずれかに収まる必要があります。詳細は公式ドキュメントのワーカー構成を参照してください。

https://docs.aws.amazon.com/emr/latest/EMR-Serverless-UserGuide/app-behavior.html#worker-configs

例えば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択に限定される点は、ジョブ設定時に留意が必要です。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事