Elastic Compute: Auto Scaling Policies, Lambda Tuning, and Compute Selection
Task 3.2 covers designing compute that performs under load and scales elastically: mapping each workload to the right service — EC2, Lambda, Fargate, AWS Batch, or EMR; choosing EC2 instance families by the resource that actually bottlenecks; tuning Lambda with its single memory dial under the 15-minute cap; picking a container orchestrator (ECS or EKS) and the capacity it runs on (Fargate, ECS Managed Instances, or your own EC2 instances); and, above all, selecting the right Auto Scaling policy — target tracking, step, scheduled, or predictive — including scaling on custom metrics like SQS queue backlog, and keeping new instances from distorting the metrics while they boot. It sits in Design High-Performing Architectures, and scaling-policy selection is one of the most repeatable question patterns on the whole exam. The scenarios hand you a traffic shape and a constraint qualifier — MOST performant, LEAST operational overhead, MOST cost-effective — and every option computes; only one fits. By the end of this lesson you will read the traffic pattern out of a scenario and name the compute service, instance family, and scaling policy it demands, with the eliminations to defend the choice.
On this page10 sections
- Map the workload to the compute service before tuning anything
- AWS Batch vs Lambda vs EMR: the big-job boundary
- EC2 instance families: pick by the bottleneck, then right-size
- Lambda performance: one dial, one hard wall
- Containers: ECS vs EKS, and the Fargate launch decision
- Auto Scaling policies: four ways to decide when to scale
- Warmup, cooldown, grace period, and warm pools
- Scaling on the right signal: custom metrics and decoupled tiers
- Levers outside the fleet: edge offload, hibernation, and the decision tables
- Worked scenarios: reasoning through Task 3.2 questions
- Map any workload to the right compute service — EC2, Lambda, Fargate, AWS Batch, EMR, or EMR Serverless — from its duration, trigger, framework, and management constraints
- Select an EC2 instance family by identifying the bottleneck resource, and right-size it with AWS Compute Optimizer rather than an idle-instance check
- Size Lambda memory knowing where extra CPU stops helping, and choose between provisioned concurrency, SnapStart, and reserved concurrency
- Choose the right Auto Scaling policy — target tracking, step, simple, scheduled, or predictive — for a given traffic pattern, including one-off events
- Use default instance warmup, the health check grace period, and warm pools correctly when instances are slow to become ready
- Design queue-based scaling with a backlog-per-instance metric, and fan events out with SNS so decoupled consumers scale independently
- Decide between ECS and EKS, and between Fargate, ECS Managed Instances, and Auto Scaling group capacity, from stated requirements
Map the workload to the compute service before tuning anything
Compute selection on SAA-C03 is a matching exercise along three dimensions: how long the work runs, what triggers it, and how much infrastructure you are willing to manage. Read those out of the scenario and the service names itself.
Lambda owns short, event-driven work: a request arrives, a file lands in S3, a message hits a queue, and a function runs for seconds or minutes — never more than 15 minutes — then disappears. No servers, no capacity planning, near-instant scale-out per event. The moment a scenario implies long-running processes, sustained connections, or specialized hardware, Lambda is out.
Fargate owns containers without servers: you bring a container image, define CPU and memory per task, and AWS runs it — no EC2 fleet to patch, size, or scale. It handles long-running services Lambda cannot, while keeping the no-instance-management property. "Containers" plus "least operational overhead" is the Fargate signature.
EC2 owns everything that needs control: specific instance hardware, GPUs, custom AMIs, OS-level agents, long-lived stateful processes, or licensing tied to hosts. Maximum flexibility, maximum management — which is why, under a LEAST-overhead qualifier, EC2 loses to a serverless option whenever the workload permits one.
AWS Batch owns large-scale batch jobs: it queues jobs, provisions right-sized compute (including Spot), runs work that takes hours, and tears capacity down after. EMR owns big-data frameworks: when the scenario says Spark, Hadoop, Hive, or Presto by name, EMR is the managed home for it — the next section draws that line precisely.
The exam tests this as elimination under a qualifier. A four-hour nightly job rules out Lambda on duration alone; a containerized API with "no infrastructure management" rules out EC2; a Spark migration rules in EMR. Notice that the dimensions are independent — a workload can be long-running and serverless (Fargate), or event-driven and hardware-bound (EC2 behind a queue) — so read all three before committing. Get the service right first: instance families, memory dials, and scaling policies are tuning within the choice, never a substitute for it.
AWS Batch vs Lambda vs EMR: the big-job boundary
Three services fight over "a lot of processing," and the exam separates them cleanly once you know each one's home ground.
Lambda wins when the units of work are small, independent, and event-shaped: resize each uploaded image, process each stream record, handle each API call. Its scale-out is per-event and effectively instant, but every invocation must finish inside 15 minutes, and you get no say in the underlying hardware. In a big-job scenario, Lambda is usually the distractor whose elimination sentence is the runtime: "each job takes several hours" ends the conversation.
AWS Batch wins when the work is jobs, plural and heavy: thousands of transcoding, simulation, genomics, or financial-modeling runs that each take minutes to hours. You submit jobs to a job queue with dependencies and priorities; a compute environment provisions appropriately sized instances — or Fargate capacity, or Spot Instances for the cost-sensitive variant — scales with the queue, and scales to zero when it drains. There is no runtime cap and no scheduler to build. The phrase "batch jobs" in a scenario is almost a service name: an answer that hand-builds the same thing from an Auto Scaling group, a custom queue poller, and cron is the LEAST-overhead elimination.
EMR wins on vocabulary: it is managed infrastructure for big-data frameworks. If the scenario's workload is already written for Spark, Hadoop, Hive, or Presto — or describes petabyte-scale analytics with those ecosystems — EMR runs it without you standing up and tuning a cluster by hand. The distinction from Batch is the framework: Batch runs your containerized or scripted jobs; EMR runs framework jobs. A team migrating on-premises Spark does not want Batch; a team rendering video frames — or running independent containerized simulations — does not want EMR in any form.
EMR itself comes in shapes that the exam separates by how much cluster you want to own:
| Option | What you manage | Fits when |
|---|---|---|
| Long-running EMR cluster on EC2 | Instance types, cluster size (EMR managed scaling can resize it), the cluster staying up | Steady, frequent or interactive framework work that keeps a cluster busy |
| Transient EMR cluster on EC2 | Instance types; the cluster launches, runs its steps, then terminates automatically | Scheduled framework jobs where an idle cluster between runs would waste money |
| EMR Serverless | Only the application (Spark or Hive) and the job; workers are provisioned and scaled per job, and you pay for what the job uses | Framework jobs at irregular times when the team does not want to choose instance types or keep any cluster running |
Scheduled container jobs have their own simple answer. A containerized job that runs on a timetable — a weekly ETL export, say — runs as an Amazon ECS task on Fargate started by an Amazon EventBridge Scheduler schedule (or an EventBridge rule): the task launches, runs to completion, and stops, so nothing sits idle between runs and there is no instance to patch. Packaging the job as a Lambda container image does not escape the duration wall: a function deployed from a container image is still a Lambda function, and every invocation still ends at 15 minutes.
The exam's shape here is a workload description plus all three as options. Ask two questions in order: does each unit of work fit in 15 minutes (no → Lambda out)? Is a named big-data framework involved (yes → EMR; no → Batch)? That two-step resolves nearly every variant.
EC2 instance families: pick by the bottleneck, then right-size
When the service choice lands on EC2, the next decision is the instance family, and the rule is single: identify the resource the workload exhausts first, and buy the family built around it. The exam does not test instance-size trivia; it tests whether you can hear the bottleneck in the workload description.
General purpose (the M family) balances CPU, memory, and network — the default for web servers, application tiers, and small databases with no dominant hunger. Its sibling, the burstable T family, runs on CPU credits: a low baseline with the ability to burst, ideal for spiky, mostly-idle workloads like dev environments and low-traffic sites — and wrong for anything that needs sustained CPU, where exhausted credits become a mystery slowdown the exam likes to plant.
Compute optimized (C family) buys the highest CPU-to-memory ratio: batch processing, media transcoding, scientific modeling, high-performance web serving. Memory optimized (R and the larger X families) buys the reverse: in-memory caches and databases, real-time analytics over large datasets, anything whose working set must live in RAM. Storage optimized (I and D families) buys large local instance store — I for fast NVMe random IOPS (I/O-hungry distributed databases, search clusters), D for dense HDD storage and sequential throughput (data lakes, distributed file systems). Accelerated computing (P and G families, and purpose-built ML chips) buys GPUs and accelerators for training, inference, and graphics.
Selection is not a one-time act — right-sizing is a performance activity. An undersized instance throttles the workload; an oversized one wastes money without helping it. AWS Compute Optimizer is the recognition-level answer here: it analyzes utilization history and recommends instance types and sizes (it also covers Lambda memory and EBS volumes), so "identify over-provisioned or under-provisioned instances with the least effort" resolves to Compute Optimizer, not to a hand-rolled CloudWatch spreadsheet.
Do not confuse it with AWS Trusted Advisor's low-utilization check. That check flags instances that look idle — low CPU and network over recent days — as candidates to stop or downsize. It does not model a better instance type or size, and by design it cannot notice an instance that is under-provisioned and starving for CPU or memory. When a scenario needs both directions (some instances too big, some too small) and concrete type-and-size recommendations, Compute Optimizer is the tool; Trusted Advisor answers only "which instances look idle?"
Exam questions state the bottleneck in plain clothes — "in-memory analytics," "CPU-bound encoding," "millions of low-latency IOPS to local storage" — and list one family per option. Translate symptom to family, and treat a T-family instance mysteriously degrading under sustained load as the CPU-credit story until proven otherwise.
Lambda performance: one dial, one hard wall
Lambda tuning questions reduce to a single fact with several consequences: memory is the only performance dial, and CPU scales with it proportionally. You do not pick vCPUs for a function; you pick a memory allocation (128 MB to 10,240 MB), and more memory brings more CPU (and better network throughput) along with it.
The first consequence: a CPU-bound function gets faster by allocating more memory, even if it never touches the extra RAM. A scenario where a compute-heavy function is slow and uses only a fraction of its memory is testing exactly this — raising memory is how you raise CPU. The second consequence is the cost twist: Lambda bills on gigabyte-seconds, memory multiplied by duration. Doubling memory doubles the per-second rate, but if the added CPU cuts the duration enough, total cost can hold steady or even drop while latency improves. So "more memory" is not automatically "more expensive".
The dial has a ceiling for some code. At 1,769 MB a function gets the equivalent of one full vCPU; above that, Lambda adds further vCPUs, up to six at 10,240 MB. Extra vCPUs only help code that runs work in parallel (multiple threads or processes). Single-threaded, CPU-bound code stops getting faster at about 1,769 MB — memory set above that point leaves the duration flat while the per-second price keeps rising, so cost goes up for nothing. Finding the right setting is measurement, not guesswork: test a few sizes, or use Compute Optimizer's Lambda memory recommendations.
The hard wall: 15 minutes maximum execution time, whether the function is deployed as a .zip archive or as a container image. Any workload described in hours — or any job that cannot be decomposed into sub-15-minute units — disqualifies Lambda outright. The architectural responses are decomposition (Step Functions coordinating many short functions) or relocation (the job belongs on AWS Batch or an ECS task on Fargate).
Startup latency is a separate problem from execution speed, with its own levers:
| Lever | What it does | What it does not do |
|---|---|---|
| Memory | Sets CPU, so it changes how fast the handler runs on every invocation | Remove the initialization delay of a new execution environment |
| Provisioned concurrency | Keeps a set number of environments initialized and ready, so requests up to that level skip cold starts; you pay for the configured environments while they exist, even when idle | Make the handler code itself run faster |
| SnapStart | Snapshots the initialized environment when a version is published and resumes new environments from the snapshot, cutting initialization time for slow-starting runtimes (Java, Python, .NET); there is no charge for provisioned environments sitting idle | Speed up execution; it also cannot be combined with provisioned concurrency on the same version |
| Reserved concurrency | Caps a function's concurrency and guarantees it that share of the account's concurrency, protecting downstream systems and other functions | Pre-initialize anything — it is a limit and a guarantee, not a warm pool |
So a function that is slow on every request needs more memory; a function that is slow only on requests after a quiet period needs provisioned concurrency or SnapStart; the two causes are independent, so each symptom is diagnosed and fixed on its own — more memory never removes a cold start, and pre-initialization never speeds a warm request. SnapStart fits when paying for idle pre-initialized environments is unwelcome; provisioned concurrency fits when a fixed level of instantly ready capacity is the requirement. For SnapStart on Java there is no additional charge; for Python and .NET, snapshot caching and restoration are billed. And Lambda's scaling model is per-event concurrency: it fans out horizontally without an Auto Scaling group, which is why no Lambda option ever pairs with a scaling policy.
Containers: ECS vs EKS, and the Fargate launch decision
Container questions are two decisions stacked: which orchestrator, then which launch type runs its tasks.
The orchestrator choice is requirement-driven, not taste-driven. Amazon ECS is the AWS-native orchestrator: simpler to operate, deeply integrated with IAM, load balancers, and CloudWatch, no cluster software of your own to run. It is the default when the scenario just says "containers" with no ecosystem constraint. Amazon EKS is managed Kubernetes, and it wins only when the scenario names its reasons: the team already runs Kubernetes and wants its manifests, tooling, and operators to carry over; the organization requires Kubernetes for portability across clouds or on-premises; or open-source Kubernetes ecosystem tools are a stated dependency. Absent a Kubernetes keyword, choosing EKS buys operational complexity for nothing — which is precisely how the exam frames the wrong answer.
The capacity decision comes second. Fargate (available to both ECS and EKS) is serverless: each task gets its requested CPU and memory, and there are no EC2 instances to provision, patch, right-size, or scale underneath. Under a LEAST-operational-overhead qualifier, Fargate wins whenever nothing in the scenario demands host control — but Fargate offers no GPUs and no choice of instance type. For ECS, two EC2-backed options cover what Fargate cannot:
| ECS capacity | Who runs the instances | GPUs / specific instance types | Custom AMI |
|---|---|---|---|
| Fargate | AWS; no instances visible to you | No | No |
| ECS Managed Instances | AWS provisions, patches, scales, and replaces EC2 instances in your account, choosing types from the attributes you specify | Yes, including GPU instance types | No — instances use AWS-managed images |
| Auto Scaling group capacity provider (EC2 launch type) | You: your launch template, your AMI, your patching | Yes | Yes |
Read the constraint, then the table answers. GPU containers with least management and no image requirement → ECS Managed Instances. Any container fleet whose instances must boot from your own AMI → an Auto Scaling group capacity provider, because it is the only option that runs your own image. Daemons on every host, or dense reserved capacity cheaper than per-task pricing, also point to EC2-backed capacity.
Keep the layers straight, because distractors blur them: ECS and EKS decide how containers are orchestrated; Fargate and EC2 decide where they run. "EKS on Fargate" is a coherent architecture; "Fargate versus EKS" is a category error the answer options sometimes bait. Elasticity carries into this layer too: a containerized service scales by adjusting its running task count against load, the same policy logic as an Auto Scaling group — target tracking on utilization or request count per target — applied to tasks instead of instances.
The exam's container questions almost always carry a qualifier. "Migrate containerized microservices with the least infrastructure management" is ECS or EKS on Fargate — pick the orchestrator by whether Kubernetes is named. "Standardize on Kubernetes across environments" is EKS. "GPU-accelerated inference in containers" rules out Fargate and leaves the two EC2-backed options, split by whether a custom AMI is required.
Auto Scaling policies: four ways to decide when to scale
An Auto Scaling group needs a policy that answers one question — when does capacity change, and by how much? — and SAA-C03 tests whether you can match the policy types to traffic shapes. There are four families (target tracking, step and its older sibling simple scaling, scheduled, and predictive), and they combine.
Target tracking is the default and the AWS recommendation: pick a metric and a value — keep average CPU utilization at 50%, keep request count per target at a set level — and the group adds and removes instances automatically to hold the target, like a thermostat. It creates and manages its own CloudWatch alarms, so there is no alarm arithmetic, which is why "simplest way to keep the fleet at a healthy utilization" resolves here.
Step scaling exists for when one target is not expressive enough and you already know how much capacity each level of load needs. A step scaling policy is attached to one CloudWatch alarm; its step adjustments cover ranges of how far the metric is above (or below) that alarm's threshold — for example, add 1 instance when the breach is 0–10 units, add 3 when it is 10–30, add 6 beyond that. You do not create a separate alarm per step; you typically create one policy for scale-out and another for scale-in. Step scaling keeps responding to further alarm evaluations while a scaling activity is in progress.
Simple scaling is the older form: one alarm, one fixed adjustment, and then a cooldown during which the policy ignores further alarms until the activity completes and the cooldown expires. It cannot scale harder for a bigger breach and reacts slowly to sustained spikes, so AWS recommends step or target tracking instead; on the exam it is the distractor that reacts in one size only.
Scheduled scaling handles known clocks. A scheduled action sets the group's minimum, maximum, and/or desired capacity at a date and time (once, or on a recurring cron schedule) — business hours, a nightly batch window, a one-off launch. It scales on the calendar, not on load. Two traps: raising only the maximum launches nothing, because max is a ceiling and the group only grows when desired capacity rises; and a fixed schedule is wrong when the pattern recurs but its size varies, because someone has to keep editing the numbers.
Predictive scaling is the machine-learning answer: it analyses recent load history (it needs at least 24 hours, and learns daily and weekly patterns from up to the last 14 days), forecasts the coming days, and launches capacity ahead of the predicted demand. It fits cyclical traffic — weekday morning ramps, regular weekly rhythms — especially when instances take minutes to initialize, so purely reactive policies always lag the ramp. It runs in one of two modes: forecast only (the default) produces forecasts you can evaluate but changes no capacity, and forecast and scale actually provisions to the forecast. Predictive scaling pairs with target tracking, which corrects forecast error in real time. What it cannot do is anticipate a one-off event — a quarterly filing deadline, a single product launch — because nothing in the history repeats it. A known one-off spike is a scheduled action that raises minimum (and desired) capacity before the event and lowers it afterwards, with target tracking still handling the noise.
The exam gives you a traffic story and several policies; the story's shape — steady target, graduated severity tiers, fixed calendar or one-off date, recurring forecastable cycle — is the answer, and policies can combine (scheduled or predictive setting the floor, target tracking following the actual load). Whatever the policy, a group whose new instances take minutes to become useful also needs the warmup settings in the next section, or it will overshoot.
Warmup, cooldown, grace period, and warm pools
A group overshoots when it counts new instances as capacity but keeps judging load by metrics that don't yet include their help — so it adds more instances, then more, then lurches past the need. Four settings sound alike and do different jobs:
| Setting | What it actually does |
|---|---|
| Default instance warmup | For the warmup period after launch, a new instance's usage data is left out of the group's aggregated metrics used by target tracking and step scaling, while the instance still counts toward current capacity. The policy sees honest load and does not keep launching. AWS strongly recommends setting it to roughly how long an instance takes to start serving. |
| Default cooldown | Applies to simple scaling policies: after an activity, wait before reacting again. When no default instance warmup is set, target tracking and step scaling fall back to the default cooldown as their warmup value — another reason to set the warmup explicitly. |
| Health check grace period | Only delays health checks on a new instance so it isn't marked unhealthy and replaced while still booting. It has no effect on scaling metrics, so it cannot stop over-scaling. |
| Warm pool | Keeps pre-initialized instances beside the group so scale-out takes them from the pool instead of launching and configuring from scratch. |
The warmup fixes the measurement; it does not make instances ready faster. When initialization itself is the problem — a slow bootstrap that installs and configures software, or an application that loads a large dataset into memory — a warm pool moves that work ahead of demand. Instances run their initialization once (lifecycle hooks can hold them until it finishes), then wait in the pool in one of three states:
| Pool state | What survives while waiting | What you pay while waiting | Fits when |
|---|---|---|---|
| Stopped | Disk (EBS) contents; RAM is cleared, so in-memory state reloads on start | EBS storage (and any attached Elastic IPs) only | Slow install/configuration is on disk, and idle cost must be lowest |
| Running | Everything — the instance is up | Full instance hours | Fastest possible entry into service matters more than idle cost |
| Hibernated | RAM contents, saved to the encrypted EBS root volume and restored on start | EBS storage (including the saved RAM) and any attached Elastic IPs | Expensive in-memory state (warmed caches, loaded datasets) must survive without paying for running instances |
Hibernation is the same mechanism you can use on a standalone instance: RAM saved to the EBS root volume, then resumed with memory, caches, and processes intact instead of a fresh boot. Inside a warm pool it becomes standby capacity that resumes with its working set already loaded. Combine the pieces rather than choosing between them: a warm pool shortens time-to-serve, and the default instance warmup keeps the policy from over-reacting during whatever startup time remains.
Scaling on the right signal: custom metrics and decoupled tiers
CPU is a proxy, and sometimes a bad one. The canonical case is the queue-driven worker fleet: producers drop jobs on an SQS queue, an Auto Scaling group of consumers processes them. Worker CPU tells you the workers are busy; it does not tell you the queue is ten minutes deep. The scaling signal that matters is backlog per instance — the number of visible messages in the queue divided by the number of running instances. You publish that as a custom CloudWatch metric (or let target tracking compute it with metric math from the queue's ApproximateNumberOfMessagesVisible and the group's in-service instance count) and run a target tracking policy against it. Queue grows, fleet grows; queue drains, fleet shrinks — capacity follows work, not a proxy for work.
Why divide by instances at all? Because target tracking assumes the metric moves in proportion to capacity: add instances and the metric should fall. Raw ApproximateNumberOfMessagesVisible doesn't behave that way — the same 600 messages mean very different things to 2 workers and to 20, and adding workers does not change the queue length in the moment — so a target on raw queue length scales erratically. Backlog per instance does fall as workers are added, so the policy converges. The target value comes from your latency goal: acceptable latency ÷ average processing time per message. If a message takes 2 seconds and no message should wait more than 100 seconds, each instance can carry a backlog of 50, so the target is 50. The exam tells this story explicitly ("scale consumers based on the number of messages in the queue") and expects the custom-metric target-tracking answer over any CPU-based policy.
The pattern generalizes into the design principle behind it: decouple workloads so each tier scales independently. When a web tier calls a processing tier synchronously, the two must scale in lockstep, and a burst that outruns the processing tier turns into user-facing errors. Put a queue between them and the dynamics change: the web tier acknowledges quickly and scales on request load; the queue absorbs the burst as buffered depth; the worker tier scales on backlog at its own pace. Nothing is dropped, and each tier's Auto Scaling policy watches a signal native to that tier.
Queues and topics solve different decoupling problems. An SQS queue delivers each message to one consumer: consumers polling the same queue compete, and whichever receives a message hides it from the others while it is processed. That is right for spreading work across identical workers, and wrong when several different services must each see every event — an audit log, a search indexer, and a notification service all needing each new document. That is publish/subscribe: the producer publishes once to an SNS topic, and the topic delivers a copy to every subscriber. The fan-out pattern subscribes one SQS queue per consuming service to the topic, so each service gets every message and its own buffer: a slow or offline consumer builds up its own backlog without delaying the others or the producer, and each consumer fleet scales on its own queue's backlog. (EventBridge offers the same publish-once, deliver-to-many model with content-based routing rules.)
This is also a performance answer, not just a resilience one: buffering converts an unabsorbable spike into a manageable queue depth, and independent scaling means you size each tier for its load instead of the worst tier's load. Choosing the signal is itself a tested skill: web fleets behind a load balancer scale well on request count per target, worker fleets on queue backlog, CPU-bound processors on utilization — the correct metric is whichever one moves first when the workload's real demand moves. On the exam, "traffic spikes cause lost requests" or "the processing tier cannot keep up during bursts" points at the queue-plus-independent-scaling redesign, with the SQS backlog metric driving the worker fleet.
Levers outside the fleet: edge offload, hibernation, and the decision tables
Two levers improve compute performance without touching a scaling policy. Edge offload: putting CloudFront in front of an application caches static and cacheable dynamic responses at edge locations, so a large share of requests never reaches the origin at all — the origin fleet shrinks, latency drops for global users, and traffic spikes hit the CDN's capacity instead of yours. When a scenario describes origin servers strained by global read-heavy traffic, "add CloudFront" can outrank "add instances" — it removes the demand rather than absorbing it. At recognition level, know that light request-manipulation logic can run at the edge itself (CloudFront Functions and Lambda@Edge), moving small pieces of compute next to the user so the origin fleet is not scaled for work the edge can do. Contrast AWS Global Accelerator: it gives an application static anycast IP addresses and carries users' TCP or UDP traffic over the AWS global network to the nearest healthy Regional endpoint, improving the network path and failover — but it caches nothing, so every request still reaches the origin. For read-heavy repeat content, CloudFront reduces origin load; Global Accelerator does not. Hibernation is the third lever: in a warm pool's Hibernated state (previous section), standby instances resume with memory, caches, and long-lived processes intact, so scale-out skips slow in-memory initialization.
The two lookups that resolve most Task 3.2 questions:
| Scenario phrase | Scaling policy |
|---|---|
| "Keep average CPU (or requests per target) at a set level, simplest to manage" | Target tracking |
| "Different scaling amounts for different breach sizes" | Step scaling (one alarm, step adjustments per range) |
| "Traffic rises every weekday at business open" (fixed, known clock) | Scheduled scaling |
| "One-off event at a known date and time" | Scheduled action raising minimum/desired capacity (plus target tracking) |
| "Recurring daily/weekly pattern; instances take minutes to initialize; reactive scaling lags" | Predictive scaling in forecast-and-scale mode (paired with target tracking) |
| "Scale consumers with queue depth" | Target tracking on a custom backlog-per-instance metric |
| "Group overshoots while new instances boot" | Default instance warmup (not the health check grace period) |
| "Instances take many minutes to initialize before they can serve" | Warm pool (Stopped, Running, or Hibernated by cost vs state) |
| Workload phrase | Compute service |
|---|---|
| Event-driven tasks finishing in minutes, no servers | Lambda |
| Containers, least infrastructure management | Fargate (under ECS or EKS) |
| Kubernetes named as a requirement | EKS |
| Thousands of long-running batch jobs, queued and prioritized | AWS Batch |
| Spark / Hadoop / Hive workloads on a cluster you size | EMR on EC2 (transient if it should end after its steps) |
| Spark / Hive jobs, no instance types to choose, no cluster left running | EMR Serverless |
| Containerized job on a timetable, no idle servers | ECS task on Fargate started by EventBridge Scheduler |
| GPU containers on ECS, least management, no custom AMI | ECS Managed Instances |
| Specific hardware, GPUs with a custom AMI, OS control | EC2 (family by bottleneck; ASG capacity provider for ECS) |
| Every message delivered to several independent consumers | SNS topic fanning out to one SQS queue per consumer |
Exam questions hide these rows inside operational stories; extraction is the skill. When two rows fit, the qualifier arbitrates — LEAST overhead favors the serverless row, MOST cost-effective favors Spot-friendly Batch or right-sized families.
Worked scenarios: reasoning through Task 3.2 questions
Scenario 1. A ticketing platform's traffic climbs steeply every weekday from early morning to a midday peak, in a pattern that recurs week after week but whose height varies with the events on sale. Application instances take several minutes after launch to warm their caches before serving efficiently. Users report slow responses during the morning ramp. Which scaling approach fixes this with the LEAST ongoing effort?
Reasoning: The complaint is lag: by the time reactive policies see the metric move, demand has outrun capacity, and the minutes-long warm-up widens the gap. That eliminates target tracking and step scaling alone — both are reactive; step scaling just reacts in fancier increments. Scheduled scaling tempts because the clock recurs, but the magnitude varies with the sales calendar: a fixed schedule either over-provisions quiet days or under-provisions big ones, and someone has to keep editing it — failing LEAST ongoing effort. The pattern is recurring and learnable, and capacity must exist before demand: that is predictive scaling, forecasting from history and launching instances ahead of the ramp, paired with target tracking to correct forecast error in real time. Two details complete it: the policy must be in forecast and scale mode (forecast only changes nothing), and the group's default instance warmup should match the cache-warming time so new capacity is measured honestly. Recurring shape, variable magnitude, slow-to-ready instances — those three clues together are the predictive-scaling signature.
Scenario 2. A research company runs nightly genomics analyses: thousands of independent containerized jobs, each running two to six hours, submitted in a burst and processed under a strict budget. What compute architecture fits with the least management?
Reasoning: Run the two-step. Duration first: two to six hours obliterates Lambda — the 15-minute cap is absolute, and no decomposition story is offered. Framework second: nothing says Spark or Hadoop, so EMR is solving a problem this workload doesn't have. A self-managed Auto Scaling group with a hand-built job queue could work, but it recreates scheduling, prioritization, and scale-to-zero by hand — the classic LEAST-management elimination. AWS Batch is purpose-built for exactly this shape: submit the burst to a job queue, let a managed compute environment provision capacity, run the containers, and scale to zero at dawn. The strict budget adds the finisher: configure the compute environment to use Spot Instances — interruption-tolerant, retryable batch jobs are Spot's ideal tenant. Batch on Spot answers both the qualifier and the budget in one move.
Tip. SAA-C03 tests Task 3.2 with traffic-shaped scenarios: a workload description, a pain point (lag during ramps, lost requests in bursts, slow functions, over-scaling while instances boot, budget pressure), and a qualifier like LEAST operational overhead or MOST cost-effective. The most repeatable shapes are the scaling-policy match — recurring-but-variable patterns to predictive (forecast and scale), fixed clocks and one-off events to scheduled actions on minimum and desired capacity, steady utilization goals to target tracking, graduated responses to step scaling, queue depth to a backlog-per-instance metric — and the compute-selection two-step, where a job's duration eliminates Lambda at 15 minutes and a named big-data framework splits EMR (or EMR Serverless) from AWS Batch. Expect near-twins that sound alike but do different jobs: default instance warmup vs health check grace period vs cooldown; provisioned concurrency vs SnapStart vs more memory; Fargate vs ECS Managed Instances vs an Auto Scaling group capacity provider; Compute Optimizer vs Trusted Advisor; SNS fan-out vs one shared queue; CloudFront vs Global Accelerator. Distractors scale on CPU when the real signal is a queue, hand-build what Batch or Fargate provides managed, pick EKS with no Kubernetes requirement, or raise Lambda memory past the point where single-threaded code can use it.
- Pick the compute service by duration, trigger, and management appetite: event-driven and under 15 minutes = Lambda; containers with least overhead = Fargate; hardware control = EC2; queued heavy jobs = AWS Batch; named big-data frameworks = EMR, or EMR Serverless when no cluster should be chosen or kept running.
- Over 15 minutes rules out Lambda — container-image functions included; a named framework (Spark/Hadoop/Hive) means EMR; otherwise batch jobs mean AWS Batch (Spot for retryable work), and a scheduled container job is an ECS task on Fargate started by EventBridge Scheduler.
- Choose EC2 families by the bottleneck resource — C for CPU, R/X for memory, I/D for local storage I/O, P/G for accelerators, M as the balanced default; right-size with Compute Optimizer, which recommends types in both directions, unlike Trusted Advisor's idle-instance check.
- Lambda memory sets CPU: more memory can make a function faster and cheaper, but single-threaded code stops speeding up near 1,769 MB (one vCPU). Cold starts are fixed by provisioned concurrency (paid, always warm) or SnapStart (snapshot restore), never by memory.
- Target tracking is the default; step scaling = one alarm with step adjustments per breach range; simple scaling = one adjustment plus a cooldown; scheduled = known times (raise min/desired, not just max); predictive = recurring patterns, in forecast-and-scale mode.
- Default instance warmup keeps booting instances out of the metrics while counting their capacity; the health check grace period only delays health checks; warm pools (Stopped, Running, Hibernated) pre-initialize slow instances.
- Scale worker fleets on backlog per instance with a target of acceptable latency ÷ per-message processing time — raw queue length is not proportional to fleet size.
- Consumers on one SQS queue compete for messages; when every service must see every event, fan out through SNS to one queue per consumer. ECS vs EKS turns on a Kubernetes requirement; GPUs rule out Fargate, and a custom AMI rules out ECS Managed Instances.
Frequently asked questions
What is the difference between target tracking and step scaling?
Target tracking works like a thermostat: you choose a metric and a target value — keep average CPU at 50%, keep requests per target at a set level — and the Auto Scaling group creates its own alarms and adds or removes instances to hold it. Step scaling makes you define the response yourself: a step scaling policy is attached to one CloudWatch alarm, and its step adjustments set different capacity changes for different ranges of breach above that alarm's threshold, so a small breach adds a little capacity and a large one adds a lot. Simple scaling, the older form, makes one fixed adjustment per alarm and then waits out a cooldown. Use target tracking by default, and step scaling when graduated responses to known load levels are required.
When should I use predictive scaling vs scheduled scaling?
Both act before demand arrives; they differ in who decides the timing and size. Scheduled scaling follows actions you write: at a set date and time it changes the group's minimum, maximum, or desired capacity — right when timing and amount are known, including a one-off event such as a product launch or a filing deadline (raise minimum and desired capacity; raising only the maximum launches nothing). Predictive scaling forecasts from recurring daily and weekly patterns in recent history and provisions ahead of the predicted ramp — right when the pattern repeats but its size varies, especially when instances are slow to initialize. It cannot predict a one-off event, and it only changes capacity in forecast-and-scale mode (forecast-only, the default, just reports). Pair either with target tracking.
How does Lambda memory affect CPU and performance?
Memory is Lambda's only performance dial: CPU and network throughput scale with the memory you allocate, so raising memory is how you give a function more compute even if it never uses the extra RAM. At 1,769 MB a function gets the equivalent of one vCPU; higher settings add vCPUs (up to six at 10,240 MB), which only help code that runs in parallel — single-threaded CPU-bound code stops getting faster around 1,769 MB, so going higher just costs more. Because Lambda bills memory multiplied by duration, more memory can shorten a run enough that cost stays flat or drops. Memory does not fix cold starts; provisioned concurrency or SnapStart does.
When should I use AWS Batch instead of Lambda?
Draw the line at duration and shape. Lambda fits small, independent, event-triggered units of work that each finish within its hard 15-minute maximum — image processing per upload, per-record stream handling, API backends. AWS Batch fits jobs: large volumes of queued work where each item runs minutes to hours, needs scheduling, priorities, or dependencies, and benefits from right-sized compute that scales with the queue and down to zero afterwards — transcoding, simulations, genomics, financial modeling. Batch also runs on Spot Instances for major savings on interruption-tolerant work. If any single unit of work can exceed 15 minutes and cannot be decomposed, Lambda is disqualified and Batch is the default answer.
Should I use ECS or EKS for containers?
Choose by stated requirements. Amazon ECS is the AWS-native orchestrator: simpler to run, tightly integrated with IAM, load balancing, and CloudWatch, and the default when a scenario just needs containers orchestrated with minimal complexity. Amazon EKS is managed Kubernetes, and it wins when Kubernetes itself is the requirement — existing manifests, Helm charts, or operators must carry over, or the organization needs Kubernetes portability. Where the tasks run is a separate choice: Fargate for no instances at all (but no GPUs), ECS Managed Instances for AWS-managed EC2 capacity including GPU types (but no custom AMIs), or an Auto Scaling group capacity provider when you need your own AMI and full host control.
How do I scale EC2 instances based on an SQS queue?
Scale on backlog per instance, not raw queue length. Divide the queue's ApproximateNumberOfMessagesVisible by the number of in-service instances, publish it as a custom CloudWatch metric (or compute it with metric math in the policy), and attach a target tracking policy. The target is the backlog one instance can carry while meeting your latency goal: acceptable latency divided by average processing time per message — 100 seconds ÷ 2 seconds per message gives a target of 50. Raw queue length does not fall in proportion as instances are added, so a target on it scales erratically; backlog per instance does, so the policy stays correct at any fleet size.
What is an EC2 Auto Scaling warm pool, and which state should it use?
A warm pool is a set of pre-initialized EC2 instances kept beside an Auto Scaling group, so scale-out takes ready instances from the pool instead of launching and configuring new ones from scratch — the fix when initialization takes many minutes. Pooled instances wait in one of three states. Stopped costs only EBS storage (and any Elastic IPs) but clears RAM, so in-memory state reloads on start. Running is fastest but bills full instance hours. Hibernated saves RAM to the encrypted EBS root volume, so caches and loaded datasets survive, at the cost of EBS storage (including the saved RAM) and any Elastic IPs — no instance hours. Choose Stopped when the slow work is on disk, Hibernated when it is in memory, Running only when instant readiness outweighs idle cost.
What is the difference between default instance warmup and the health check grace period?
They solve different problems. Default instance warmup is a scaling setting: for the warmup period after an instance launches, its usage data is left out of the Auto Scaling group's aggregated metrics used by target tracking and step scaling, while the instance still counts toward current capacity — so the group stops adding instances in response to load that the new ones will soon absorb. AWS strongly recommends setting it. The health check grace period is a health setting: it only delays health checks on a new instance so a slow boot isn't mistaken for failure and replaced. It has no effect on scaling metrics, so it cannot stop a group from over-scaling.
Source
This lesson covers the "Design High-Performing Architectures" domain of the official SAA-C03 exam guide. Vendors revise their guides — check the source for the current version.
- AWS Certified Solutions Architect – Associate (SAA-C03) exam guide — Amazon Web Services
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.