Guide · AWS Fargate Spot · ECS Cost Optimization
AWS Fargate Spot for MCP Server Probe Workers: 70% Cost Savings
AWS Fargate Spot lets you run ECS tasks on spare AWS capacity at up to 70% less than On-Demand Fargate pricing — making it ideal for the stateless, idempotent probe workers that power MCP server uptime monitoring. An MCP monitoring fleet that costs $50/month on Fargate On-Demand can cost $15/month on Fargate Spot with the right capacity provider configuration and a two-minute SIGTERM handler. This guide explains the Fargate Spot pricing model, why MCP probe workers are a natural fit for Spot, how to set up capacity providers to blend On-Demand and Spot capacity, and the SIGTERM handler you need to checkpoint in-flight probes before a task is interrupted. For ECS capacity provider CDK setup see ECS Capacity Providers for MCP Server Monitoring; for the SIGTERM checkpoint pattern see Handling Fargate Spot Interruptions for MCP Workers.
TL;DR
Fargate Spot gives you up to 70% off On-Demand Fargate prices in exchange for accepting 2-minute SIGTERM interruptions when AWS reclaims the capacity. MCP probe workers are stateless and short-lived — a worker interrupted mid-probe just re-queues its batch and another worker picks it up. Configure your ECS service with a FARGATE_SPOT capacity provider (weight 3) and a FARGATE base of 1–2 On-Demand tasks for the probe coordinator; add a process.on('SIGTERM', ...) handler that drains in-flight probes to SQS before the 2-minute window closes. At 1,000 monitored endpoints running 10 parallel probe workers, switching to 80% Spot reduces monthly ECS compute from ~$45 to ~$13.
What Fargate Spot is
AWS Fargate Spot is a purchasing option for ECS Fargate tasks that draws on unused EC2 Spot capacity in AWS's data centers. When spare capacity is available, Fargate Spot tasks are provisioned at a discount of up to 70% vs standard Fargate On-Demand pricing. When AWS needs the capacity back — due to increased On-Demand demand — it sends a two-minute warning (a SIGTERM signal to the task containers) before forcibly terminating the task with SIGKILL.
Key facts about Fargate Spot:
- Interruption rate is low but non-zero: AWS publishes interruption rates per instance family; Fargate Spot interruptions are typically under 5% per day for most capacity pools. In practice, probe workers run for hours uninterrupted.
- You cannot choose which capacity pool: Fargate Spot is a managed offering — you specify vCPU/memory for the task definition and AWS places it in an appropriate capacity pool. You do not get to pin to a specific EC2 instance type as you would with EC2 Spot.
- Pricing is per-second: You pay for the time your task actually runs, billed to the second after a 1-minute minimum. An interrupted task that ran for 3 minutes and 45 seconds is billed for 3 minutes and 45 seconds at the Spot rate.
- Availability zones matter: Fargate Spot capacity varies by AZ. Running tasks in multiple AZs reduces the probability that all tasks are simultaneously interrupted. ECS distributes tasks across AZs when you use a VPC subnet list spanning multiple AZs.
Why MCP probe workers are a strong Spot fit
Not every workload tolerates Spot interruptions. A database, a WebSocket server maintaining client connections, or a batch job that must complete within a hard deadline is a poor fit. MCP probe workers have properties that make them almost ideal for Spot:
| Property | Why it helps on Spot |
|---|---|
| Stateless | No in-memory state that cannot be reconstructed. Each probe result is written to the database immediately on completion; an interrupted worker loses at most the in-flight batch it was currently processing. |
| Idempotent | Running the same probe twice produces two records for the same 60-second window; the dashboard aggregates by time window and deduplicates. A re-queued probe batch that runs again is harmless. |
| Short per-probe duration | Each individual MCP endpoint probe takes 100ms–10s. A SIGTERM with a 2-minute window gives a worker time to finish its current batch of 10–20 probes and drain the results before termination. |
| Horizontally scalable | The probe coordinator distributes endpoint batches via SQS. When one worker is interrupted, its incomplete batches become visible after the SQS visibility timeout and are picked up by surviving workers. No single worker is a bottleneck. |
| Continuously replacing | ECS automatically launches a replacement Fargate Spot task (or falls back to On-Demand) when a Spot task is interrupted. Probe coverage resumes within 30–60 seconds of an interruption. |
The one property that makes probe workers require some care on Spot: they maintain an open batch of in-progress probes at any given moment. A SIGTERM handler that checkpoints the current batch to SQS before shutdown ensures those probes are not lost. See Handling Fargate Spot Interruptions for MCP Workers for the full pattern.
Fargate Spot vs On-Demand cost model
Fargate pricing is based on vCPU-hours and GB-hours of memory consumed by running tasks. The exact Spot discount varies by region and time, but AWS typically shows 50–70% discounts. Using a representative discount of 66% in us-east-1:
| Task size | On-Demand (mo) | Spot at 66% off (mo) | Saving |
|---|---|---|---|
| 0.25 vCPU, 0.5 GB (probe worker) | ~$4.50 | ~$1.53 | ~$2.97/task |
| 0.5 vCPU, 1 GB (probe coordinator) | ~$8.99 | ~$3.06 | ~$5.93/task |
| 1 vCPU, 2 GB (registry crawler) | ~$17.98 | ~$6.11 | ~$11.87/task |
For AliveMCP at 1,000 monitored endpoints, a typical deployment runs:
- 1 probe coordinator (On-Demand, for stability): ~$9/mo
- 8 probe workers (Fargate Spot, 0.25 vCPU/0.5 GB each): 8 × $1.53 = ~$12/mo
- 1 registry crawler (Fargate Spot, 1 vCPU/2 GB): ~$6/mo
Total: ~$27/mo. The equivalent all-On-Demand fleet would cost ~$82/mo — a 67% reduction by running 9 of 10 tasks on Spot. At 10,000 endpoints (80+ workers), Spot saves over $300/mo.
Task definition: probe worker
The probe worker task definition is identical whether you run it On-Demand or Spot — Fargate Spot is a capacity selection choice made at the ECS service level (via capacity providers), not in the task definition itself.
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Duration } from 'aws-cdk-lib';
import { Construct } from 'constructs';
export function createProbeWorkerTaskDef(
scope: Construct,
repo: ecr.IRepository,
executionRole: iam.IRole,
taskRole: iam.IRole,
): ecs.FargateTaskDefinition {
const taskDef = new ecs.FargateTaskDefinition(scope, 'ProbeWorkerTaskDef', {
// Small vCPU/memory: probe workers are I/O-bound (HTTP calls), not CPU-bound.
// 0.25 vCPU is sufficient for 20 concurrent probes per worker.
cpu: 256, // 0.25 vCPU
memoryLimitMiB: 512,
executionRole,
taskRole,
runtimePlatform: {
cpuArchitecture: ecs.CpuArchitecture.ARM64, // Graviton: extra ~20% off both On-Demand and Spot
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
},
});
taskDef.addContainer('ProbeWorker', {
image: ecs.ContainerImage.fromEcrRepository(repo, 'latest'),
environment: {
PROBE_CONCURRENCY: '20', // concurrent MCP endpoint probes per worker
PROBE_TIMEOUT_MS: '8000', // per-probe timeout
SQS_PROBE_QUEUE_URL: process.env.SQS_PROBE_QUEUE_URL ?? '',
DB_REGION: 'us-east-1',
},
logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'probe-worker' }),
healthCheck: {
command: ['CMD-SHELL', 'node -e "process.exit(0)"'],
interval: Duration.seconds(30),
timeout: Duration.seconds(5),
retries: 3,
startPeriod: Duration.seconds(10),
},
});
return taskDef;
}
The ARM64 Graviton runtime applies an additional ~20% discount on top of the Fargate Spot price — Graviton tasks have lower base Fargate pricing than x86. This is orthogonal to Spot and compounds the savings: Graviton On-Demand is already cheaper than x86 On-Demand, and Graviton Spot is cheaper still.
ECS service: attaching Fargate Spot via capacity providers
You associate a Fargate Spot preference with an ECS service through a capacity provider strategy. The strategy specifies two capacity providers — FARGATE (On-Demand) and FARGATE_SPOT — with a base count for On-Demand tasks and a weight ratio for the remainder.
import * as ecs from 'aws-cdk-lib/aws-ecs';
import { Construct } from 'constructs';
export function createProbeWorkerService(
scope: Construct,
cluster: ecs.Cluster,
taskDef: ecs.FargateTaskDefinition,
subnetIds: string[],
securityGroupIds: string[],
): ecs.FargateService {
return new ecs.FargateService(scope, 'ProbeWorkerService', {
cluster,
taskDefinition: taskDef,
desiredCount: 8,
capacityProviderStrategies: [
{
// On-Demand: run at least 1 task at all times even during Spot shortages
capacityProvider: 'FARGATE',
base: 1,
weight: 1,
},
{
// Spot: remaining tasks use spare capacity at 66% discount
capacityProvider: 'FARGATE_SPOT',
base: 0,
weight: 3,
},
],
vpcSubnets: { subnets: subnetIds.map(id => ({ subnetId: id } as any)) },
assignPublicIp: false,
enableExecuteCommand: true,
});
}
With base: 1 for FARGATE and weight: 1 / 3, the resulting task distribution for desiredCount: 8 is:
- 1 On-Demand task (FARGATE base)
- Remaining 7 tasks distributed by weight: FARGATE gets 1/(1+3) = 25% → 1.75 ≈ 2 tasks; FARGATE_SPOT gets 3/(1+3) = 75% → 5.25 ≈ 5 tasks
- Actual result: 2 On-Demand + 5 Spot = 7 tasks plus 1 base = roughly 3 On-Demand + 5 Spot
For a probe worker service where losing 1–2 tasks briefly is acceptable, a more aggressive Spot-heavy strategy (base: 0, FARGATE_SPOT weight only) works fine as long as your SIGTERM handler checkpoints in-flight probes. See ECS Capacity Providers for MCP Server Monitoring for the full CDK cluster setup including enabling both capacity providers at the cluster level.
SIGTERM handler: minimum viable version
Before exploring the full checkpoint pattern, here is the minimum SIGTERM handler every Fargate Spot task must have to avoid data loss:
// probe-worker/src/index.ts
let isShuttingDown = false;
let activeProbeCount = 0;
process.on('SIGTERM', async () => {
console.log('[shutdown] SIGTERM received — Fargate Spot interruption or ECS scale-in');
isShuttingDown = true;
// Wait for in-flight probes to complete (up to 90 seconds)
const deadline = Date.now() + 90_000;
while (activeProbeCount > 0 && Date.now() < deadline) {
console.log(`[shutdown] Waiting for ${activeProbeCount} in-flight probes...`);
await new Promise(res => setTimeout(res, 1000));
}
if (activeProbeCount > 0) {
// Did not finish in time — re-queue incomplete batch to SQS so another worker picks it up
await requeueIncompleteProbes();
console.log(`[shutdown] Re-queued ${activeProbeCount} incomplete probes`);
}
console.log('[shutdown] Exiting cleanly');
process.exit(0);
});
// In your probe loop: check isShuttingDown before starting a new batch
async function probeLoop(): Promise {
while (!isShuttingDown) {
const batch = await dequeueProbeBatch();
if (!batch) { await new Promise(r => setTimeout(r, 5000)); continue; }
activeProbeCount += batch.endpoints.length;
try {
await runProbeBatch(batch);
} finally {
activeProbeCount -= batch.endpoints.length;
}
}
}
This handler gives the probe worker 90 seconds of the 2-minute SIGTERM window to finish its current batch. The remaining 30 seconds is buffer before SIGKILL. For the full pattern — including SQS visibility timeout management and batch checkpointing — see Handling Fargate Spot Interruptions for MCP Workers.
ECS service replacement behavior on interruption
When a Fargate Spot task receives SIGTERM and eventually exits, ECS treats it as a task that stopped unexpectedly. The ECS service scheduler immediately attempts to launch a replacement task to maintain desiredCount. The replacement is provisioned from whichever capacity provider has available capacity — typically another Fargate Spot task if Spot capacity is available, or a Fargate On-Demand task if Spot is temporarily constrained.
The replacement task starts cold: it pulls from SQS for the next batch of endpoints to probe. If the interrupted task re-queued its incomplete batch to SQS before exiting, those endpoints are available for the replacement worker to pick up immediately. The probe coverage gap — the window between task interruption and replacement task picking up work — is typically 30–90 seconds (ECS launch latency for Fargate is roughly 30s for a cached container image).
For a 60-second probe interval, a 30–90 second gap means at most one probe cycle is missed per interrupted task. Uptime calculations can tolerate this: a single missed probe does not flip an endpoint's status to "down" — AliveMCP requires two consecutive failed probes before marking an endpoint as down, so a launch-gap-induced miss is invisible to status calculations.
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
| All Spot tasks interrupted simultaneously | AWS reclaims capacity for an entire AZ or instance family (rare, but possible during peak periods) | Spread tasks across 3+ AZs; set FARGATE base to 2 On-Demand tasks; CloudWatch alarm on RunningTaskCount < desiredCount * 0.5 to alert on mass interruption |
| Replacement task stuck in PROVISIONING for >3 minutes | Fargate Spot capacity unavailable in the selected AZ; ECS is waiting for capacity | Use a capacity provider strategy with FARGATE fallback weight so ECS falls back to On-Demand when Spot is unavailable |
| Probes skipped for 5–10 minutes after interruption | SIGTERM handler didn't re-queue the in-flight batch; those endpoints miss multiple 60s windows | Implement the SQS re-queue pattern in SIGTERM handler; set SQS visibility timeout to 90s so unacknowledged messages become visible again automatically if the handler crashes |
| Duplicate probe records for some endpoints | Interrupted worker partially committed results before SQS message was deleted; replacement worker processes the same SQS message again | Use SQS FIFO queue with message deduplication ID = endpoint-slug + probe-window-epoch; or accept duplicates and deduplicate at query time with MAX(check_time) per window |
| Graviton task fails to pull ARM64 container image | ECR repository only has an x86 image; ARM64 Fargate cannot run x86 images | Build and push a multi-arch image: docker buildx build --platform linux/amd64,linux/arm64 --push; ECR supports multi-arch manifests natively |