Scalable, Loosely Coupled Architectures: SQS, SNS, EventBridge, Step Functions
A loosely coupled architecture connects its components through managed intermediaries (queues, topics, event buses, load balancers and APIs) so that each component can scale, fail and change without dragging the others with it. SAA-C03 Task 2.1 covers the services that make this work: queuing with Amazon SQS, publish/subscribe with SNS, event routing with EventBridge, workflow orchestration with Step Functions, API Gateway as a managed front door, the compute choice among Lambda, Fargate and ECS or EKS on EC2, and the scaling levers around them: Auto Scaling policies, load balancers, caches, the edge, read replicas and the right storage type. The exam rarely asks what a queue is. It hands you a tightly coupled system that drops orders during a flash sale, or one event that five downstream systems must react to, and asks which design fixes it with the least operational overhead. By the end of this lesson you will be able to pick the integration service, compute layer, scaling policy and data-tier lever that a scenario calls for, and explain why each runner-up option loses.
On this page17 sections
- Why loose coupling means resilience and independent scaling
- SQS decisions: standard vs FIFO, visibility timeout, and dead-letter queues
- SQS in practice: retention, dead-letter queues, redrive, and Lambda consumers
- SNS pub/sub and the SNS-to-SQS fan-out pattern
- EventBridge: event routing, SaaS integration, and choosing among the three
- Step Functions: orchestrating multi-step workflows with retries
- API Gateway: the managed front door — and the integration decision table
- Compute selection: Lambda vs Fargate vs ECS/EKS on EC2
- Migrating applications into containers
- Purpose-built managed services: Secrets Manager, Parameter Store, and Transfer Family
- Scaling strategies: horizontal vs vertical, and choosing the Auto Scaling policy
- Load balancing: ALB vs NLB vs Gateway Load Balancer, and sticky sessions
- Caching strategies: lazy loading, write-through, TTLs, and where to cache
- The edge: CloudFront vs Global Accelerator
- Scaling the data tier: read replicas, Aurora endpoints, Multi-AZ, and RDS Proxy
- Storage types: object, file, and block
- Worked scenarios: reasoning through Task 2.1 questions
- Explain why loose coupling improves both resilience and scaling, and spot tight coupling inside a scenario
- Configure SQS correctly: standard vs FIFO, message group and deduplication IDs, visibility timeout, retention, dead-letter queues and Lambda consumers
- Select SQS, SNS, EventBridge, Step Functions or API Gateway from a scenario's requirements, including FIFO fan-out, cross-account events and Express vs Standard workflows
- Decide between Lambda, Fargate and ECS or EKS on EC2, and migrate an application into containers without losing state, logs or secrets
- Choose an Auto Scaling policy and a load balancer for a workload, and scale queue consumers on backlog per instance
- Apply caching strategies, CloudFront or Global Accelerator, read replicas and the right storage type (object, file, block) to the bottleneck a scenario names
Why loose coupling means resilience and independent scaling
Tight coupling means one component calls another synchronously and depends on that component being up, responsive, and correctly sized at that exact moment. A web tier that writes orders directly into a processing tier inherits every one of the processing tier's problems: if processing slows down, web requests time out; if processing crashes, orders are lost; if traffic spikes, both tiers must scale in lockstep or the whole chain fails. The failure of one component propagates to everything upstream of it. That propagation is what Task 2.1 asks you to design away.
Loose coupling inserts an intermediary — a queue, a topic, an event bus, a load balancer, an API — between components so that each side depends only on the intermediary, not on the other side's availability or capacity. Two benefits follow, and the exam tests both. First, resilience: if a consumer fails, work waits durably in the intermediary instead of being dropped, and the consumer catches up when it recovers. Second, independent scaling: each tier scales on its own signal — the web tier on request count, the worker tier on queue depth — so a spike in one place never forces an emergency resize of everything else.
Loose coupling is also why stateless design keeps appearing in this task. A component can only be scaled horizontally or replaced freely if any instance can handle any request, which means session state, uploaded files, and in-flight job data must live outside the instance — in ElastiCache or DynamoDB for sessions, S3 for objects, a queue for pending work. A multi-tier architecture (web, application, data) is the classic frame: each tier sits behind its own intermediary and scales independently, and microservices push the same idea further by giving each service its own deployment, data store, and scaling policy.
On the exam, this section is the diagnosis step. When a stem says requests are "lost during peak traffic," components "time out waiting for a downstream service," or two systems "must be updated together," you are being shown tight coupling — and the correct answer almost always introduces a managed intermediary rather than making the coupled components bigger.
SQS decisions: standard vs FIFO, visibility timeout, and dead-letter queues
Amazon SQS is the buffering intermediary, and the first decision is standard vs FIFO. A standard queue offers nearly unlimited throughput with at-least-once delivery and best-effort ordering: occasionally a message arrives twice or out of order, so consumers must be idempotent. A FIFO queue guarantees ordering within a message group and deduplicates sends inside a five-minute window. FIFO queues have lower default throughput limits, but high-throughput mode raises them a long way by spreading work across message groups, so "FIFO cannot scale" is rarely the deciding argument. The real question is whether the workload needs ordering or send-side deduplication at all, weighed against FIFO's higher price per request and the extra design work (group IDs, deduplication IDs). Default to standard; choose FIFO when the scenario says order matters or duplicate sends must be suppressed.
The message group ID decides what is ordered. FIFO ordering holds only inside one group. Messages in different groups are delivered in parallel to different consumers, while within a group, once a message (or a received batch of up to 10 from that group) is in flight, no further messages from that group are delivered until those are deleted or their visibility timeout expires. So the group ID should be the entity whose order matters: the account, the order, the device. One shared group ID for everything serialises the whole queue through one in-flight receive batch at a time; a random group ID per message gives you no ordering at all.
Deduplication works on sends, by deduplication ID. A producer either passes an explicit MessageDeduplicationId or turns on content-based deduplication, which uses a SHA-256 hash of the message body. Content-based deduplication fails when a retry changes the body (a timestamp, a request ID), because the hash changes; derive an explicit deduplication ID from a business key such as the order number instead. The group ID orders messages but never marks one as a duplicate. And deduplication does not stop redelivery: a message whose visibility timeout expires before it is deleted is delivered again, FIFO or not.
That is the visibility timeout. SQS does not delete a message when a consumer receives it; the message becomes invisible for the timeout while the consumer works, and the consumer deletes it after finishing. If the consumer crashes, the timeout expires and the message reappears for another consumer, which is how SQS retries. Set the timeout shorter than processing time and healthy consumers get interrupted by duplicates: "messages are processed twice" is fixed by raising the visibility timeout above the processing time, not by switching queue types. A consumer handling an unusually long message can extend that one message with ChangeMessageVisibility (up to 12 hours in total) instead of raising the timeout for the whole queue.
| Standard queue | FIFO queue | |
|---|---|---|
| Delivery | At least once; occasional duplicates | Duplicate sends removed within 5 minutes; redelivery after a visibility timeout still possible |
| Ordering | Best effort | Strict within a message group; groups run in parallel |
| Throughput | Nearly unlimited | Lower by default; high-throughput mode raises it |
| Choose when | Order does not matter and consumers are idempotent | Per-entity order or send-side deduplication is a requirement |
Finally, the pattern that makes SQS a resilience tool: the queue as a buffer. Put a queue between a web tier and a worker tier and a traffic spike becomes a backlog instead of an outage: the web tier acknowledges instantly, the queue absorbs the burst durably, and the worker tier scales on the backlog to drain it. Dead-letter queues, retention and Lambda consumers are covered in the next section.
SQS in practice: retention, dead-letter queues, redrive, and Lambda consumers
A queue only protects you for as long as it keeps messages. Message retention defaults to four days and can be set from 60 seconds to 14 days. If consumers are down longer than the retention period, the oldest messages are deleted unprocessed, so size retention to the longest outage you must survive. The visibility timeout does not extend retention; a message expires on its original schedule however many times it is received. (Kinesis Data Streams, by comparison, keeps records for 24 hours by default, extendable.)
A dead-letter queue (DLQ) catches poison messages. A redrive policy on the source queue moves any message received more than maxReceiveCount times into the DLQ, where you alarm on it and inspect it without blocking the main queue. Three details matter. The DLQ must be the same type as its source: a FIFO queue needs a FIFO DLQ, a standard queue a standard DLQ. For standard queues, a message keeps its original enqueue timestamp when it moves to the DLQ, so the DLQ's retention period should be longer than the source queue's, or messages that failed late can expire almost as soon as they arrive. And a redrive allow policy on the DLQ only controls which source queues may use it as their DLQ; it does not move anything.
After you fix the consumer, DLQ redrive moves the dead-lettered messages back to the source queue (or another queue) from the console or the StartMessageMoveTask API, with no custom copy code.
Lambda as the consumer. With an SQS event source mapping, Lambda polls the queue and invokes your function with batches. Four settings decide whether it behaves:
- Visibility timeout at least six times the function timeout (AWS's recommendation), so messages are not redelivered while a batch is still being processed or retried after throttling.
- Partial batch responses (
ReportBatchItemFailures): the function returns the IDs of the messages that failed, and only those return to the queue. Without it, one bad message makes the whole batch visible again and the successful ones are processed twice. - Maximum concurrency on the event source mapping (2 to 1,000) caps how many concurrent invocations that queue can drive, which is how you protect a downstream database. Reserved concurrency alone also caps the function, but by throttling it: throttled messages go back to the queue, count as receives, and can end up in the DLQ without ever failing.
- Provisioned concurrency keeps execution environments initialised so a predictable spike meets no cold starts; schedule it with Application Auto Scaling around known peaks. Reserved concurrency guarantees capacity but pre-warms nothing.
SNS pub/sub and the SNS-to-SQS fan-out pattern
When several independent systems must react to the same event, you need publish/subscribe, and on AWS that starts with Amazon SNS. A publisher sends one message to a topic; SNS pushes a copy to every subscriber: SQS queues, Lambda functions, HTTP/S endpoints, Amazon Data Firehose, and for notifications email, SMS and mobile push. The publisher knows nothing about the subscribers, so adding a consumer means adding a subscription, not changing producer code.
Keep the contrast with SQS sharp: an SQS message is consumed by one worker (competing consumers), while an SNS message is delivered to every subscriber. Three teams that each need every order event cannot share one SQS queue; they would take each other's messages.
SNS pushes and does not keep messages for later, which is why the standard pattern is SNS-to-SQS fan-out: subscribe one SQS queue per consumer. Each consumer gets its own durable buffer, retries, DLQ and scaling signal, and a consumer that goes offline for an hour simply accumulates a backlog while the others carry on.
Message filtering. A subscription filter policy delivers only matching messages to that subscriber. The filter policy scope can be the message attributes (the default) or the message body, so SNS can filter on fields inside a JSON payload without the publisher adding attributes.
Ordered fan-out with FIFO topics. When order must survive fan-out, use an SNS FIFO topic. SQS FIFO queues can subscribe only to SNS FIFO topics, and SNS FIFO topics deliver only to SQS queues (not directly to Lambda, HTTP endpoints or email); put Lambda behind the queue. The message group ID works as it does in SQS: set it to the entity whose order matters.
Encrypted queues. If a subscribed queue uses SSE-KMS, SNS must be able to use the key: the key policy has to allow sns.amazonaws.com kms:GenerateDataKey and kms:Decrypt. The AWS managed aws/sqs key's policy cannot be edited, so it cannot be used with SNS; use a customer managed key, or SSE-SQS (SQS-managed encryption, which is not a KMS key).
EventBridge: event routing, SaaS integration, and choosing among the three
Amazon EventBridge is an event bus with rules: producers put structured JSON events on a bus, rules match them by content (source, detail-type or any field in the payload), and route matches to targets, for example sending only orders above a set value to a fraud-check workflow. One event can match many rules, so EventBridge fans out too. Since SNS can also filter on the message body, the choice between them rests on sources and targets rather than on filtering alone:
- Sources: the default bus already carries AWS service events (an EC2 state change, an API call recorded by CloudTrail), and SaaS partner event sources deliver supported third-party applications' events onto a partner event bus with no polling or webhook code.
- Targets: EventBridge rules can start Step Functions executions, run ECS tasks, call API destinations (third-party HTTP APIs with managed auth and rate limits) and send to other event buses, along with Lambda, SQS, SNS and Kinesis. SNS cannot deliver to Step Functions.
Cross-account events take three pieces. In the sending account, a rule targets the receiving account's event bus (rules created since March 2023 must also specify an IAM role that allows events:PutEvents on that bus). The receiving bus needs a resource-based policy allowing the senders to put events; a condition on aws:PrincipalOrgID grants a whole AWS Organization, including accounts created later. And events arriving on the receiving bus go nowhere until a rule on that bus routes them to local targets.
Amazon EventBridge Scheduler is the separate, serverless way to invoke a target on a schedule (one-time, rate or cron expressions, with time zones and retries), replacing a cron job on an EC2 instance that exists only to trigger work.
Archive and replay records events from a bus and re-sends a chosen time window later, which is how you reprocess events after fixing a consumer bug. For S3, turning on Amazon EventBridge delivery in a bucket's event notification settings sends every object event to the default bus, where rules can match on any field (key suffix, size, bucket) across many buckets and route to many targets. Native S3 notifications filter only by prefix and suffix per bucket, and overlapping filters for the same event type cannot go to different destinations.
The selection logic compresses to this. Choose SQS when one consumer group must reliably work through a backlog. Choose SNS when many subscribers need the same message with high throughput and minimal latency, typically fanning into SQS. Choose EventBridge when events come from AWS services or SaaS partners, when targets include Step Functions, ECS tasks or other accounts' buses, when routing rules belong in one central place, or when you need archive and replay.
Step Functions: orchestrating multi-step workflows with retries
Queues and topics implement choreography — each service reacts to events with no central coordinator. Some processes instead need orchestration: an explicit, ordered, stateful workflow where step 3 runs only if step 2 succeeded, failures at any point are retried with backoff, and someone can see exactly where each execution stands. That is AWS Step Functions: you define a state machine of tasks (Lambda functions, ECS tasks, direct SDK integrations with over two hundred services), and Step Functions runs it with sequencing, Choice branching, Parallel branches, Map for fan-out over a collection, and per-step Retry and Catch policies with exponential backoff.
The exam's favorite framing is Step Functions versus chaining Lambda functions — one function synchronously invoking the next, or a tangle of functions wired through queues. Chaining fails in ways the exam wants you to articulate: a synchronous chain accumulates all the runtime into the first function's 15-minute limit and bills you for idle waiting; there is no central place to define retries or compensation when step 4 fails; and the workflow's current state lives nowhere, so a crash mid-chain leaves orphaned work. Step Functions externalizes the state, gives every step its own retry policy, and shows the execution visually. Trigger phrases: "multi-step process," "coordinate multiple Lambda functions," "retry individual steps," "if the step fails, the whole process must roll back or alert."
Step Functions also handles long waits and human approval: a task can pause on a callback task token (.waitForTaskToken) until an external system or an approver responds, and a .sync integration waits for a job (an ECS task, a Glue job) to finish. Both patterns need the Standard workflow type. The two types differ like this:
| Standard | Express | |
|---|---|---|
| Maximum duration | 1 year | 5 minutes |
| Execution semantics | Exactly once | At least once (asynchronous); at most once (synchronous) |
Callback (.waitForTaskToken) and .sync | Supported | Not supported |
| Pricing | Per state transition | Per execution, duration and memory |
| Fits | Long-running, auditable business processes, human approval | High-volume, short event processing (IoT ingestion, stream transforms) |
Processing many items: Inline Map vs Distributed Map. An Inline Map state runs iterations inside the parent execution, up to 40 at a time, and every iteration's events count toward that execution's 25,000-event history limit, so it suits dozens or hundreds of items. A Distributed Map reads its items from S3 (a list of objects, a CSV or JSON file, an S3 Inventory report) and runs each item or batch as a child workflow execution, up to thousands in parallel, with MaxConcurrency to protect downstream services; it is built for millions of objects. If the job is simply one operation per object (copy, tag, restore, invoke a Lambda function) with no workflow logic, S3 Batch Operations is the simpler tool.
Keep the boundary with SQS crisp, because the exam blurs it deliberately. SQS decouples two parties and buffers work; it has no notion of sequence across steps. Step Functions coordinates many steps with dependencies, branching, and error handling. If the scenario is "absorb bursts of independent jobs," queue it. If it is "run these five operations in order with retries and a failure path," orchestrate it.
API Gateway: the managed front door — and the integration decision table
Amazon API Gateway decouples clients from backends the way a queue decouples producers from consumers. Clients call a stable API contract; behind it you can swap a Lambda function for a container service, or route paths to different microservices, without any client changing. It also takes over cross-cutting work: throttling, authentication (IAM, Amazon Cognito, Lambda authorizers, JWT authorizers on HTTP APIs) and, on REST APIs, validation and caching.
API Gateway in front of Lambda is the standard serverless web or mobile backend: no servers to manage, capacity that scales per request and pay-per-call pricing on both sides. Its limits come from Lambda (a 15-minute maximum per invocation) and from API Gateway's own integration timeout, so long-running work belongs behind a queue or a Step Functions workflow rather than in the synchronous request.
API Gateway can integrate directly with AWS services, with no Lambda in between: a method can send to SQS or start a Step Functions execution. Both API types can do this (HTTP APIs have a first-class SQS-SendMessage integration), which makes "accept requests and queue them with least overhead" a no-code answer.
REST API vs HTTP API. HTTP APIs are cheaper and lower latency with fewer features. REST APIs add the controls the exam tests:
- Usage plans with API keys: per-client throttle limits and quotas (requests per day, week or month). These exist only on REST APIs.
- Stage caching: responses cached for a TTL (300 seconds by default, up to 3,600) so repeated reads never reach the backend.
- Request validators with models: malformed bodies or missing parameters are rejected with a 400 before the integration is invoked, so the backend never pays for bad requests.
- AWS WAF association.
Keep two controls apart. Throttling and quotas reject excess requests (HTTP 429); only caching serves repeated requests more cheaply. And an AWS WAF rate-based rule blocks requests from an IP address (or another aggregation key, such as a header) that exceeds a rate over a short window; it has no notion of a registered client with a monthly quota. Per-client throttles plus quotas means a REST API with usage plans and API keys.
With all five integration services covered, here is the selection table to memorize:
| Service | Problem it solves | Trigger phrases in the stem |
|---|---|---|
| SQS | Buffer work between a producer and one consumer group; absorb spikes; retry via visibility timeout; isolate poison messages with a DLQ | "requests are lost during spikes," "process asynchronously," "decouple web tier from processing tier" |
| SNS | Push one message to many subscribers; fan out to SQS queues for durable per-consumer processing | "notify multiple systems," "each service needs a copy," "add consumers without changing the publisher" |
| EventBridge | Route events to targets based on event content; ingest SaaS and AWS service events; cross-account event buses; archive and replay | "route based on event content," "events from a SaaS application," "react to AWS resource state changes" |
| Step Functions | Orchestrate multi-step workflows with per-step retries, branching, parallelism, and human approval; state kept outside the code | "multi-step," "coordinate Lambda functions," "retry failed steps," "manual approval," "visual workflow" |
| API Gateway | Stable managed front door for synchronous clients: throttling, auth, validation, caching; decouples client contract from backend implementation | "expose a REST API," "throttle per client," "replace the backend without changing clients" |
Most integration-service questions are answered by matching the stem to one row of this table and then checking the qualifier — the wrong options are usually other rows that solve a problem the scenario does not have.
Compute selection: Lambda vs Fargate vs ECS/EKS on EC2
The decoupled work has to run somewhere, and the choice runs from most managed to most controlled. AWS Lambda is the default for short, event-driven, spiky work: it scales from zero automatically, bills per request and duration, and is a native target of SQS, SNS, EventBridge, S3 and API Gateway. Its defining constraint is the 15-minute maximum execution time: a task that can exceed it, or a long-lived process such as a persistent server, rules Lambda out. Steady very-high-volume workloads can also make per-invocation pricing worse than always-on containers.
AWS Fargate runs containers with no EC2 instances to manage: you declare CPU and memory per task, and AWS provisions and patches the capacity. It fits containerised, long-running work when the team wants no server management. Know its limits, because they decide many questions:
- No GPUs. GPU workloads need ECS or EKS on GPU EC2 instances.
- No privileged containers, and awsvpc network mode only (each task gets its own network interface). Host-level agents that need the host's network or kernel need the EC2 launch type.
- No DaemonSets on EKS Fargate, and no ECS daemon scheduling on Fargate. A monitoring or log agent that must run once per host uses the ECS daemon scheduling strategy (exactly one task per container instance) or a Kubernetes DaemonSet on EC2 nodes; on Fargate, the agent runs as a sidecar in each task or pod.
Fargate Spot runs interruptible tasks at a discount. Mix it with regular Fargate through a capacity provider strategy: base pins the first N tasks to one provider (say, FARGATE for a guaranteed minimum), and weight splits the remaining tasks between providers (say, 1 FARGATE to 3 FARGATE_SPOT).
ECS or EKS on EC2 trades convenience for control: specific instance types (GPUs, high memory), host daemons, privileged containers, or Reserved Instances, Savings Plans and Spot for steady capacity. ECS is the simpler AWS-native orchestrator; EKS is for teams that require Kubernetes itself (existing manifests, tooling or portability). Plain EC2 remains for full OS control or host-bound licensing.
| Compute | Choose when |
|---|---|
| Lambda | Event-driven, short tasks (hard 15-min cap), spiky or infrequent traffic, zero server management, per-request billing |
| Fargate | Long-running containers or jobs over 15 minutes, no instances to manage, no GPU, privileged or per-host daemon needs |
| ECS on EC2 | Containers needing instance control: GPUs, privileged containers, daemon tasks on every host, Spot/Reserved cost optimisation |
| EKS | Kubernetes is a stated requirement: existing manifests, tooling, or portability; EC2 nodes for DaemonSets and GPUs |
| EC2 | Full OS control, non-containerised legacy apps, host-bound licensing |
Qualifiers do the rest: "least operational overhead" pushes toward Lambda or Fargate; "jobs take 40 minutes" eliminates Lambda; "must keep using existing Kubernetes manifests" forces EKS; "needs GPUs" eliminates Fargate.
Migrating applications into containers
Migrating an application into containers means packaging it as an image and removing everything that ties it to one machine, so any copy of the task can run anywhere and be replaced at any time. Lifting a monolith into ECS on Fargate as is buys consistent deployments and easier scaling without a rewrite; breaking it into microservices can come later. The work is in what the old server used to hold:
- Logs: write to stdout/stderr and use the
awslogslog driver (or FireLens) to ship them to CloudWatch Logs. Fargate task storage is ephemeral, so log files written inside the container disappear with the task. - Secrets and configuration: reference them in the task definition's
secretsfield (Secrets Manager secret or Parameter Store parameter ARNs); ECS injects them as environment variables at start-up. Never bake them into the image. - Session state: move it to ElastiCache or DynamoDB so any task can serve any user.
- Shared files: use Amazon EFS. An EBS volume belongs to one Availability Zone and, in Kubernetes, is
ReadWriteOnce: it attaches to one node at a time, and a pod using it is pinned to that zone. Replicas spread across several AZs that need the same files use EFS (the EFS CSI driver on EKS,ReadWriteMany; an EFS volume in the ECS task definition).
Lambda container images are a packaging option, not a different runtime model. An image (up to 10 GB) must implement the Lambda Runtime API, which you get by starting from an AWS base image or adding the runtime interface client, and the function still has the 15-minute limit and the event-driven invocation model. Packaging an existing web server as a Lambda image does not make it a long-running service; that workload belongs on Fargate or ECS.
Purpose-built managed services: Secrets Manager, Parameter Store, and Transfer Family
The exam guide names AWS managed services by use case because the right answer is often "use the service built for this instead of running it yourself". Two pairs come up in Task 2.1.
Secrets Manager vs Parameter Store. Both store encrypted values that applications and ECS tasks fetch by reference. Secrets Manager adds built-in rotation: for Amazon RDS, Aurora, Redshift and DocumentDB it rotates credentials with AWS-provided rotation functions (or fully managed rotation for credentials RDS manages), updating the database and the secret together. Managed external secrets extend this to supported SaaS partners without a Lambda function; any other third-party secret needs a custom rotation function. Parameter Store SecureString parameters are KMS-encrypted and cheaper, but have no built-in rotation; an advanced-tier expiration policy can only notify you or delete the parameter. Rotating the KMS key does not change the password either: it changes the key material used to encrypt it.
| Need | Service |
|---|---|
| Database credentials rotated automatically on a schedule | Secrets Manager |
| Configuration values and secrets with no rotation requirement, at low cost | Parameter Store (SecureString for secrets) |
| Inject either into ECS tasks | Task definition secrets referencing the ARN |
AWS Transfer Family provides fully managed SFTP, FTPS, FTP and AS2 endpoints that store files directly in Amazon S3 or Amazon EFS. Partners keep their existing clients and workflows, and you stop patching, scaling and making highly available an SFTP server on EC2. When a scenario says partners "upload files over SFTP" and the data should land in S3 with least overhead, this is the service. Data-movement choices beyond it (DataSync, Storage Gateway) are covered in the data ingestion lesson.
Scaling strategies: horizontal vs vertical, and choosing the Auto Scaling policy
Scale horizontally by default. Vertical scaling (a bigger instance) is simple but has a hard ceiling, usually needs a restart, and leaves a single point of failure. Horizontal scaling (more instances behind a load balancer) has no practical ceiling and adds availability, but needs stateless components. On SAA-C03, vertical scaling is usually the wrong answer dressed up as the easy one.
For EC2, horizontal scaling means an Auto Scaling group across several AZs, and the policy is a decision about what you know in advance:
| Policy | Use when | Watch out for |
|---|---|---|
| Target tracking | "Keep this metric near X" (CPU 50%, requests per target, backlog per instance); least configuration | Reacts after load arrives |
| Step / simple scaling | Hand-tuned reactive responses: different-sized adjustments at different alarm thresholds | More configuration to get right |
| Scheduled actions | A surge at a known date and time (a sale starting at 09:00 on a given day) | Cannot follow unpredictable bursts |
| Predictive scaling | Recurring daily or weekly cycles; capacity is launched ahead of the forecast | Forecasts from up to 14 days of history, so one-off events are not predicted |
AWS recommends pairing predictive scaling with target tracking: the forecast provisions for the regular cycle and target tracking catches the unplanned spike on top.
Scale queue consumers on backlog per instance. CPU is a poor signal for workers (they may wait on I/O while the queue grows), and schedules cannot follow bursts. AWS's recommended metric is backlog per instance: the queue's visible messages (ApproximateNumberOfMessagesVisible) divided by the number of running instances, published as a custom metric or built with metric math. Set the target to the backlog one instance can clear within your acceptable latency (acceptable seconds divided by average seconds per message) and let target tracking keep it there. More depth on fleet scaling is in the elastic compute lesson.
Load balancing: ALB vs NLB vs Gateway Load Balancer, and sticky sessions
The load balancer is picked by protocol and by what clients need from its addresses.
| Application Load Balancer | Network Load Balancer | Gateway Load Balancer | |
|---|---|---|---|
| Layer and protocols | Layer 7: HTTP, HTTPS, gRPC (gRPC target groups behind an HTTPS listener), WebSockets | Layer 4: TCP, UDP, TLS | Layer 3 traffic to inline appliances (GENEVE) |
| Balances | Each request | Each connection | Flows through firewall/IDS fleets |
| Routing | Path, host, header, query string | Port | Transparent bump-in-the-wire |
| IP addresses | Node IPs change; use the DNS name | One static IP or Elastic IP per AZ | Not client-facing |
- Non-HTTP protocols or long-lived TCP/UDP connections (game servers, MQTT, custom protocols) need an NLB.
- Clients that allowlist IP addresses need fixed addresses. An NLB gives one Elastic IP per AZ. Placing AWS Global Accelerator in front of an ALB also gives static IPs, but the ALB still only carries HTTP, HTTPS and gRPC, so it does not help a non-HTTP protocol.
- Long-lived HTTP/2 or gRPC connections stay uneven behind an NLB, because it balances whole connections and each client sticks to one target for its connection's life. An ALB balances each request, so gRPC calls spread across targets.
- Inline third-party appliances (firewalls, intrusion detection) are the Gateway Load Balancer.
Sticky sessions (session affinity via a load balancer cookie) keep each user on the same target. They are the fallback when session state cannot be moved out of the instance, for example when no code change is allowed. The cost: sessions on an instance that fails or is scaled in are lost, and load can skew toward instances holding long sessions. Where code can change, externalising state to ElastiCache or DynamoDB is the real fix. More on load balancers in the network architectures lesson.
Caching strategies: lazy loading, write-through, TTLs, and where to cache
A cache serves repeated reads from fast memory so the backend handles fewer of them, and the strategy decides how stale a cached value may be. Pick the layer first: CloudFront at the edge for HTTP content, API Gateway stage caching for repeated API responses, ElastiCache (Valkey, Redis OSS or Memcached) for application data and sessions, and DAX as an in-memory, API-compatible cache in front of DynamoDB.
| Strategy | How it works | Trade-off |
|---|---|---|
| Lazy loading (cache-aside) | Read the cache; on a miss, read the database and populate the cache | Only requested data is cached, but every miss costs an extra trip, and an edited value stays stale until it expires or is evicted. With no TTL that can be indefinitely. |
| Write-through | Every database write also updates the cache | Cached data is fresh on write, but everything written gets cached, including data nobody reads. Pair it with a TTL. |
| TTL | Each key expires after a set time | Bounds staleness to the TTL; shorter TTL means more misses |
Two misconceptions to avoid. An eviction policy such as allkeys-lru acts only when the cache reaches its memory limit; it does not free memory held by cold entries before then, and it does nothing for staleness. And a scheduled preload with a long TTL guesses the working set in advance, then serves whatever it loaded until the TTL ends, which breaks any short staleness requirement. The usual answer is lazy loading or write-through with a TTL sized to the staleness the business accepts. Cache engine choices are covered in the high-performance databases lesson.
The edge: CloudFront vs Global Accelerator
CloudFront caches; Global Accelerator does not. That one difference answers most questions that offer both.
Amazon CloudFront is a CDN for HTTP, HTTPS and WebSocket traffic. It serves cached responses from edge locations, cutting both user latency and origin load, and carries uncached requests to the origin over the AWS network. How well it offloads the origin depends on the cache key. A cache policy decides which query strings, headers and cookies form the cache key; an origin request policy decides what is forwarded to the origin without becoming part of the key. Adding every query string or header to the cache key fragments the cache into near-unique entries and drops the hit ratio. Longer TTLs and Origin Shield (an extra regional caching layer that collapses origin fetches) do not fix a fragmented key; moving values out of the key into the origin request policy does. CloudFront can also reach an internal ALB or EC2 instance in a private subnet through VPC origins.
AWS Global Accelerator gives an application two static anycast IP addresses and carries TCP and UDP traffic from the nearest edge location over the AWS backbone to Regional endpoints (ALBs, NLBs, EC2 instances, Elastic IPs). Health checks steer traffic between Regional endpoint groups in seconds, without waiting for DNS caches to expire, which is where it beats Route 53 failover. It caches nothing, so every request still reaches your endpoints and origin load is unchanged.
| Requirement | Choose |
|---|---|
| Reduce origin load, cache static or repeated content for global users | CloudFront |
| Non-HTTP (UDP, custom TCP) traffic needs lower latency worldwide | Global Accelerator |
| Static IP addresses for client allowlists, fronting several Regions | Global Accelerator |
| Fast multi-Region failover not bound by DNS caching | Global Accelerator |
Scaling the data tier: read replicas, Aurora endpoints, Multi-AZ, and RDS Proxy
Read replicas scale reads; Multi-AZ provides availability; RDS Proxy manages connections. Mixing them up is the trap.
RDS read replicas copy the primary asynchronously and serve read-only traffic (reports, read-heavy pages), so the primary handles the writes. Readers accept slight replication lag. A Multi-AZ DB instance deployment keeps a synchronous standby in another AZ that serves no traffic; it exists only for failover. A Multi-AZ DB cluster (MySQL and PostgreSQL) is different: its two standbys are readable, so it adds read capacity as well as failover.
Aurora endpoints. The cluster (writer) endpoint points at the primary; the reader endpoint load-balances across every Aurora Replica, including replicas you also placed in a custom endpoint. So pointing the application at the reader endpoint and the reporting tool at a custom endpoint does not isolate them: application reads still land on the reporting replicas. To separate workloads, create dedicated replicas and a custom endpoint for each workload, each listing only its own replicas.
RDS Proxy pools and shares database connections, which protects the database from connection storms (many Lambda invocations each opening a connection) and shortens failover for clients. It does not execute queries anywhere else: a primary saturated by query CPU still needs read replicas, caching or a bigger instance. Failover design is in the high availability lesson.
Storage types: object, file, and block
Storage type is chosen by how the application accesses data: object storage through an HTTP API, file storage through a shared file system, block storage as a disk attached to an instance.
| Object: Amazon S3 | File: Amazon EFS (and FSx) | Block: Amazon EBS | |
|---|---|---|---|
| Access | HTTP API (GET/PUT whole objects) | NFS mount (EFS); SMB, Lustre and others (FSx) | Disk attached to an EC2 instance |
| Sharing | Any number of clients | Many instances, containers and Lambda functions at once, across AZs | One instance (Multi-Attach is the exception) |
| Scope and durability | Regional, stored across multiple AZs (except the One Zone storage classes) | EFS Regional file systems span AZs | One Availability Zone |
| Capacity | Virtually unlimited, pay per GB stored | EFS grows and shrinks automatically | Provisioned size, IOPS and throughput |
| Fits | Static assets, uploads, backups, data lakes | Shared content and home directories; shared state for containers | Boot volumes, databases, low-latency transactional I/O |
EBS Multi-Attach lets one Provisioned IOPS (io1 or io2) volume attach to up to 16 Nitro-based instances in the same AZ, and the application must use a cluster-aware file system to avoid corrupting data. It is not a general way to share files; EFS is. Instance store gives the fastest local I/O but loses data when the instance stops. Volume types, FSx variants and S3 performance are in the high-performance storage lesson.
Worked scenarios: reasoning through Task 2.1 questions
Scenario 1 — the spike-buffering redesign. An e-commerce site runs a web tier that synchronously submits orders to a processing tier of EC2 instances. During flash sales, the processing tier saturates, web requests time out, and some orders are lost. The company needs a design that ensures no orders are lost and handles unpredictable spikes cost-effectively. Options: (A) move the processing tier to larger instances; (B) put an SQS queue between the tiers and run processors in an Auto Scaling group that scales on queue depth; (C) publish orders to an SNS topic that pushes to the processing instances; (D) increase the web tier's timeout values.
Eliminate A: vertical scaling raises the ceiling but keeps the synchronous coupling — a big enough sale still saturates it, orders are still lost at the limit, and you now pay for peak capacity all the time. Eliminate D: longer timeouts change when failures surface, not whether they happen; nothing stores the order. Eliminate C: SNS is push-based delivery to subscribers, not a durable work buffer with consumer-paced retrieval — it does not let the processing tier drain a backlog at its own pace. B is correct: the queue durably absorbs the spike (no lost orders), the web tier returns instantly, and workers scale out on backlog and back in afterward (cost-effective). This exact shape — synchronous tiers, spikes, lost work — is the most common Task 2.1 question.
Scenario 2 — the fan-out. When an order is placed, three systems must each process the event independently: inventory, shipping, and analytics. Each must process every event reliably even if it goes offline temporarily, and the company wants to add future consumers without modifying the ordering application. Options: (A) the application writes to one SQS queue that all three systems poll; (B) the application publishes to an SNS topic with an SQS queue subscribed per consumer; (C) the application calls each system's API in sequence; (D) a Step Functions workflow invokes each system.
Eliminate A: consumers on a single queue compete — each message is processed by one of them, so inventory would receive only a third of the events. Eliminate C: synchronous calls are tight coupling — one slow system delays the order path, and adding a consumer means changing application code. D actually works, but it is centralized orchestration for what is independent parallel consumption: adding a consumer means editing the state machine, and the ordering app becomes coupled to the workflow's completion. B is correct: SNS fans out a copy to every subscriber, each queue buffers durably through downtime, and new consumers are just new subscriptions. When two options both function, the one where consumers are independent and the publisher never changes wins the loose-coupling qualifier.
Tip. SAA-C03 tests this task with redesign scenarios: a tightly coupled system that loses requests during spikes, times out on a slow downstream, or needs code changes for every new consumer, plus a qualifier such as most cost-effective or least operational overhead. Expect the web-to-worker queue insertion, SNS-to-SQS fan-out (including FIFO topics and encrypted queues), EventBridge for AWS or SaaS events and cross-account buses, Step Functions in place of chained Lambda functions, Lambda vs Fargate vs ECS on EC2 against a runtime, GPU or host-agent requirement, and scaling, caching, edge and read-replica choices matched to a named bottleneck. Many questions turn on configuration detail: the message group ID, deduplication vs redelivery, visibility timeout for Lambda consumers, DLQ retention, usage plans vs WAF rate-based rules, the Aurora reader endpoint, and the Auto Scaling policy that fits a known or unknown surge. Traps include vertical scaling offered as the fix for a coupling problem, one shared queue where every consumer needs its own copy, Global Accelerator offered to reduce origin load, a Multi-AZ standby offered for read traffic, and Lambda proposed for a job that runs longer than 15 minutes.
- A queue between tiers turns a traffic spike into a backlog: SQS plus a worker tier scaling on backlog per instance is the default spike-absorption redesign.
- SQS FIFO orders messages per message group (set the group ID to the entity whose order matters) and deduplicates sends within five minutes by deduplication ID; it does not prevent redelivery after a visibility timeout.
- Retention (default 4 days, up to 14) limits how long a queue can cover an outage; standard-queue DLQs need longer retention than the source, and DLQ redrive moves fixed messages back.
- With Lambda on SQS: visibility timeout at least six times the function timeout, partial batch responses, and the event source mapping's maximum concurrency (not reserved concurrency) to cap load.
- SNS-to-SQS fan-out gives every consumer its own durable buffer; SNS filters on attributes or body, SQS FIFO queues subscribe only to SNS FIFO topics, and EventBridge wins on AWS/SaaS sources, Step Functions targets, cross-account buses and replay.
- Step Functions orchestrates multi-step work; Express workflows stop at five minutes and support neither callbacks nor .sync, and Distributed Map handles millions of S3 items.
- Per-client throttles plus quotas are REST API usage plans; WAF rate-based rules block by rate, not quota. Fargate has no GPUs, privileged containers or per-host daemons.
- Match the lever to the bottleneck: target tracking (plus predictive for cycles, scheduled for known dates), NLB for non-HTTP or static IPs, caches with TTLs, CloudFront to cut origin load, Global Accelerator for static IPs and fast failover, read replicas or custom Aurora endpoints for reads, EFS for shared files.
Frequently asked questions
What is the difference between SQS and SNS?
SQS is a pull-based queue: producers send messages, and each message is consumed by one worker from a competing consumer group, with the queue durably buffering work until consumers are ready. SNS is push-based publish/subscribe: one published message is delivered to every subscriber of a topic. Use SQS to decouple and buffer work for one processing tier; use SNS when multiple systems each need a copy of the same message. The two combine in the fan-out pattern — an SNS topic with an SQS queue subscribed per consumer — so every consumer gets pub/sub reach plus a durable, independently scaled buffer.
When should I use EventBridge instead of SNS?
Choose EventBridge when events come from AWS services or SaaS partner applications, when targets include Step Functions, ECS tasks, API destinations or another account's event bus, when you want routing rules managed centrally, or when you need archive and replay to reprocess past events. Choose SNS for straightforward one-to-many delivery at very high throughput and low latency, especially fan-out into SQS queues, and for SMS, email or mobile push notifications. Filtering alone does not separate them: SNS subscription filter policies can match message attributes or fields in the message body. The deciding factor is usually where events come from and which targets must receive them.
What is the SQS visibility timeout and what happens if it is too short?
When a consumer receives an SQS message, the message is not deleted; it becomes invisible to other consumers for the visibility timeout. The consumer deletes it after processing; if the consumer crashes, the timeout expires and the message reappears, which is how SQS retries. If the timeout is shorter than processing time, messages reappear while still being processed and are handled twice, even on a FIFO queue, whose deduplication only covers duplicate sends. Raise the timeout above processing time, extend individual long-running messages with ChangeMessageVisibility, and for Lambda consumers set it to at least six times the function timeout.
When should I use an SQS FIFO queue instead of a standard queue?
Use FIFO when the workload requires ordering per entity (for example, every update to one account processed in sequence) or deduplication of repeated sends within a five-minute window. Ordering applies within a message group, so set the group ID to the entity whose order matters; different groups are processed in parallel. FIFO costs more per request and needs group and deduplication IDs designed, and its default throughput is lower, though high-throughput mode raises it considerably. If order does not matter and consumers are idempotent, a standard queue is simpler, cheaper and effectively unlimited in throughput.
Should I use Step Functions or chain Lambda functions together?
Use Step Functions whenever a process has multiple steps with dependencies, retries, branching, parallel work, or human approval. Chaining Lambda functions — one synchronously invoking the next — packs the whole workflow into the first function's 15-minute limit, bills for idle waiting, scatters retry logic across code, and keeps no central record of where an execution stands if something crashes mid-chain. Step Functions externalizes the workflow state, gives each step its own retry and catch policy with exponential backoff, supports executions up to a year on Standard workflows, and shows each execution visually for debugging.
When should I use Lambda vs Fargate?
Use Lambda for short, event-driven tasks: it scales from zero, bills per request, and plugs natively into SQS, SNS, EventBridge, S3 and API Gateway, but every invocation must finish within the 15-minute limit, even when packaged as a container image. Use Fargate for containerised workloads that run longer or continuously, such as APIs, stream consumers or batch jobs, when the team wants no EC2 instances to manage. Fargate does not offer GPUs, privileged containers or per-host daemons (no DaemonSets on EKS Fargate), so those requirements move the workload to ECS or EKS on EC2.
What is fan-out architecture in AWS?
Fan-out is the pattern where one published event is delivered to multiple independent consumers in parallel. On AWS the standard implementation is an SNS topic with an SQS queue subscribed for each consumer: the publisher sends one message, SNS copies it to every queue, and each consumer processes from its own queue at its own pace with its own retries and dead-letter queue. This keeps the publisher completely decoupled — adding a new downstream system is just a new subscription. EventBridge achieves fan-out too, with rules routing one event to multiple targets, and adds content-based filtering when different consumers need different subsets of events.
What is the difference between CloudFront and Global Accelerator?
Amazon CloudFront is a content delivery network for HTTP, HTTPS and WebSocket traffic that caches responses at edge locations, so it lowers both user latency and the load on your origin. AWS Global Accelerator caches nothing: it gives an application two static anycast IP addresses and carries TCP or UDP traffic over the AWS backbone to Regional endpoints, with health-based failover between Regions that does not wait for DNS caches. Choose CloudFront to offload the origin and speed up web content; choose Global Accelerator for non-HTTP protocols, static IPs that clients can allowlist, or fast multi-Region failover.
Source
This lesson covers the "Design Resilient 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.