AWS SAA-C03: Compute, Auto Scaling & Instance Management — Study Guide

Part of the AWS SAA-C03 Complete Study Guide. Practice these concepts with verified answers in the AWS/Amazon exam hub, or take timed practice tests on ExamRoll.io.

EC2 Instance Types and AMIs

Selecting the right EC2 instance family is the foundation of a well-architected compute layer, because family, generation, and size together determine CPU architecture, memory-to-vCPU ratio, network bandwidth, and available accelerators. General purpose (M6i, M7g, T-series) suits balanced web tiers and mixed workloads. Compute-optimized (C7i, C7gn) fits CPU-bound simulations, encoding, batch, and web front-ends. Memory-optimized (R7i, X2idn) targets in-memory databases and caches. Storage-optimized (I4i, D3) targets NoSQL, HDFS, and data warehousing. Accelerated (P5, G5, Trn1, Inf) provides GPUs or ML silicon. Graviton (g suffix) instances typically deliver 20–40% better price-performance for scale-out workloads that compile cleanly to ARM64.

The T-family is burstable and defaults to standard mode, where CPU credits accumulate while idle and drain during bursts; when credits are exhausted, performance throttles to the baseline. For workloads with unpredictable spikes — small web tiers, dev/test, or Elastic Beanstalk environments backing a spiky front end — T-instances in unlimited mode let the instance borrow credits and bill a small surcharge per vCPU-hour of overage, avoiding user-visible throttling. This is why Beanstalk environments with brief CPU saturation are usually fixed by enabling unlimited mode rather than upgrading to a costlier compute-optimized family. Reserving T-series for genuinely spiky, low-average workloads matters: sustained CPU on the standard mode exhausts credits within minutes.

Vertical scaling (moving to a bigger size within the family) is bounded by the largest available instance, forces downtime to change, introduces a single point of failure, and cannot spread the workload across Availability Zones. Whenever load varies significantly, horizontal scaling with an Auto Scaling group is the correct answer.

Golden AMIs bake application code, runtime, and dependencies into the root snapshot, producing fast, deterministic launches — critical when Auto Scaling reacts to a spike. Bootstrapping through user-data on every launch adds minutes of latency during exactly the wrong moment.

Storage Choices: Instance Store vs EBS, Snapshots, and Fast Snapshot Restore

Instances offer two storage substrates: instance store (ephemeral NVMe attached to the physical host) and Amazon EBS (network-attached block). Instance store delivers the lowest possible latency but its data is destroyed on stop, hibernate, terminate, or hardware failure. Treating it as durable storage is a common and dangerous mistake — it is only appropriate for scratch space, buffers, caches, or replicated data such as an HDFS data node whose replicas exist on other nodes. Durable data belongs on EBS, backed by snapshots stored in S3.

Snapshots are incremental and independent once created, so restoring one to a new volume never affects the source. The canonical way to duplicate a large production dataset into a test environment is to snapshot the source volume and create new volumes from that snapshot. However, volumes restored from snapshots lazy-load blocks from S3 on first read, causing significant I/O latency until each block is hydrated. The same lazy-loading affects new instances launched from AMIs whose root snapshots are large — they appear sluggish for several minutes after launch.

EBS Fast Snapshot Restore (FSR) eliminates that penalty. Enable FSR on the snapshot for the AZs where the ASG launches instances, and volumes created from it deliver full provisioned performance immediately:

aws ec2 enable-fast-snapshot-restores \
  --availability-zones us-east-1a us-east-1b \
  --source-snapshot-ids snap-0123456789abcdef0

This is essential when scale-out events must add capacity in seconds rather than minutes.

Enhanced Networking with ENA and EFA

Advertised network throughput scales with instance size but is only achievable when the Elastic Network Adapter (ENA) driver is present. ENA provides SR-IOV-based enhanced networking, supporting up to 200 Gbps on newer instances. Modern AMIs (Amazon Linux 2, recent Ubuntu, Windows) ship with ENA enabled; verify with:

aws ec2 describe-instances --instance-ids i-0abc \
  --query 'Reservations[].Instances[].EnaSupport'

modinfo ena | grep version
ethtool -i eth0

Without ENA, instances silently fall back to lower throughput and higher jitter, undermining any low-latency design regardless of placement group choice.

For microsecond-scale collectives — CFD, weather modeling, molecular dynamics, large-model training — add Elastic Fabric Adapter (EFA). EFA exposes an OS-bypass transport (Libfabric) to MPI and NCCL, avoiding the kernel network stack entirely. EFA only delivers its benefit inside a cluster placement group on supported instance types (c7gn, hpc7a, p5), and requires the EFA driver plus a compatible MPI (Open MPI, Intel MPI) or NCCL build.

aws ec2 create-placement-group --group-name hpc-cg --strategy cluster
aws ec2 run-instances --instance-type hpc7a.96xlarge \
  --placement GroupName=hpc-cg \
  --network-interfaces InterfaceType=efa,DeviceIndex=0,SubnetId=subnet-abc

Placement Groups

Placement groups control the physical topology of instances:

TypeLayoutBest fitConstraint / failure impact
ClusterSame rack, low-latency 10/25/100 Gbps spineHPC, MPI, low-latency trading, tightly-coupled analyticsSingle AZ; rack failure hits all
PartitionUp to 7 partitions per AZ, isolated hardwareHDFS, Cassandra, KafkaPartition-level isolation
SpreadEach instance on distinct hardware, max 7 per AZSmall critical fleetsHard instance-count limit

Choosing wrongly wastes the feature. A cluster group cannot span AZs — it is intra-AZ by design. Spread cannot host a hundred web servers because of the seven-per-AZ ceiling. Partition placement, not spread, is the correct tool for a 40-node Kafka cluster because it aligns replica placement with fault domains. For general high availability, do not confine an ASG to one placement group in one AZ — spread the ASG across multiple AZs.

For streaming analytics or MPI workloads needing minimum node-to-node latency, the correct combination is a cluster placement group plus ENA-enabled instances; cluster collapses the hop count and ENA delivers the packets-per-second capacity needed to realize that latency benefit.

Auto Scaling Groups and Launch Templates

An Auto Scaling group (ASG) is the runtime unit that maintains a desired count of EC2 instances across one or more AZs, defined by three integers — MinSize, DesiredCapacity, MaxSize — and a list of subnets. The ASG itself does not describe what to launch; that is the responsibility of a launch template, the modern replacement for launch configurations. Launch templates support versioning, mixed instance policies, Spot/On-Demand mixing, IMDSv2 enforcement, capacity reservations, and unlimited-mode T-instances. A launch template references an AMI, instance type(s), security groups, IAM instance profile, user data, and block device mappings.

The canonical stateless-web pattern is AMI + Launch Template + ASG + ALB. The AMI provides fast boot; the ALB (or NLB for TCP/UDP) distributes traffic and drives health checks; the ASG registers new instances into the target group automatically and terminates unhealthy ones. Multi-AZ deployment (at minimum two AZs, three for quorum systems) is mandatory — an ASG bound to a single subnet cannot survive an AZ outage because the ASG itself cannot launch replacements while the AZ is impaired.

MyASG:
  Type: AWS::AutoScaling::AutoScalingGroup
  Properties:
    MinSize: 2
    MaxSize: 20
    DesiredCapacity: 4
    VPCZoneIdentifier: [subnet-a, subnet-b]
    TargetGroupARNs: [!Ref AppTargetGroup]
    LaunchTemplate:
      LaunchTemplateId: !Ref AppLT
      Version: !GetAtt AppLT.LatestVersionNumber
    HealthCheckType: ELB
    HealthCheckGracePeriod: 120

Scaling Policies: Target Tracking, Step, Scheduled, Predictive

ASG scaling has four modes, each solving a different problem:

Dynamic scaling is inherently lagging — it reacts only after a metric breaches, and new instances then need minutes to boot, register, and warm up. For a workforce app where users all arrive at 09:00 and see 2–3 hours of slowness while the ASG catches up, dynamic scaling alone is the wrong answer. Layer scheduled actions ahead of the peak to raise MinSize and DesiredCapacity before the demand arrives:

ScheduledAction:
  AutoScalingGroupName: web-asg
  ScheduledActionName: pre-sale-warmup
  Recurrence: "0 8 * * *"
  MinSize: 20
  DesiredCapacity: 30
  MaxSize: 200

Then let target tracking absorb residual variability. Predictive scaling is the right pick when the peak’s shape is stable but its exact timing drifts day-to-day. For predictable non-production shutdowns (dev environments off nights and weekends), a scheduled action to desired=0, min=0 on Friday evening and back up Monday morning is the least-overhead solution — the ASG itself is the schedule engine, no Lambda or EventBridge glue required.

The choice of metric matters as much as the choice of policy. CPU works for CPU-bound web workloads, but for backlog-driven workers pulling from SQS you must scale on queue depth, not CPU: a worker blocked on I/O may show 10% CPU while millions of messages accumulate. Use ApproximateNumberOfMessagesVisible per instance, exposed as a custom metric or via the built-in SQSQueueBacklogPerInstance target:

backlog_per_instance = messages_visible / running_instances
target = acceptable_latency_seconds / avg_processing_seconds_per_msg
TargetTrackingConfiguration:
  CustomizedMetricSpecification:
    MetricName: BacklogPerInstance
    Namespace: MyApp/Scaling
    Statistic: Average
  TargetValue: 100

Similarly, latency-sensitive HTTP tiers should track TargetResponseTime or RequestCountPerTarget; memory-, disk-, or downstream-latency-bound apps should publish a custom metric that reflects the real bottleneck. Scaling on CPU when CPU is not the constraint produces exactly the failure mode where instances never scale out, queues grow unbounded, and the ALB returns 5xx.

Health Checks and Lifecycle Hooks

ASGs default to EC2 status checks, which catch hypervisor failure but not application failure. Enabling ELB health checks on the ASG delegates the replacement decision to the load balancer’s application-level probe, which is essential when the OS is fine but the process is wedged. Set HealthCheckGracePeriod long enough for user data to complete; otherwise fresh instances get terminated mid-bootstrap in a loop.

The load balancer choice affects the meaning of “healthy”:

FeatureALBNLB
Layer7 (HTTP/HTTPS)4 (TCP/UDP/TLS)
Health checksHTTP/HTTPS with status codes and pathsTCP by default; HTTP optional
RoutingHost/path/header rulesFlow hash
Best forWeb/API servicesUltra-low latency, static IPs, non-HTTP

An HTTP application behind an NLB doing only TCP health checks will keep serving traffic from an instance that accepts connections but returns 500s. Switch to an ALB — or configure HTTP health checks on the NLB — to restore meaningful signaling.

Lifecycle hooks pause instances in Pending:Wait or Terminating:Wait so external automation can act. On launch, a hook lets you register with a config-management system, warm caches, or pull secrets before the ALB sends traffic. On terminate, a hook lets you drain sessions, flush logs, and deregister from a service mesh. Hooks emit EventBridge events; handlers must call CompleteLifecycleAction or the hook times out into its default (CONTINUE or ABANDON).

Warm Pools and Hibernation

Cold-launch time is a real problem for applications that load large models, warm caches, or JIT-compile before serving. A warm pool is a pre-initialized reserve attached to the ASG: instances launch, run bootstrap, then are stopped, kept running, or hibernated, held in the pool until the ASG scales out. Pulling from the pool skips minutes of boot.

Hibernation suspends the OS to the encrypted EBS root volume so JVM heap, ML weights, and OS page cache come back on resume. Requirements: encrypted root volume large enough to hold RAM, instance RAM ≤ 150 GB on a supported family, and HibernationOptions.Configured = true at launch. A warm pool with PoolState: Hibernated combines both — instances cost nothing for compute while stopped and resume in seconds with populated memory. This is the correct pattern when an application “takes a long time to load memory before being productive.”

Auto-Recovery for Non-Scalable Workloads

Not every workload scales horizontally. Legacy applications with MAC-tied licenses, file-based locks, or in-memory session state without a shared store cannot run on more than one instance — spinning up additional nodes causes data corruption or license violations. For these, resilience comes from automatic recovery, not scale-out.

Two patterns work. A CloudWatch alarm on StatusCheckFailed_System paired with the EC2 recovery action preserves instance ID, private IP, Elastic IP, and EBS attachments across the underlying-host failure. Simpler still: an ASG with MinSize=MaxSize=1 spanning multiple AZs replaces a failed instance and, unlike the recovery action, can survive AZ failure — provided state is externalized off the root volume or the AMI is rebuildable.

Load Balancers and Subnet Placement

ALB operates at L7, terminating HTTP/HTTPS, offering host/path/header routing, HTTP/2, WebSockets, WAF/Cognito/OIDC integration. NLB operates at L4, preserves client IP, supports static IPs and TLS passthrough, PrivateLink, and sustains millions of connections per second. Gateway Load Balancer inserts third-party appliances (firewalls, IDS) into the traffic path.

Subnet placement is where architectures most often break. An internet-facing ALB must be attached to public subnets — subnets with a 0.0.0.0/0 route to an Internet Gateway — one per AZ where targets live. Targets themselves stay in private subnets. If the ALB is placed in private subnets, or the “public” subnets lack an IGW default route, clients see connection timeouts. Target reachability requires the target security group to allow inbound from the ALB’s security group on the target port; no NAT is needed between ALB and targets in the same VPC.

Client → IGW → ALB (public subnets, SG: allow 443 from 0.0.0.0/0)
              → Targets (private subnets, SG: allow 8080 from ALB-SG)

Enable ELB health checks on the ASG so unhealthy targets are replaced, not merely deregistered. Cross-zone load balancing (default on ALB, opt-in on NLB) evens the request distribution regardless of instance count per AZ. Route 53 must resolve to the ALB via an alias record (or weighted/latency policy across multiple ALBs) — never point Route 53 at individual EC2 IPs, because a failed instance keeps receiving traffic until TTL expiry and the ASG replacement has a different IP.

Purchasing Models and Mixed-Instance Fleets

Purchase model choice is the single largest lever for reducing EC2 spend, independent of architecture pattern.

ModelCommitmentDiscount vs ODBest for
On-DemandNone0%Unpredictable, short-lived, dev
Reserved Instance (Standard)1 or 3 yr, instance-family lockedUp to ~72%Steady-state, known family/region
Reserved Instance (Convertible)1 or 3 yr, exchangeableUp to ~54%Steady but family may change
Compute Savings Plan1 or 3 yr, $/hr commitUp to ~66%Flexible across EC2 family/region/OS, Fargate, Lambda
EC2 Instance Savings Plan1 or 3 yr, family+region lockedUp to ~72%Steady workload in one family
Scheduled RIRecurring windowModerateNightly batch, known windows
SpotNone; 2-min interruption noticeUp to ~90%Fault-tolerant, stateless, batch, CI

The rational strategy: baseline load on Reserved Instances or a Savings Plan, bursts on On-Demand, fault-tolerant work on Spot. In an ASG, this is expressed as a mixed-instances policy:

MixedInstancesPolicy:
  LaunchTemplate:
    LaunchTemplateSpecification:
      LaunchTemplateId: lt-0abc123
      Version: $Latest
    Overrides:
      - InstanceType: m5.large
      - InstanceType: m5a.large
      - InstanceType: m6i.large
      - InstanceType: m6a.large
  InstancesDistribution:
    OnDemandBaseCapacity: 4          # covered by Savings Plan
    OnDemandPercentageAboveBaseCapacity: 20
    SpotAllocationStrategy: price-capacity-optimized

Diversifying instance types deepens the Spot pool and reduces correlated interruptions. price-capacity-optimized (or capacity-optimized) balances price with pool depth so instances are less likely to be reclaimed.

Spot is appropriate for stateless, checkpointable, retriable, or horizontally redundant workloads: web workers behind an ALB, Batch jobs with automatic retry, Spark executors, CI runners. It is not appropriate as the sole capacity for critical always-on services, stateful primary databases, or leader nodes with no recovery path — two-minute notice cannot guarantee safe shutdown, and correlated fleet-wide reclamation of a specific instance type is a real failure mode. When the requirement says “must not be disrupted,” Spot is disqualified.

The inverse trap is applying RIs or Savings Plans to genuinely variable workloads: you pay the hourly commit whether used or not, so a workload running 40 hours a week under a 168-hour commit wastes 76% of the reservation. When capacity itself — not price — is the concern (event-driven surge, disaster recovery), use an On-Demand Capacity Reservation: a pure AZ-scoped guarantee at On-Demand rates, combinable with a Savings Plan for the discount.

Serverless Compute: Lambda and Fargate

Serverless shifts capacity management to the platform. Lambda suits event-driven, short-lived work: S3 ObjectCreated triggers, DynamoDB Streams, low-volume SQS consumers, API Gateway backends, glue logic. Memory (128 MB – 10,240 MB) provisions CPU proportionally, so tuning memory upward often reduces cost by shortening duration. Hard limits define its scope: 15-minute maximum execution, 10 GB memory, 10 GB /tmp, 250 MB unzipped deployment (or 10 GB via container image), 6 MB synchronous payload. Using Lambda for a 30-minute video transcode, a multi-hour ETL, or GPU training is architecturally wrong — the function times out mid-work and retry logic just multiplies the waste. For latency-sensitive paths, mitigate cold starts and VPC-attached ENI init time with Provisioned Concurrency.

Fargate runs containers without managing EC2 hosts. It is the right pick when tasks exceed 15 minutes, need custom runtimes, or fit ECS/EKS orchestration but the team does not want capacity to manage. Fargate is more expensive per vCPU-hour than EC2 Spot, so when steady-state utilization is high and predictable a mixed-instance ASG with Spot on ECS is cheaper. For bursty or unpredictable traffic, Fargate’s per-second billing wins.

For orchestration across many Lambdas, Step Functions is preferable to chaining through SNS/SQS because it gives visual execution history, retry semantics, centralized error handling, and durable state. Standard workflows bill per state transition and run up to a year; Express workflows optimize for high-volume, short-duration event processing.

For fan-out of thousands of parameterized containerized jobs — genomics, Monte Carlo, ETL — AWS Batch handles queuing, dependency resolution, retries, and provisioning across managed EC2, Spot, or Fargate. Jobs reference a job definition (container + vCPU/memory + IAM role) and Batch bin-packs them onto right-sized instances. Step Functions frequently orchestrates Batch, Lambda, and ECS within one state machine.

NeedService
Fan-out of thousands of independent containerized jobsAWS Batch
Coordinated multi-step workflow with branching/retriesStep Functions
Short event-driven code (<15 min, <10 GB memory)Lambda
Long-running or GPU/large-memory containerized workECS/EKS or Batch on EC2
Fine-grained autoscaled compute for HTTP APIsASG + ALB, or Lambda behind API Gateway

Elastic Beanstalk

Beanstalk is a managed PaaS that provisions the ALB, ASG, EC2 instances, and optionally RDS from an application bundle. It supports rolling, rolling-with-additional-batch, immutable, and blue/green deployments with CloudWatch integration. Scaling policies are exposed as environment options (metric, thresholds, min/max). Because Beanstalk defaults to burstable instance types, enabling T-unlimited mode is the low-effort fix for brief CPU saturation. Scheduled scaling actions attach to the Beanstalk-managed ASG like any other. Choose Beanstalk when the team wants standard web-app topology without hand-rolling CloudFormation and when rolling updates and versioned bundles match the release cadence.

Fleet Management with Systems Manager

SSH-and-bastion patterns are fragile and expensive to secure. AWS Systems Manager replaces them. The SSM Agent (pre-installed on Amazon Linux 2, Ubuntu, Windows) plus an instance profile granting AmazonSSMManagedInstanceCore is all that is needed.

aws ssm send-command \
  --targets Key=tag:Env,Values=prod \
  --document-name AWS-RunShellScript \
  --parameters 'commands=["yum -y update"]'

Scheduled Start/Stop Automation

For non-prod EC2 and RDS that must be off outside business hours, the low-maintenance pattern is EventBridge Scheduler → Lambda. A cron rule (cron(0 19 ? * MON-FRI *)) invokes a Lambda that stops tagged resources; a second rule at 07:00 starts them.

import boto3
ec2 = boto3.client('ec2'); rds = boto3.client('rds')

def handler(event, _):
    action = event['action']  # 'start' or 'stop'
    ids = [i['InstanceId'] for r in ec2.describe_instances(
        Filters=[{'Name':'tag:AutoStop','Values':['true']}]
    )['Reservations'] for i in r['Instances']]
    getattr(ec2, f'{action}_instances')(InstanceIds=ids)
    for db in rds.describe_db_instances()['DBInstances']:
        if any(t['Key']=='AutoStop' and t['Value']=='true' for t in db['TagList']):
            getattr(rds, f'{action}_db_instance')(DBInstanceIdentifier=db['DBInstanceIdentifier'])

This is serverless, has no fleet to patch, and scales by tag. The AWS Instance Scheduler solution works equivalently. A subtlety: a stopped RDS instance auto-starts after seven days, so the stop schedule must recur to re-stop it. Combined with ASGs whose scheduled actions zero out capacity on weekends, idle-hour cost approaches zero.

Networking Pitfalls Around Scaled Compute

NAT Gateway placement. A NAT gateway lives in one AZ. A fleet spanning three AZs that routes all egress through a single NAT in us-east-1a pays inter-AZ transfer on every packet from us-east-1b/us-east-1c, and loses egress entirely when us-east-1a degrades. The correct pattern is one NAT gateway per AZ, with each private subnet’s route table pointing to the NAT in its own AZ. This localizes traffic and removes the single-AZ failure mode.

NAT instances. A single EC2-based NAT becomes a bandwidth and PPS bottleneck as the fleet grows, and is a SPOF. Managed NAT Gateways scale to 100 Gbps per gateway. For AWS service traffic, VPC endpoints (Gateway for S3/DynamoDB, Interface for others) bypass NAT entirely, cutting cost and eliminating saturation risk during scale events.

Recurring Design Traps



All domains · Next: Containers

Practice Compute questions → · Timed practice on ExamRoll.io →

Pass the whole exam — not just this question

You found this answer. Get every verified question and explanation in one place, and save hours of prep. Free to start.

Pass your exam →

Browse Amazon →

Related guides

All-in-one access

One subscription. Every exam.

Every plan unlocks unlimited answer search, practice tests, AI explanations, and the full resource library — in 20+ languages.

Monthly
24.87
Just €0.83/day
Everything included:
  • Unlimited answer search
  • Unlimited practice tests
  • AI-powered explanations
  • Full resource library
  • 20+ languages
  • Weekly content updates
  • Rewards & referrals
  • Priority support
Start free trial

No credit card required*

Best value
12 months
179.87
Just €0.49/daySave 40%
Everything included:
  • Unlimited answer search
  • Unlimited practice tests
  • AI-powered explanations
  • Full resource library
  • 20+ languages
  • Weekly content updates
  • Rewards & referrals
  • Priority support
Start free trial

No credit card required*

✓ Free plan included · ✓ Cancel anytime · ✓ All plans unlock the full product