Evaluations

Spark runtime upgrade — negative control

EMR Serverless emr-6.15.0 → emr-7.5.0 · Aug 14, 2026

ExampleA rounding fault was injected into the candidate on purpose, to show that differences are caught. This is not an assessment of the upgrade.
Decision
OutputNOT_EQUIVALENT3 of 10 checks differ.
RecommendationDO_NOT_CUT_OVER

Do not cut over: the candidate produced different output from the baseline.

Recorded reasons
  • 1 dual run(s) produced different output on emr_serverless:emr-7.5.0 than on emr_serverless:emr-6.15.0
  • no estate was inventoried, so nothing is known about workloads other than the one(s) run here
  • no source was statically scanned for incompatible constructs
Checks
Both runs read the same input. Each check compares one property of each output dataset.
CheckBaseline · emr-6.15.0Candidate · emr-7.5.0Result
q1_input_sample
Schema equivalence4 columns4 columnsPASS
Row count100100PASS
Row hashsha256 04553898f826…sha256 04553898f826…PASS
Null counts0 nulls / 4 columns0 nulls / 4 columnsPASS
Numeric aggregates9 aggregates9 aggregatesPASS
q1_pricing_summary
Schema equivalence10 columns10 columnsDIFFERED
ValueBaselineCandidate
sum_charge typedecimal(38,6)decimal(38,1)
Row count66PASS
Row hashsha256 984eb4735fc1…sha256 5e7671c91ac6…DIFFERED
ValueBaselineCandidate
content hash differs984eb4735fc1b990…5e7671c91ac6d5cc…
Null counts0 nulls / 10 columns0 nulls / 10 columnsPASS
Numeric aggregates24 aggregates24 aggregatesDIFFERED
ValueBaselineCandidate
sum_charge:max1418207490.4101811418207475.3
sum_charge:min1391007086.0812121391007105.8
sum_charge:sum8427644657.0161888427644818.5

Compared exactly, with no tolerance.

Runs
One run on each side, not a benchmark.
Baseline and candidate runs
FieldBaselineCandidate
EngineEMR Serverless emr-6.15.0EMR Serverless emr-7.5.0
Spark3.4.1-amzn-23.5.2-amzn-1
Wall clock48.5s126.0s (+159.9%)
Metered cost$0.0157$0.0172
Why these timings and costs are not comparable
  • runs were not given stated, identical capacity; no run manifest records a worker count or size, so wall clock is not attributable to the platform
  • EMR Serverless bills each worker for a one-minute minimum, so a run shorter than that is charged for capacity it did not consume and metered cost is not a clean proxy for resource-seconds
Evidence
EMR Serverless pricing
https://aws.amazon.com/emr/pricing/
EMR job run (baseline, emr-6.15.0)
application 00g7nppcfcrf8i09 / job run 00g7nppqou31tg0b, us-east-1
EMR job run (candidate, emr-7.5.0, seeded fault)
application 00g7nppdqs2mr409 / job run 00g7nppt0p4fu80b, us-east-1
billed utilization + rates (baseline, emr-6.15.0)
0.208 vCPU-hr x $0.052624/vCPU-hr + 0.831 GB-hr x $0.0057785/GB-hr = $0.0157 (EMR Serverless us-east-1 x86_64; from aws emr-serverless get-job-run)
billed utilization + rates (candidate, emr-7.5.0, seeded fault)
0.227 vCPU-hr x $0.052624/vCPU-hr + 0.907 GB-hr x $0.0057785/GB-hr = $0.0172 (EMR Serverless us-east-1 x86_64; from aws emr-serverless get-job-run)
Recorded
Aug 14, 2026, 06:00 UTC
Raw evaluation record (JSON)
{
  "coverage": {
    "definitions_parsed_without_error": null,
    "estate_enumerated": false,
    "files_scanned": 0,
    "jobs_inventoried": 0,
    "tasks_inventoried": 0,
    "workloads_dual_run": 1
  },
  "customer": "SparkPilot reference migration - NEGATIVE CONTROL, seeded difference injected (--seed-difference rounding). This run exists to prove the comparator can fail; it is not an assessment of the runtime upgrade",
  "data_provenance": "live_run_seeded_fault",
  "equivalence": [
    {
      "applied_tolerances": {},
      "assertions_performed": 10,
      "findings": [
        {
          "dataset": "q1_pricing_summary",
          "detail": "sum_charge: source type decimal(38,6) vs target type decimal(38,1)",
          "kind": "schema_type",
          "severity": "DIFFERENCE"
        },
        {
          "dataset": "q1_pricing_summary",
          "detail": "content hash differs: source 984eb4735fc1b990… vs target 5e7671c91ac6d5cc…",
          "kind": "row_hash",
          "severity": "DIFFERENCE"
        },
        {
          "dataset": "q1_pricing_summary",
          "detail": "sum_charge:max: source 1418207490.410181 vs target 1418207475.3 (tolerance rel=0.0, abs=0.0)",
          "kind": "aggregate_value",
          "severity": "DIFFERENCE"
        },
        {
          "dataset": "q1_pricing_summary",
          "detail": "sum_charge:min: source 1391007086.081212 vs target 1391007105.8 (tolerance rel=0.0, abs=0.0)",
          "kind": "aggregate_value",
          "severity": "DIFFERENCE"
        },
        {
          "dataset": "q1_pricing_summary",
          "detail": "sum_charge:sum: source 8427644657.016188 vs target 8427644818.5 (tolerance rel=0.0, abs=0.0)",
          "kind": "aggregate_value",
          "severity": "DIFFERENCE"
        }
      ],
      "source_platform": "emr_serverless:emr-6.15.0",
      "target_platform": "emr_serverless:emr-7.5.0",
      "verdict": "NOT_EQUIVALENT",
      "workload_id": "tpch-q1-reference-v1"
    }
  ],
  "evidence_links": {
    "EMR Serverless pricing": "https://aws.amazon.com/emr/pricing/",
    "EMR job run (emr615-baseline)": "application 00g7nppcfcrf8i09 / job run 00g7nppqou31tg0b in AWS account 400920695203, us-east-1",
    "EMR job run (emr750-seeded)": "application 00g7nppdqs2mr409 / job run 00g7nppt0p4fu80b in AWS account 400920695203, us-east-1",
    "billed utilisation + rates (emr615-baseline)": "0.208 vCPU-hr x $0.052624/vCPU-hr + 0.831 GB-hr x $0.0057785/GB-hr = $0.0157 (EMR Serverless us-east-1 x86_64; from aws emr-serverless get-job-run; recomputable in scripts/generate_reference_migration_report.py)",
    "billed utilisation + rates (emr750-seeded)": "0.227 vCPU-hr x $0.052624/vCPU-hr + 0.907 GB-hr x $0.0057785/GB-hr = $0.0172 (EMR Serverless us-east-1 x86_64; from aws emr-serverless get-job-run; recomputable in scripts/generate_reference_migration_report.py)",
    "comparator guard mutation run": "scripts/verify_equivalence_guards.py",
    "manifest probe": "src/sparkpilot/migration/workloads/probe.py",
    "raw run manifests": "docs/evidence/runs/",
    "reference workload": "src/sparkpilot/migration/workloads/reference_tpch_q1.py"
  },
  "generated_at": "2026-08-14T06:00:01+00:00",
  "git_commit": "b10aac5",
  "performance": [
    {
      "estimated_cost_usd": 0.015747725499999997,
      "platform": "emr_serverless:emr-6.15.0",
      "spark_version": "3.4.1-amzn-2",
      "wall_clock_seconds": 48.477,
      "workload_id": "tpch-q1-reference-v1"
    },
    {
      "estimated_cost_usd": 0.0171867475,
      "platform": "emr_serverless:emr-7.5.0",
      "spark_version": "3.5.2-amzn-1",
      "wall_clock_seconds": 126.015,
      "workload_id": "tpch-q1-reference-v1"
    }
  ],
  "performance_confounds": [
    "runs were not given stated, identical capacity; no run manifest records a worker count or size, so wall clock is not attributable to the platform",
    "EMR Serverless bills each worker for a one-minute minimum, so a run shorter than that is charged for capacity it did not consume and metered cost is not a clean proxy for resource-seconds"
  ],
  "rationale": [
    "1 dual run(s) produced different output on emr_serverless:emr-7.5.0 than on emr_serverless:emr-6.15.0",
    "no estate was inventoried, so nothing is known about workloads other than the one(s) run here",
    "no source was statically scanned for incompatible constructs"
  ],
  "recommendation": "DO_NOT_CUT_OVER",
  "scanner_findings_joined_to_jobs": false,
  "scanner_job_overlap": "unknown",
  "schema": "sparkpilot.portability-manifest/v1",
  "source_platform": "emr_serverless:emr-6.15.0",
  "substitutions": [],
  "target_platform": "emr_serverless:emr-7.5.0"
}