Study Guide · DVA-C02

DVA-C02 Cheat Sheet

354 entries · 13 chapters · 4 domains

Development with AWS Services

Application Code on AWS

Read full chapter

Cheat sheet

Sharp facts the exam loves — scan these before test day.

Loose coupling means putting a managed buffer between caller and worker

Two components are tightly coupled when the caller invokes the worker directly and blocks for the answer, so a slow or down worker fails the caller too. The fix is to insert a managed intermediary (an Amazon SQS queue, an Amazon SNS topic, or an Amazon EventBridge bus) so the producer hands off and returns immediately while the consumer works on its own schedule. Decoupled, the two scale, retry, and fail independently, and a worker outage shows up as queue depth rather than caller errors.

Trap Adding more compute capacity to the worker to stop a slow worker from failing the caller, when the real fix is to decouple them with a managed buffer so the caller no longer blocks on the worker at all.

Asynchronous handoff changes the contract: the caller no longer learns the outcome inline

A synchronous call blocks until a result returns, like an API Gateway request waiting on a Lambda response; an asynchronous call hands the work off and returns before it finishes, like publishing to SNS, enqueuing to SQS, or invoking Lambda with the Event invocation type. Asynchronous is what buys loose coupling, but because the caller gets no inline result, failures surface later through retries, dead-letter queues, or destinations instead of as an immediate error.

Trap Expecting an asynchronous (Event) invocation to return the function's result to the caller, when it returns immediately after accepting the request and the outcome surfaces only later through retries, a DLQ, or destinations.

Push shared state out of the compute tier so the compute stays stateless and disposable

A stateless component keeps no client-specific data between requests, so any instance (or any Lambda execution environment) can serve any request, which is what lets a fleet scale horizontally and lets a failed unit's work be retried elsewhere. A stateful component remembers prior requests locally, breaking horizontal scaling because a follow-up must reach the same instance. Move shared state into a managed store such as DynamoDB, ElastiCache, or an SQS queue; statelessness is also what makes a retry safe to repeat.

Trap Storing session or client-specific state in a Lambda function's /tmp or in module-level globals to persist it between requests, when the execution environment is reused unpredictably and never guaranteed, so shared state belongs in DynamoDB or ElastiCache.

1 question tests this
Pick the messaging service by who reads each message

The deciding question across SQS, SNS, and EventBridge is who consumes a given message. SQS delivers each message to exactly one consumer from a competing pool, so it suits work distribution and load leveling. SNS pushes a copy to every subscriber in parallel, so it suits fanout to many endpoints. EventBridge routes to every target whose rule matches fields inside the event JSON, so it suits content-based routing and consuming AWS-service or SaaS event sources.

Trap Picking SNS to distribute a workload across a pool of competing workers so each item is handled once, when SNS copies every message to every subscriber; single-consumer-per-message work distribution is SQS.

SQS is a poll-based queue where one consumer claims each message

An SQS queue durably holds messages until a consumer polls and explicitly deletes them, and competing consumers share the load, so it is the choice for distributing work, leveling spiky load, and buffering between tiers. Because each message is claimed by one consumer at a time, SQS is not a fanout mechanism. If several independent systems each need the same message, give each its own queue.

Trap Reaching for a single SQS queue to notify several independent systems: the message goes to just one consumer from the pool, not to all of them.

SQS messages are retained 4 days by default, up to 14 days maximum

An unconsumed SQS message survives for the queue's retention period, which defaults to 4 days and can be raised to a 14-day maximum, after which SQS deletes it automatically. Retention governs how long an undelivered message waits for a consumer. It is unrelated to how long an in-flight message stays hidden while one consumer works on it.

Trap Raising the retention period to stop a message from reappearing mid-processing: retention controls how long an unconsumed message lives, not in-flight invisibility, which is the visibility timeout.

Visibility timeout hides a received message so a second consumer doesn't grab it

When a consumer receives an SQS message it becomes invisible to other consumers for the visibility-timeout window, which defaults to 30 seconds and can be set up to 12 hours. If the consumer deletes the message before the window expires it is gone for good; if processing overruns the window the message reappears and may be processed again. So size the timeout to the real processing time, not the default.

Trap Leaving the visibility timeout at the 30-second default when processing routinely takes longer, so the message reappears and a second consumer reprocesses it before the first one finishes.

1 question tests this
Standard queues are at-least-once with best-effort ordering; FIFO queues add ordering and exactly-once processing

An SQS Standard queue gives at-least-once delivery (AWS states more than one copy of a message might be delivered) with only best-effort ordering, in exchange for nearly unlimited throughput. A FIFO queue preserves first-in-first-out order and provides exactly-once processing within its deduplication interval, at a lower throughput ceiling. Choose FIFO only when ordering or no-duplicates is a hard requirement, because it trades away throughput.

Trap Assuming a Standard queue preserves order because messages usually arrive in sequence: ordering is only best-effort, and duplicates can occur; strict order or no-duplicates needs FIFO.

High-throughput FIFO mode raises a FIFO queue's per-queue throughput when you need more

A plain FIFO queue trades throughput for ordering and exactly-once processing. When you need higher throughput while keeping FIFO semantics, enable high-throughput FIFO mode, which raises the per-queue / per-API-action quota and scales with the number of distinct message group IDs: spreading messages across more message groups increases capacity. Without enough distinct group IDs the throughput gain doesn't materialize.

Trap Enabling high-throughput FIFO but routing every message through one message group ID, so the throughput stays bottlenecked because capacity scales with the number of distinct group IDs.

A Lambda SQS event source mapping batches up to 10,000 messages for Standard but only 10 for FIFO

The Lambda SQS event source mapping reads messages in batches with a batch size that defaults to 10. Standard queues allow up to 10,000 messages per batch, but FIFO queues are capped at 10. Any batch size above 10 also requires a MaximumBatchingWindowInSeconds of at least 1 second so Lambda waits long enough to fill the larger batch.

Trap Setting a batch size above 10 for a FIFO source queue, which is capped at 10; the larger 10,000-message batches apply only to Standard queues.

SNS is push-based pub/sub that delivers a copy to every subscriber: the fanout pattern

A publisher sends one message to an SNS topic and SNS pushes a copy to every subscriber in parallel, which is the fanout pattern for triggering several independent systems from one event. SNS does not store messages for later polling, so a subscriber that is offline at publish time loses that message. It is the right tool when one event must reach many endpoints at once.

Trap Assuming an offline SNS subscriber will pick up the message later: SNS is deliver-then-forget and keeps nothing for polling, so the message is lost unless a queue buffers it.

Subscribe an SQS queue per consumer to SNS for durable, independent fanout

Because SNS itself keeps no copy, the resilient fanout pattern subscribes an SQS queue to the SNS topic for each downstream consumer. SNS pushes the message to every queue, and each consumer polls its own durable copy on its own schedule, so a consumer that is offline at publish time still processes the message when it returns. This combines SNS's one-to-many fanout with SQS's durability.

Trap Subscribing one shared SQS queue to the topic for several consumers, which makes them compete for each message so only one processes it; durable independent fanout needs one queue per consumer.

3 questions test this
SNS subscription filter policies match on message attributes, not arbitrary body fields

An SNS subscription filter policy lets a subscriber receive only messages whose message attributes match the policy, so you can fan out selectively from one topic. The match is on attributes, not on arbitrary fields inside the message body. If routing must depend on content within the event JSON, that is EventBridge's job, not SNS's.

Trap Expecting an SNS filter policy to route on a field buried in the message body: SNS filters match message attributes only; content-based routing on the event JSON is EventBridge.

EventBridge routes events to targets by rules that match fields inside the event JSON

EventBridge is a content-based event router: rules match on fields within the event's JSON body and deliver to the targets whose rule matches, rather than to a flat topic of subscribers. Reach for it when routing depends on event content, when consuming AWS-service or SaaS partner event sources, or when you need a schema registry. It delivers at-least-once to each target, just like SQS Standard and SNS.

Trap Choosing SNS subscription filter policies to route on fields inside the event payload, when those filters match only message attributes; matching on the event JSON body is EventBridge's content-based routing.

An EventBridge archive retains events indefinitely by default and can replay them

EventBridge can archive matching events and later replay them onto a bus, useful for reprocessing or recovery. The archive's retention is configurable in days, but it defaults to indefinite retention, not a short window, so events are kept until you set a limit. SNS has no comparable replay; needing to replay past events is a strong cue for EventBridge.

Trap Assuming an EventBridge archive defaults to a short retention like 24 hours: the default is indefinite retention until you configure a day limit.

At-least-once delivery makes idempotent consumers mandatory

SQS Standard delivery, SNS delivery to each subscriber, and EventBridge delivery to each target are all at-least-once, so a consumer can legitimately receive the same message more than once. A consumer is idempotent when processing the same message twice yields the same result as once. Without idempotency, duplicate deliveries produce duplicate side effects (double charges, double records), so design the consumer to tolerate repeats.

Trap Trusting that a Standard queue or SNS delivers each message exactly once so the consumer need not handle repeats, when these services are at-least-once and duplicates are expected, not exceptional.

1 question tests this
Implement idempotency with a DynamoDB conditional write on a dedup key

The canonical idempotency implementation derives a stable key from the message (its message ID or a business key) and writes it to DynamoDB with a conditional attribute_not_exists check. The first delivery's write succeeds; a duplicate delivery fails the condition, so the consumer skips the side effect instead of re-running it. If duplicates are simply unacceptable and ordering matters, an SQS FIFO queue removes the burden from your code instead.

Trap Keying the dedup record on a value that changes between deliveries of the same logical message rather than a stable message ID or business key, so the conditional write never collides and duplicates slip through.

3 questions test this
Retry transient failures with exponential backoff plus jitter, not fixed-interval loops

When a dependency throttles or returns a 5xx, retrying immediately and in lockstep across many callers creates a synchronized retry storm that keeps the dependency overwhelmed. Exponential backoff roughly doubles the wait each attempt, and jitter adds a random offset so callers spread out instead of retrying in unison. Together they let a struggling dependency recover.

Trap Using a fixed-interval sleep-and-retry loop: without jitter every caller retries in unison, re-creating the retry storm the backoff was meant to avoid.

5 questions test this
The AWS SDK already retries throttling and transient errors, so tune its config instead of hand-rolling

The AWS SDKs implement automatic retries with exponential backoff and jitter for throttling and transient errors, so you adjust the SDK's retry configuration (retry mode and max attempts) rather than writing sleep loops by hand. Reserve custom retry-with-backoff, and a circuit breaker that stops calling a dependency that keeps failing, for third-party HTTP integrations the SDK does not own, so a failing downstream doesn't exhaust your function's concurrency.

Trap Hand-coding an exponential-backoff retry loop around AWS SDK calls to handle throttling, when the SDK already retries those errors and you only need to tune its retry mode and max attempts.

3 questions test this
Let the SDK pull temporary role credentials from the provider chain: never hardcode access keys

Application code reaches AWS through the language-specific AWS SDK (Boto3, AWS SDK for JavaScript v3, others) and the AWS CLI from the shell; both resolve credentials through the default credential provider chain. On Lambda or EC2 that means the attached IAM role's temporary credentials, and the SDK signs every request with them (SigV4) automatically. Grant the compute a role and let the SDK pull credentials from the chain rather than baking long-lived access keys into source or environment variables.

Orchestration centralizes workflow control; choreography distributes it

In orchestration a single coordinator (an AWS Step Functions state machine) drives the steps, owns retries, and holds the workflow state in one place, which makes runs easy to visualize and debug. In choreography each service reacts to events via SNS or EventBridge with no central brain, which is looser and more scalable but hard to trace end-to-end. Choose orchestration when a multi-step workflow must be auditable and centrally coordinated.

Trap Using pure SNS/EventBridge choreography for a workflow that must be debugged end-to-end: with no central coordinator, tracing where a multi-step run failed is hard; Step Functions orchestration gives that visibility.

Step Functions Standard workflows are exactly-once with built-in Retry/Catch; Express trades that for high volume

A Step Functions Standard workflow gives exactly-once execution, an auditable run history, and built-in Retry and Catch with backoff per state: the right fit for ordered, durable, traceable multi-step processes. Express workflows are tuned for high-volume, short-duration runs and do not provide the exactly-once durability of Standard. When a stem asks for an ordered, auditable workflow with visible state, that points to Standard.

Trap Choosing Express workflows for a long-running, auditable, exactly-once process because they cost less per run, when Express favors high-volume short bursts and gives up the exactly-once durability and full run history Standard provides.

Long polling eliminates empty SQS ReceiveMessage responses and cuts cost

When an SQS consumer keeps getting empty ReceiveMessage responses on a sporadic queue and paying for those calls, enable long polling by setting ReceiveMessageWaitTimeSeconds to a value from 1 to 20 seconds (20 is the maximum). Long polling queries all SQS servers and waits until a message is available or the wait time expires, so it returns far fewer empty responses, lowers request cost, and still delivers messages promptly. Short polling (wait time 0) is the default and is what generates the costly empty replies.

Trap Switching to short polling, raising the visibility timeout, or adding more consumers to fix empty responses: the dial that removes empty responses is long polling via ReceiveMessageWaitTimeSeconds.

6 questions test this
Route poison SQS messages to a DLQ with a redrive policy and maxReceiveCount

To stop messages that repeatedly fail from blocking a queue, configure a redrive policy on the source queue that names the dead-letter queue in deadLetterTargetArn and sets maxReceiveCount to the number of receives allowed before SQS moves the message to the DLQ (for example 5). The DLQ must be the same type as the source: a FIFO source needs a FIFO DLQ. The redrive policy lives on the source queue, not the DLQ.

Trap Setting the redrive policy on the dead-letter queue itself: the policy and maxReceiveCount belong on the SOURCE queue, which points at the DLQ.

7 questions test this
ReportBatchItemFailures retries only the failed records in a batch

By default, when one record in a Lambda batch from SQS, Kinesis, or DynamoDB Streams fails, the whole batch is retried and already-succeeded records are reprocessed. To retry only the failures, set the event source mapping's FunctionResponseTypes to ReportBatchItemFailures and return a batchItemFailures list containing the identifiers (messageId for SQS, sequence number for Kinesis/DynamoDB Streams) of just the failed records. Lambda then makes only those records visible again.

Trap Expecting partial-batch behavior automatically: without ReportBatchItemFailures enabled AND a batchItemFailures response, a single failure re-drives the entire batch.

5 questions test this
Lambda proxy integration passes the whole request in the event object

With Lambda proxy integration (AWS_PROXY), API Gateway maps the entire client request into the Lambda event object: event.httpMethod, event.path, event.pathParameters, event.queryStringParameters, event.headers, and the raw event.body (a string the function must parse, e.g. json.loads). Caller metadata such as the source IP and stage name live under event.requestContext (requestContext.identity.sourceIp, requestContext.stage). The function reads request data from this event rather than from any framework-style request object.

Trap Multi-value headers and query strings are NOT in headers/queryStringParameters: they are exposed separately as multiValueHeaders and multiValueQueryStringParameters.

10 questions test this
A proxy-integration Lambda must return statusCode, headers, and a string body or API Gateway answers 502

With Lambda proxy integration the function itself shapes the HTTP response, which must be a JSON object with an integer statusCode, a headers map, and a body that is a STRING (serialize objects yourself). If the function returns a malformed shape (a bare object, a non-string body, or missing statusCode), API Gateway cannot translate it and returns 502 Bad Gateway with 'Internal server error', even though the function ran successfully.

Trap Seeing a 502 with successful function logs and blaming the function code: the cause is almost always a response not in the {statusCode, headers, body-as-string} shape.

2 questions test this
With proxy integration, CORS headers must come from the Lambda response

Under Lambda proxy integration, API Gateway passes responses straight through, so the actual cross-origin headers (Access-Control-Allow-Origin, and as needed Access-Control-Allow-Methods/Headers) must be added by the Lambda function in its response object. The console's 'Enable CORS' only wires up the OPTIONS preflight (a MOCK integration) and cannot inject headers into your GET/POST responses. Configure both: an OPTIONS method for preflight plus the headers returned by the function.

Trap Assuming the console 'Enable CORS' button alone fixes proxy-integration CORS: it handles only preflight; the per-method response headers still have to be returned by the Lambda function.

8 questions test this
API Gateway request validation rejects bad payloads before the backend runs

To validate that requests carry required fields and conform to types/ranges without writing validation in Lambda, define a JSON Schema model in API Gateway and attach a request validator (with ValidateRequestBody enabled) to the method. API Gateway checks the body against the model and returns 400 Bad Request itself for invalid requests, so the backend Lambda is never invoked, saving cost and centralizing validation.

Trap Validating required fields inside the Lambda function when the goal is to block invalid requests early: a model plus request validator rejects them at API Gateway before invocation.

4 questions test this
Use non-proxy (custom AWS) integration with VTL mapping templates to transform requests/responses

When the API must reshape the payload (transform the incoming request before Lambda, convert the Lambda response before the client, or override the status code) use Lambda custom (non-proxy, AWS-type) integration and write Velocity Template Language (VTL) mapping templates in the integration request and integration response. Lambda proxy integration passes data through unchanged and offers no mapping templates, so it cannot do this transformation.

Trap Choosing proxy integration when payload transformation/mapping templates are required: proxy is pass-through; mapping templates exist only on non-proxy (AWS) integrations.

6 questions test this
Lambda authorizers do custom auth and return an IAM policy; TOKEN vs REQUEST differ by identity source

For authorization logic API Gateway can't do natively (validating a JWT, checking custom claims, or querying a database) use a Lambda authorizer, which returns an IAM policy (Allow/Deny) plus an optional context object passed to the backend. A TOKEN authorizer takes a single bearer token from one header and can pre-screen it with a regex before invoking; a REQUEST authorizer can use multiple identity sources (headers, query strings, stage variables) as both input and the authorization cache key.

Trap Reaching for a Cognito authorizer when business rules require a database lookup or multi-source input: that custom logic needs a Lambda authorizer (REQUEST type for multiple identity sources).

4 questions test this

AWS Lambda Functions

Read full chapter

Cheat sheet

Sharp facts the exam loves — scan these before test day.

Tune Lambda with memory: CPU scales with it

Memory is the only direct sizing dial on a Lambda function, settable from 128 MB to 10,240 MB, and vCPU is allocated proportionally rather than configured separately. A function reaches roughly one full vCPU at 1,769 MB and up to six vCPUs at the maximum, so a CPU-bound function can finish faster at higher memory and sometimes cost less overall even though the per-millisecond rate rises. Treat memory as the performance tuning knob, not just a capacity ceiling.

Trap Assuming raising memory always costs more: for a CPU-bound function the shorter duration can offset or beat the higher per-ms rate.

A Lambda invocation is capped at 15 minutes

A single Lambda execution can run at most 15 minutes (900 seconds), the maximum configurable timeout. Work that needs longer must be split into smaller invocations or moved to a service built for long-running compute such as ECS, Fargate, or Step Functions for orchestration.

Init code runs once per environment, not once per request

Lambda runs everything outside the handler (SDK clients, database connection pools, configuration loads) once during the init phase of a new execution environment, then reuses that warm environment across later invocations where only the handler body runs. This is why you place expensive setup at module scope: it is amortized across many requests instead of repeated per call.

Trap Putting SDK client and connection-pool setup inside the handler, where it re-runs on every invocation and throws away the warm-environment reuse that makes module-scope init cheap.

6 questions test this
/tmp is ephemeral scratch space, never durable state

Each execution environment gets an ephemeral /tmp directory, configurable from 512 MB up to 10,240 MB (10 GB), that lives only for the life of that environment and can vanish at any time. Use it for scratch work within an invocation, but never as a database or for state that must survive: persist anything durable to S3, DynamoDB, or EFS instead.

Trap Caching data in /tmp and expecting later invocations to read it: a fresh environment starts with an empty /tmp and the data is gone.

Layers share libraries without rebundling: up to five per function

A Lambda layer packages shared libraries, a custom runtime, or dependencies separately so multiple functions reuse them instead of bundling the same code into every deployment package. You can attach up to five layers to a function, and their unzipped contents count against the same 250 MB unzipped limit as the function code.

Trap Treating layers as a way around the 250 MB unzipped limit, when layer contents count against that same ceiling and so don't expand your total package budget.

3 questions test this
Zip caps at 50 MB direct / 250 MB unzipped; containers go to 10 GB

A .zip deployment package is limited to 50 MB when uploaded directly and 250 MB unzipped including all attached layers, and uploading the zip from S3 raises only the direct-upload limit, not the 250 MB unzipped ceiling. A container image pushed to Amazon ECR raises the ceiling all the way to 10 GB, so large dependency trees, ML models, or custom runtimes point to a container image rather than a zip.

Trap Reaching for an S3-hosted zip to fit a 400 MB dependency tree: S3 lifts the upload size but the 250 MB unzipped limit still applies, so the answer is a container image.

5 questions test this
Execution role = what the function may do (outbound)

The execution role is the IAM role a Lambda function assumes at runtime to call other AWS services, such as reading a DynamoDB table or writing to S3: it is the function's outbound permission set. Without the right action in this role, the function's own SDK calls to other services fail with access-denied, regardless of who is allowed to invoke the function.

Trap Fixing the function's own access-denied SDK calls by editing who can invoke it, when outbound calls fail because the execution role lacks the action, not because of an invoke permission.

15 questions test this
Resource-based policy = who may invoke the function (inbound)

A Lambda function's resource-based policy controls inbound access: which accounts or services (API Gateway, Amazon S3, EventBridge, SNS) are permitted to invoke it. When you wire up an S3 event or an API Gateway integration, that grant is added here: it governs the opposite direction from the execution role, so anchor the two by direction of access.

Trap Adding the invoke permission to the execution role: inbound invoke rights belong on the function's resource-based policy, not the role the function assumes.

6 questions test this
Poll-based sources need permissions in the execution role, not a resource policy

For poll-based sources like SQS, Kinesis Data Streams, and DynamoDB Streams there is no resource-based policy granting them inbound access, because Lambda itself reads from the source on your behalf. The execution role must therefore carry the polling permissions (such as sqs:ReceiveMessage or the stream read/describe actions). Push sources need the resource policy; poll sources need the execution role.

Trap Adding a resource-based policy to let SQS or Kinesis invoke the function, when poll sources are read by Lambda using the execution role's permissions, so the resource policy does nothing here.

Synchronous invocation: the caller owns retries

A synchronous (RequestResponse) invocation (API Gateway, the CLI invoke default) runs the function and returns the result to a caller that waits. Lambda performs no retry on its own, so if the function errors the caller decides what happens next; a 500 from API Gateway means the client (or the integration) must handle the retry and error path.

Trap Expecting Lambda to retry a failed synchronous invocation, when on the RequestResponse path Lambda retries nothing and the waiting caller owns the retry and error handling.

Asynchronous invocation: Lambda owns retries, default 2

An asynchronous (Event) invocation (S3 events, EventBridge, SNS) hands the event to an internal Lambda-managed queue and immediately returns a 202, so the caller does not wait. Because the caller is already gone, Lambda owns the retries and re-attempts a failed invocation twice by default (three attempts total) with delays before the event is dropped unless you capture it.

Trap Reading the immediate 202 from an async invocation as the function having succeeded, when it only means the event was accepted onto Lambda's queue and the function may still fail and be retried.

5 questions test this
Capture both outcomes with destinations; a DLQ catches only failures

For asynchronous invocations, a dead-letter queue (an SQS queue or SNS topic) receives only the failed event payload, whereas Lambda destinations route both success and failure outcomes (with richer context including the request, response, and error) to SQS, SNS, EventBridge, or another function. Destinations are the modern, preferred mechanism, so when a question asks how to capture both success and failure of an async invocation, the answer is destinations.

Trap Choosing a dead-letter queue to capture successful async outcomes: a DLQ only ever receives failed events; success capture requires destinations.

13 questions test this
Event source mappings poll the source and own the batch/retry settings

An event source mapping is a Lambda-managed poller for stream and queue sources (SQS, Kinesis Data Streams, DynamoDB Streams, Amazon MSK) that reads records, groups them into a batch, and invokes the function with that batch. The mapping (not the function) owns the tuning and retry behavior, which is the key distinction from async invocation where retry settings live on the function.

Trap Tuning batch and retry behavior for an SQS or Kinesis source on the function's async settings, when for poll-based sources those settings live on the event source mapping instead.

1 question tests this
Batch size and batch window trade latency for fewer invocations

An event source mapping's batch size sets the maximum records per invocation, while the batch window (MaximumBatchingWindowInSeconds, up to 300 seconds) lets the poller wait to accumulate a fuller batch before invoking. Raising either reduces the number of invocations at the cost of added latency, so tune them together to balance throughput against how quickly records must be processed.

Trap Raising the batch window to cut invocation count without expecting added latency, when the poller waits to fill the batch, so records sit longer before processing.

Set an SQS visibility timeout of at least 6× the function timeout

When SQS is the event source, set the source queue's visibility timeout to at least six times the function's timeout. The longer window keeps an in-flight batch hidden from other consumers while the function processes it, so a slow batch is not redelivered and processed twice before the original invocation finishes.

Trap Leaving the SQS visibility timeout at the 30-second default for a multi-minute function: the message reappears mid-processing and gets handled again.

1 question tests this
Account concurrency defaults to 1,000 with a 100-execution shared floor

Concurrency is the number of requests in flight simultaneously across a function, and every function in a Region shares a default account ceiling of 1,000 concurrent executions (raisable via a quota request). Lambda always keeps at least 100 of that pool unreserved as a shared floor, so reserving capacity for one function can never starve all the others below that minimum.

Trap Reserving concurrency until the pool's unreserved remainder hits zero, when Lambda blocks reservations that would drop the shared unreserved floor below 100.

Reserved concurrency is a free cap-and-guarantee, not a warm pool

Reserved concurrency carves out a guaranteed slice of the account pool for one function and simultaneously caps that function at that number. It protects a downstream resource (say a database with a fixed connection limit) from being overwhelmed and guarantees the function some capacity against noisy neighbors. It adds no extra charge and pre-warms nothing, so a reserved environment still cold-starts.

Trap Reaching for reserved concurrency to eliminate cold starts: it caps and guarantees capacity but pre-warms nothing; the latency fix is provisioned concurrency.

Cut cold starts with provisioned concurrency, slimmer packages, and lighter init

A cold start is the latency added when a request lands with no warm environment and Lambda must download code, start the runtime, and run init before the handler. Reduce it by using provisioned concurrency for a deterministic fix, shipping slimmer packages with fewer dependencies for faster download and init, and moving heavy one-time setup outside the handler so it is reused across invocations.

Trap Adding RAM to a function purely to shrink cold starts, when more memory speeds the handler's CPU-bound work but doesn't remove the init phase, whereas provisioned concurrency does.

A published version is an immutable snapshot; $LATEST is mutable

Publishing a version freezes the function's code and configuration as an immutable, numbered snapshot, while $LATEST is the mutable working copy you keep editing. You publish a version to pin a known-good build that later releases and aliases can point at, since the numbered version can never change after publication.

Trap Expecting to patch the code of a published numbered version in place, when versions are immutable, so a fix means publishing a new version and re-pointing the alias.

Shift traffic safely with an alias and weighted routing

An alias is a named, movable pointer (for example prod) to a version, so callers target the alias and you re-point it to ship a release. An alias also supports weighted routing, splitting traffic between two versions (say 90% to v1 and 10% to v2) to run a canary or blue/green rollout and shift weight as confidence grows.

Trap Pointing an alias's weighted routing at $LATEST for a canary, when weighted aliases split traffic between two published numbered versions, not the mutable $LATEST.

Provisioned concurrency attaches to a version or alias, not $LATEST

Provisioned concurrency is configured on a specific version or alias, not on $LATEST. A deployment that re-points an alias to a fresh, un-provisioned version reintroduces cold starts until provisioning catches up on the new target, so plan provisioning around the release rather than assuming it follows the alias automatically.

Trap Expecting provisioned concurrency to follow an alias to a new version: the new version starts un-provisioned and cold-starts until you provision it.

VPC attachment reaches private resources but loses default internet egress

By default a Lambda function runs in a Lambda-managed network with internet access. Attaching it to a VPC by specifying subnets and security groups lets it reach private resources like an RDS instance, but it then loses default internet egress, so reaching the public internet or AWS service endpoints now requires a NAT gateway or VPC interface endpoints.

Trap Assuming a VPC-attached function keeps internet access: it does not; outbound to the internet or public AWS endpoints needs a NAT gateway or VPC endpoints.

Layer contents must use the runtime's directory convention under /opt

Lambda extracts every attached layer into /opt and adds language-specific subpaths to the search path, so a layer's zip must place dependencies in the directory the runtime expects: python/ (or python/lib/python3.x/site-packages/) for Python, nodejs/node_modules/ for Node.js. Dependencies zipped at the archive root are not found and imports fail with ModuleNotFound. The same rule lets container-image functions reuse a layer by copying its contents into /opt during the Docker build, since layers cannot be attached to container-based functions.

Trap Zipping libraries at the root of the layer archive: they must sit under python/ or nodejs/node_modules/ for the runtime to import them.

9 questions test this
Layer binaries must match the function's runtime version and the Linux/arm architecture

A layer's native code is tied to the runtime and platform it was built for: packages compiled against Python 3.12 fail on a 3.9 function, and libraries built on Windows or for the wrong CPU architecture fail in Lambda's Linux environment. Build layer dependencies (especially compiled ones like pandas/numpy) on Linux for the target runtime version and architecture (x86_64 or arm64). Interpreted languages like Go that compile to a single self-contained binary don't benefit from layers: bundle dependencies into the deployment package instead.

Trap Packaging a Lambda layer with pip on a Windows laptop and attaching it directly: Windows-compiled native modules are incompatible with Lambda's Linux runtime.

3 questions test this
Share a Lambda layer across accounts with add-layer-version-permission

Grant other accounts use of a layer version with lambda add-layer-version-permission, which sets a resource policy granting lambda:GetLayerVersion. Permissions are per layer VERSION, so each new version must be re-shared. Name a specific account ID as the principal for one account; to share with a whole AWS Organization, set the principal to '*' and pass the organization-id parameter so only accounts in that org can use it.

Trap Assuming sharing a layer covers future versions: each published version is a separate resource, so consumers can't see version N+1 until you run add-layer-version-permission for it.

7 questions test this
The Parameters and Secrets Lambda Extension caches values; SSM_PARAMETER_STORE_TTL controls staleness

The AWS Parameters and Secrets Lambda Extension fetches Parameter Store and Secrets Manager values over a local HTTP endpoint and caches them in the execution environment, cutting API calls, latency, and throttling for frequently invoked functions. The cache lifetime is the SSM_PARAMETER_STORE_TTL environment variable: a longer TTL means fewer API calls but a value can stay stale for that window after a change, so lower the TTL when freshness matters. Requests to the extension endpoint must include the X-Aws-Parameters-Secrets-Token header set to AWS_SESSION_TOKEN.

Trap Blaming Parameter Store when a Lambda keeps returning an old value after an update: it is the extension's cache; shorten SSM_PARAMETER_STORE_TTL.

4 questions test this
Fetch a whole Parameter Store hierarchy in one call with GetParametersByPath

When configuration is organized as a path hierarchy (e.g. /myapp/prod/database/...), retrieve all of it in a single GetParametersByPath call instead of one GetParameter per key; set Recursive=true to include nested levels. Storing the environment name in an env var and building the path lets the same function load per-environment config. The execution role needs ssm:GetParametersByPath permission for that path.

Trap Calling GetParameter (or GetParameters) per key for a whole tree: GetParametersByPath with Recursive=true returns the entire branch in one request.

4 questions test this
Emit structured JSON logs so CloudWatch Logs Insights can query them by field

To make Lambda logs searchable by custom fields (correlationId, errorType, status code) and Lambda context (request ID, function name), output structured JSON, either via the Lambda advanced logging controls (set log format to JSON and an application log level) or the Powertools for AWS Lambda Logger, whose inject_lambda_context decorator auto-adds request context. Then query with CloudWatch Logs Insights using fields/filter/stats, e.g. filter on a field and stats count(*) by bin(1h) to trend errors over time.

Trap Logging free-text strings and trying to parse them later: Logs Insights filters and aggregates by field only when entries are structured JSON.

6 questions test this

Application Data Stores

Read full chapter
  • Pick the data store from the access pattern, not the data shape
  • Keep large objects in S3 and store only the key in the database
  • A cache is never the source of truth
  • DynamoDB primary key is partition-only or partition + sort
  • Pick a high-cardinality partition key to avoid hot partitions
  • GSI: different key, addable any time, eventual reads only
  • LSI: same partition key, create-time only, strong reads allowed
  • On-demand absorbs spiky traffic; provisioned is cheaper for steady load
  • Read costs 1 RCU strong / 2 eventual per 4 KB; write 1 WCU per 1 KB
  • DynamoDB reads are eventually consistent by default; ask for strong only when needed
  • DynamoDB transactions are ACID but cost 2x the capacity
  • Use a conditional write with a version attribute for optimistic locking
  • Query targets one partition key; Scan reads the whole table
  • Every Query/Scan is capped at 1 MB and paginates via LastEvaluatedKey
  • Batch APIs cut round trips but are not atomic
  • TTL auto-deletes expired items for free, but on a best-effort delay
  • DynamoDB Streams give an ordered 24-hour change log for event-driven code
  • DAX is a zero-code, write-through cache for DynamoDB only
  • DAX only helps read-heavy DynamoDB workloads, and strong reads bypass it
  • Lazy loading caches on miss; write-through caches on write
  • Redis for replication and data structures; Memcached for simple key/value
  • Choose RDS/Aurora over DynamoDB when you need joins or multi-column filters
  • An AbortIncompleteMultipartUpload lifecycle rule cleans up failed multipart upload parts
  • CompleteMultipartUpload requires the upload ID plus every part's number and ETag
  • Every multipart part except the last must be at least 5 MB
  • A presigned URL dies when its signing credentials expire, regardless of ExpiresIn
  • Control versioned-bucket storage with NoncurrentVersionExpiration and NewerNoncurrentVersions
  • Lifecycle transitions tier objects down for cost; transition to Standard-IA needs a 30-day minimum
  • Deleting in a versioned bucket adds a delete marker; clean expired markers with ExpiredObjectDeleteMarker
  • Enable S3 Bucket Keys to slash SSE-KMS request costs at scale
  • RDS point-in-time recovery restores to a NEW instance within the backup retention window
  • Pick io2 Block Express for max IOPS/durability; gp3 to provision IOPS independently of size
  • Memory-intensive RDS workloads need a memory-optimized instance class
  • A Multi-AZ DB instance takes backups from the standby, avoiding primary I/O suspension
  • RDS storage autoscaling has a 6-hour cooldown and a minimum max-threshold
  • ACUUtilization near 100% means an Aurora Serverless v2 cluster is capped at its max ACUs
  • Set Aurora Serverless v2 minimum capacity to 0 ACUs to auto-pause when idle
  • Use RDS Proxy to stop Lambda concurrency from exhausting database connections

Unlock with Premium — includes all practice exams and the complete study guide.

Security

Authentication and Authorization

Read full chapter

Cheat sheet

Sharp facts the exam loves — scan these before test day.

Authentication proves who you are; authorization decides what you may do

Authentication and authorization are two separate steps, and an exam scenario almost always turns on telling them apart. Authentication verifies an identity (a SigV4-signed AWS request, a JWT validated by Cognito, or a federated login) while authorization is a distinct policy decision about what that verified identity may do, so a perfectly authenticated caller can still be denied. The practical consequence: a credential that proves identity (a Cognito ID token) is not the credential that grants access (an OAuth 2.0 access token or temporary AWS credentials).

Trap Treating a successful sign-in as proof a call is allowed: authorization is evaluated separately, so an authenticated principal can still hit an explicit deny.

An explicit Deny always beats any Allow

AWS authorization is allow + no-explicit-deny: a request is permitted only when some policy allows the action on the resource AND no policy anywhere contains an explicit Deny. A single explicit Deny overrides every Allow, which is why even an admin principal can be blocked from an S3 bucket whose bucket policy denies its ARN. Anything not explicitly allowed is denied by default (implicit deny), but that default is weaker than an explicit deny. Only an explicit Allow can overcome an implicit deny, and nothing overcomes an explicit one.

Trap Assuming an Allow on an admin or broadly-permissive policy can override an explicit Deny elsewhere, when a single explicit Deny wins over every Allow in the evaluation.

6 questions test this
Same account: an Allow in either the identity or the resource policy is enough

For a request within one account, AWS takes the union of the identity-based policy (attached to the user/role) and the resource-based policy (attached to the S3 bucket, Lambda function, SQS queue, etc.): an Allow in either one (or both) grants the action. Identity-based policies attached to a principal are themselves additive, so multiple attached policies combine. The notable exception is KMS, where the key policy must allow the principal explicitly for IAM permissions to take effect.

Trap Assuming KMS follows the union rule: a KMS key policy must grant the principal explicitly, so an identity policy alone is not sufficient.

Cross-account access needs an Allow in BOTH accounts

When the principal and the resource live in different accounts, the union rule no longer applies: you need an Allow in the caller's identity-based policy in its own account AND an Allow in the resource-based policy (or role trust policy) in the resource's account. Both sides must opt in, either one alone fails. This is the single behavioral difference between same-account and cross-account evaluation.

Trap Granting only the resource-based policy in the target account and expecting cross-account access to work: the caller's own account must also allow the action on its identity.

10 questions test this
Give compute a role, never embed long-lived access keys

Application code should carry an IAM role attached to the compute, not an access key ID and secret baked into code or config. Embedding static keys is the antipattern the exam punishes. With a role, the AWS SDK transparently obtains temporary credentials that auto-rotate and expire, so there is no long-lived secret to leak or rotate manually. Reach for the role mechanism that matches the compute: instance profile (EC2), task role (ECS), or execution role (Lambda).

Trap Storing IAM access keys in environment variables or config files on EC2/ECS/Lambda: attach a role instead and let the SDK fetch rotating STS credentials.

Match the role type to the compute: instance profile, task role, execution role

Each compute service exposes role credentials differently and the names are tested as distractors. An EC2 instance profile wraps a role and the SDK fetches credentials from the Instance Metadata Service. Enforce IMDSv2 (HttpTokens=required) to block SSRF-based credential theft. A Lambda execution role is set at function creation and auto-discovered by the SDK. For ECS, the task role is what the application code inside the container uses for its own AWS calls, distinct from the task execution role, which only lets the ECS agent pull images from ECR and ship logs to CloudWatch.

Trap Putting the application's AWS permissions on the ECS task execution role: that role is for the agent (ECR pull, CloudWatch logs); app calls use the task role.

STS sessions default to 1 hour, range 15 min to 12 h

When code switches identity it calls sts:AssumeRole, which returns temporary credentials valid for DurationSeconds: 3600 seconds (1 hour) when omitted. The value may range from 900 seconds (15 minutes) up to the role's configured maximum session duration, which an admin can set anywhere from 1 to 12 hours. Requesting more than the role's maximum fails, so the role's ceiling caps the session.

1 question tests this
Role chaining caps the session at 1 hour regardless of the role max

Role chaining (using one already-assumed role to assume a second role) always caps the new session at 1 hour, even if the target role's maximum session duration is set to 12 hours. This is a frequently tested gotcha: the 12-hour maximum applies to a directly assumed role, not to one reached by chaining from temporary credentials.

Trap Expecting a chained AssumeRole to honor a 12-hour role maximum: chaining silently caps the session at 1 hour.

3 questions test this
A trust policy Principal of :root means the whole account, not the root user

Who may assume a role is governed by the role's trust policy (its resource-based policy). A trust policy Principal of arn:aws:iam::<account-id>:root denotes account-level delegation: the entire account is trusted, NOT the AWS account root user. It means any IAM principal in that account whose own identity policy also permits sts:AssumeRole may assume the role. To trust a single entity, name it explicitly, e.g. arn:aws:iam::<account-id>:role/Build.

Trap Reading :root in a trust policy as the account's root user: it is account-wide delegation; the assuming principal still needs its own AssumeRole permission.

Require an ExternalId when a third party assumes your role

When a third-party SaaS assumes a role in your account, add a Condition on sts:ExternalId in the role's trust policy and share that agreed value with the vendor. The vendor must pass the matching ExternalId on AssumeRole or the call fails. This defends against the confused-deputy problem, where an attacker tricks the trusted third party into using its access on your behalf without knowing your secret value.

Trap Treating ExternalId as a secret password that authenticates the vendor, when it is a confused-deputy guard rather than a credential and offers no protection if the vendor is itself compromised.

8 questions test this
A permissions boundary or session policy caps permissions: it never grants them

Grants union, but ceilings intersect. Where a ceiling is present (a permissions boundary on the principal, or a session policy passed to AssumeRole) effective permissions become the intersection of the identity's grants and that ceiling, so a session can never exceed what the role's own policy already allows. A boundary with no matching grant in the identity policy authorizes nothing; you still need an explicit Allow in the identity policy for any action.

Trap Treating a permissions boundary or session policy as a way to add permissions: it only narrows; the underlying identity policy must still grant the action.

16 questions test this
A Cognito user pool authenticates users and issues three JWTs

A Cognito user pool is a user directory that performs authentication: users sign in and the pool returns an ID token, an access token (both JWTs you can verify against the pool's public JWKS), and a refresh token (opaque and encrypted, used only to mint new ID/access tokens). It answers "who is this user?": reach for a user pool when you need to sign your own application's end users in and protect your own API. The Hosted UI / managed login implements standard OAuth 2.0 grant flows (such as authorization code) so you do not hand-roll sign-in.

Trap Assuming the refresh token is a JWT you can verify against the JWKS like the others, when it is opaque and encrypted, usable only to mint new ID/access tokens.

Send the access token to authorize an API, not the ID token

Of the user-pool JWTs, the ID token carries identity claims (email, name, sub) and proves who the user is: consume it in your app to learn about the user, never to authorize a call. The access token is the OAuth 2.0 bearer token carrying scopes and groups, and it is what you present to a protected API to be authorized. The refresh token only mints new ID/access tokens and is never sent to your API.

Trap Sending the Cognito ID token to an API authorizer: it proves identity, not authorization; pass the access token, whose scopes the authorizer checks.

1 question tests this
Use a Cognito identity pool to hand a signed-in user temporary AWS credentials

A Cognito identity pool (federated identities) performs authorization to AWS: it exchanges a verified login (from a user pool, or a third-party IdP like Google, Apple, Facebook, or any SAML/OIDC provider) for temporary AWS credentials from STS, scoped by an IAM role. Reach for it only when a signed-in user must call an AWS service such as S3 or DynamoDB directly from the client. Identity pools also support an unauthenticated (guest) role, granting limited access to users who have not signed in.

Trap Reaching for an identity pool just to authenticate users or protect your own API: that is the user pool's job; the identity pool exists to vend AWS credentials.

User pool authenticates, identity pool federates to AWS: don't swap them

The decision rule the exam relentlessly tests: need to authenticate a user and protect your own API? Use a user pool and validate the access token. Need to give that user scoped AWS credentials to hit an AWS service directly? Use an identity pool, which returns STS credentials. Used together, the user pool authenticates and issues JWTs, then the identity pool swaps a token via STS for AWS credentials.

Trap Picking a user pool for "let the signed-in user write to S3 directly": that needs an identity pool's STS credentials, not just a JWT.

Cognito is for application users; IAM Identity Center is for your workforce

Match the identity service to the audience. Amazon Cognito signs in an application's external end users (customers of your app). IAM Identity Center signs in your own workforce: employees needing access to AWS accounts and business applications. The two are not interchangeable, and a scenario that names one where the other belongs is a deliberate distractor.

Trap Using Cognito for employee access to AWS accounts (or Identity Center for app customers): Cognito = app end users, Identity Center = workforce.

A Cognito or JWT authorizer gates a route declaratively, no code

An API Gateway Cognito authorizer (REST API) or JWT authorizer (HTTP API) validates an incoming bearer JWT's signature, expiry, and scopes/claims and allows or denies the route with no custom code. Point it at a Cognito user pool or any OIDC issuer and pass the access token. This is the default answer when sign-in is already Cognito and you only need standard token validation at the edge.

Trap Writing a Lambda authorizer to validate a standard Cognito/OIDC JWT, when the built-in Cognito or JWT authorizer does signature, expiry, and scope checks declaratively with no code.

2 questions test this
Use a Lambda authorizer for non-JWT tokens or bespoke logic

A Lambda authorizer (formerly custom authorizer) runs your function to return an allow/deny decision (an IAM policy document, or a simple boolean for HTTP APIs) and is the right choice for validating an opaque or non-Cognito token, or applying custom logic a declarative authorizer can't express. A TOKEN authorizer receives only the bearer token from a single header; a REQUEST authorizer receives the full request context (headers, query string, path, stage variables) and is needed when the decision depends on more than one input. Results can be cached by TTL to avoid invoking the function on every request.

Trap Choosing a TOKEN Lambda authorizer when the decision needs query strings or multiple headers: TOKEN sees only one header's token; use REQUEST for full request context.

8 questions test this
Use AWS_IAM authorization for SigV4 callers, not browser end users

API Gateway IAM authorization (AWS_IAM) requires the caller to sign the request with SigV4 using IAM credentials, and the request is evaluated through the same identity/resource-policy model as any AWS API call. It fits service-to-service or internal callers that already hold IAM credentials, not browser or mobile end users, who carry JWTs and should use a Cognito/JWT or Lambda authorizer instead.

Trap Choosing AWS_IAM authorization for browser or mobile end users, who hold JWTs rather than signable IAM credentials; SigV4/IAM auth fits service-to-service callers.

OIDC is the identity layer on top of OAuth 2.0's authorization framework

OAuth 2.0 is the authorization framework (delegating access via tokens); OIDC adds the identity (authentication) layer on top. Mapped to Cognito: the ID token is the OIDC artifact answering who the user is, and the access token is the OAuth 2.0 artifact carrying scopes for what the bearer may do. A JWT/Cognito authorizer enforces the access token's scopes at the API edge.

Trap Treating OAuth 2.0 itself as an authentication protocol, when OAuth 2.0 handles authorization and OIDC is the identity layer added on top to authenticate the user.

Edge authorizers gate the route; fine-grained rules go in code

API Gateway authorizers and IAM gate whether a caller may reach a route or action: they do not express per-resource business rules like "can THIS user edit THIS document?". For fine-grained, per-request decisions beyond what scopes capture, validate claims in your application code or use Amazon Verified Permissions. Don't expect a scope check at the edge to enforce row-level ownership.

Trap Expecting an edge scope or JWT authorizer to enforce row-level ownership like "can this user edit this document", when per-resource rules belong in code or Verified Permissions.

Apply least privilege and prefer MultiFactorAuthPresent for fresh-MFA checks

Each policy should grant only the actions and resources an identity needs, narrowed with Condition keys where possible. When gating a sensitive action on MFA, know the two condition keys differ: aws:MultiFactorAuthPresent is true only when the CURRENT credentials were obtained with MFA, while aws:MultiFactorAuthAge measures how long ago that MFA happened. Use the age key when you need recency, not just presence.

Trap Relying on aws:MultiFactorAuthAge to require MFA at all: the age key is absent (so a deny-on-absence can misfire) when no MFA occurred; aws:MultiFactorAuthPresent checks that MFA happened, the age key checks how recently.

Principal: "*" on a resource or trust policy means anyone, anywhere

A Principal of "*" on a resource-based or trust policy grants the action to any principal in any account (effectively public) and is almost always the wrong answer on a security scenario. Scope access instead to a specific principal ARN, a specific account, or aws:PrincipalOrgID to bound it to your organization. The exam dangles "*" as a tempting "it just works" option.

Trap Accepting Principal: "*" to make access "just work": it exposes the resource to every AWS account; scope to a principal ARN, account, or PrincipalOrgID.

ABAC compares aws:PrincipalTag to aws:ResourceTag in a StringEquals condition

Attribute-based access control grants access only when a tag on the calling principal matches a tag on the target resource. The policy uses a Condition with StringEquals comparing aws:ResourceTag/<Key> to the policy variable ${aws:PrincipalTag/<Key>} (for example team or Environment) so one policy scales to every team without per-team rules. When the principal is an IAM user rather than a tagged role, ${aws:username} is the matching variable against the resource's owner tag.

Trap Hard-coding a tag value as a literal string instead of the ${aws:PrincipalTag/...} policy variable: ABAC's whole point is the dynamic principal-vs-resource tag comparison.

5 questions test this
A Lambda authorizer must return principalId plus a policyDocument, with context values stringified

A REST API Lambda authorizer's output must include a principalId and a policyDocument (an IAM policy with execute-api:Invoke statements); omit either and API Gateway returns 500, not 403. Any context map it returns may hold only stringified primitives: you cannot set a JSON object or array as a context value. The backend reads those values via $context.authorizer.<key>.

Trap Returning a JSON object or array in the authorizer's context map: every context value must be a stringified primitive (string, number, boolean) or the request fails.

5 questions test this

Encryption with AWS Services

Read full chapter
  • Envelope encryption: a data key encrypts the data, a KMS key wraps the data key
  • A KMS key is the entity older docs still call a CMK
  • KMS HSMs are FIPS 140-3 Security Level 3, not 140-2 Level 2
  • GenerateDataKey returns the data key twice: plaintext to use now, ciphertext to store
  • KMS Encrypt caps at 4 KB, so anything larger needs envelope encryption
  • Customer managed keys give full control: policy, rotation, cross-account, direct use
  • AWS managed keys (aws/) rotate yearly on a fixed schedule you can't change
  • AWS owned keys are free but invisible, unauditable, and uncontrollable
  • Customer-managed rotation is optional and configurable; managed-key rotation is fixed yearly
  • The key policy is the root of trust; IAM is inert until it delegates
  • Grants are purely additive: one key, one principal, allow-only
  • Use a grant token to exercise a brand-new grant immediately
  • Encryption context is non-secret AAD that must match exactly on decrypt
  • Symmetric KMS keys are the default; pick asymmetric only for an exportable public key or signing
  • Multi-Region keys share key material across Regions so ciphertext decrypts locally
  • SSE-S3 is the zero-effort default that encrypts every new bucket since January 2023
  • SSE-KMS adds key policy, grants, and a CloudTrail audit of key use
  • SSE-C means you supply the raw key on every request and AWS stores nothing about it
  • DSSE-KMS applies two independent layers of KMS encryption for mandated dual-layer regimes
  • EBS and DynamoDB use the same envelope model; DynamoDB at-rest is always on
  • Encryption in transit is TLS, not KMS: don't provision a key for it
  • Client-side encryption keeps data opaque to AWS itself
  • ACM issues and auto-renews public TLS certificates at no charge
  • AWS Private CA issues private certificates and carries a charge (legacy name: ACM Private CA)
  • SSE-KMS read needs kms:Decrypt and write needs kms:GenerateDataKey on the key
  • S3 Bucket Keys cut SSE-KMS request cost up to 99% by switching to a bucket-level key
  • Changing a bucket's default encryption never re-encrypts existing objects
  • ACM auto-renews DNS-validated certs only while the public CNAME stays in place and the cert is in use
  • An ACM certificate must live in the same Region as the load balancer using it
  • ALB TLS version and ciphers come from a predefined ELBSecurityPolicy, not a custom one
  • The AWS Database Encryption SDK can only SIGN_ONLY a DynamoDB primary key, never encrypt it
  • AWS Database Encryption SDK attribute actions must match on decrypt, and you can't change an attribute to/from DO_NOTHING on existing data
  • Use the AWS KMS Hierarchical keyring with a branch-key supplier for multi-tenant DynamoDB encryption that minimizes KMS calls
  • A disabled KMS key drives an encrypted RDS instance into inaccessible-encryption-credentials-recoverable for 7 days
  • GenerateDataKeyWithoutPlaintext lets a key-generating component never touch plaintext key material

Unlock with Premium — includes all practice exams and the complete study guide.

Sensitive Data Management

Read full chapter
  • Never hard-code a secret: externalize it and fetch it at runtime by IAM role
  • Lambda env vars are encrypted at rest but are still the wrong home for credentials
  • Lambda's environment variables share a 4 KB total size cap
  • Reach for Secrets Manager when you need rotation or a value over 8 KB
  • Parameter Store SecureString gives KMS-encrypted storage with no per-secret charge
  • Advanced parameters add policies (including expiration) that Standard lacks
  • Use a String parameter or env var for non-secret config, not a per-secret store
  • Secrets Manager managed rotation needs no Lambda for its supported databases
  • Off the managed list, you supply a rotation Lambda implementing the four-step contract
  • Fetch the secret per use so the app picks up the rotated value
  • Classify data first so controls match its sensitivity
  • Scrub secrets and PII before they reach any log, metric, or trace
  • Use Amazon Macie to inventory PII in S3, and only in S3
  • Macie publishes findings to Security Hub directly, in ASFF format
  • Configuring a customer managed key on a Lambda function needs kms:CreateGrant and kms:Encrypt (plus kms:ListAliases for the console)
  • Deny kms:Decrypt on the function's CMK to hide Lambda env-var values while still allowing management
  • Lambda 'encryption helpers' add client-side encryption in transit and need kms:Decrypt at runtime
  • Use the alternating users rotation strategy for zero-downtime credential rotation
  • Cache secrets with the Secrets Manager client-side caching library, initialized outside the handler
  • A rotation Lambda in a no-internet VPC needs a Secrets Manager interface VPC endpoint
  • Reading a CMK-encrypted secret requires both secretsmanager:GetSecretValue and kms:Decrypt
  • Retrieving a SecureString parameter uses WithDecryption=true and needs kms:Decrypt on a CMK

Unlock with Premium — includes all practice exams and the complete study guide.

Deployment

Application Artifacts

Read full chapter

Cheat sheet

Sharp facts the exam loves — scan these before test day.

A deployment artifact is self-contained: it carries every runtime dependency

A deployment artifact is the immutable bundle you hand to AWS to run, and it must include the code plus every library the code imports at runtime, because the managed execution environment adds nothing you did not package. The one deliberate exception is environment-specific configuration and secrets, which are injected at deploy or run time so the same bundle promotes unchanged across dev, test, and prod. Configuration files and other static assets do travel inside the package; only per-environment values and credentials stay out.

Trap Assuming the managed runtime supplies common third-party libraries so you can leave them out of the bundle. Only the language's standard library and AWS SDK are present, so any other dependency you import must be packaged.

Match the Lambda .zip to the function's runtime AND architecture

A Lambda .zip archive holds your handler plus third-party libraries compiled for the function's runtime and CPU architecture: x86_64 or arm64. A package built for the wrong architecture passes upload but fails at invoke, because native dependencies are architecture-specific. When you switch a function between arm64 and x86_64, rebuild the dependencies for the new architecture.

Trap Assuming a working x86_64 .zip runs unchanged on arm64. Native extensions are compiled per-architecture and break at invoke, not at upload.

A Lambda .zip is capped at 50 MB direct upload and 250 MB unzipped

A Lambda .zip is limited to 50 MB when uploaded directly through the API, SDK, or console, and to 250 MB unzipped, a ceiling that includes the function code plus all attached layers and custom runtimes. A .zip larger than 50 MB cannot be uploaded inline; you upload it to Amazon S3 first and point Lambda at the S3 object. The 250 MB unzipped figure is the hard ceiling on total uncompressed size.

Trap Trying to push a .zip over 50 MB straight through the console or API. Anything larger must be staged in S3 and referenced from there.

3 questions test this
Use a container image in ECR when dependencies exceed the .zip limits

When a function's dependencies, native binaries, machine-learning models, or custom runtime exceed the .zip ceiling, package it as a container image instead. Lambda accepts an image up to 10 GB uncompressed, stored in Amazon ECR (Elastic Container Registry, AWS's managed image registry). The image bakes in its own dependencies, so it has no layers. Reach for .zip for small, pure-language or prebuilt-dependency functions; reach for a container image when size or native tooling rules .zip out.

Trap Picking a .zip for a large ML model or native-binary workload. It blows past the 250 MB unzipped cap, where a 10 GB container image is the intended path.

2 questions test this
A Lambda function's package type is fixed at creation

You choose .zip or container image when you create a Lambda function, and you cannot switch a function from one package type to the other afterward. To change type you create a new function. The package type is an immutable property, unlike code or configuration, which you can update in place.

Trap Assuming you can convert an existing .zip function to a container image in place. The type is locked at creation, so you must stand up a new function.

Lambda layers factor shared dependencies out of each function's .zip

A Lambda layer is a separate .zip of libraries or a runtime that multiple functions reference, so a common dependency is uploaded and stored once rather than rebundled into every function. A function can attach up to 5 layers. Layers cut duplication and per-function upload size, but a layer's unzipped contents still count toward the same 250 MB unzipped quota as the function code. They reduce duplication, not the total uncompressed ceiling. Layers apply only to .zip functions; a container-image function bakes its dependencies into the image and has no layers.

Trap Treating layers as a way around the 250 MB limit. A layer's unzipped size counts toward that same ceiling.

6 questions test this
SAM is an extension of CloudFormation, not a separate deployment engine

An AWS SAM (Serverless Application Model) template is an extension of CloudFormation: it adds shorthand serverless resource types such as AWS::Serverless::Function, and those expand into standard CloudFormation resources at deploy time. Because SAM resources sit beside plain CloudFormation resources in one template, anything you can write in raw CloudFormation you can also write in a SAM template. There is no separate SAM provisioning service to choose instead of CloudFormation.

Trap Treating "SAM vs CloudFormation" as mutually exclusive engines. SAM is CloudFormation plus a macro, so it is never an alternative to it.

Transform: AWS::Serverless-2016-10-31 is the macro that turns a template into SAM

The line Transform: AWS::Serverless-2016-10-31 at the top of a template is the CloudFormation macro that marks it as a SAM template and expands the shorthand serverless resources into standard CloudFormation resources during deployment. Spotting that exact Transform value (or an AWS::Serverless::* resource type) is how you identify a template as SAM rather than plain CloudFormation.

sam build, package, deploy map onto how the artifact reaches AWS

The SAM CLI ships an artifact in three steps. sam build resolves and installs each function's dependencies into a local .aws-sam/build directory. This is where the self-contained artifact is assembled. sam package uploads those built artifacts to Amazon S3 (or to Amazon ECR for container images) and rewrites the template's local code paths to the uploaded URIs. sam deploy submits that rewritten template to CloudFormation to create or update the stack, and it runs the package step for you if you have not already.

Trap Thinking sam build is the step that uploads the artifact to S3. Build only assembles dependencies locally; sam package (or sam deploy) is what uploads to S3 and rewrites the code URIs.

4 questions test this
Know what Parameters, Mappings, and Outputs do in a template

A CloudFormation template uses Parameters as the per-environment input knobs (instance size, table name, log level) that vary one template into different stacks; Mappings as static lookup tables resolved at deploy time (for example region to AMI ID); and Outputs to export values such as an API URL or bucket name for other stacks or operators to consume. Passing different Parameter values is what lets the same template produce a dev stack or a prod stack without editing it.

Trap Using Mappings to feed per-environment values like instance size or table name. Mappings are static lookup tables, so caller-supplied per-environment differences belong in Parameters.

Ref, Fn::GetAtt, and Fn::Sub each wire templates differently

CloudFormation intrinsic functions connect resources. Ref returns a resource's primary identifier (or a parameter's value), typically the name or ID. Fn::GetAtt returns a specific attribute of a resource, such as a bucket's ARN or a DynamoDB table's stream ARN, which Ref does not give you. Fn::Sub substitutes variables into a string, letting you interpolate references inline. Pick Fn::GetAtt when you need an attribute like an ARN; pick Ref when the primary ID or a parameter value is enough.

Trap Using Ref to get a resource's ARN. Ref returns the primary identifier, so an ARN or other attribute needs Fn::GetAtt.

Use a change set to preview a stack update before executing it

A change set is a preview of exactly what a stack update would add, modify, or delete, generated before you execute it, the safe way to update a stack because you confirm the blast radius first, including any resource replacements. When a question asks how to see the impact of a template change before applying it, the answer is a change set, not a blind stack update.

Trap Running a plain stack update when the stem wants a preview of the changes. Only a change set shows what will change before you commit.

Nested stacks compose large architectures from reusable child templates

Nested stacks let one parent template reference child templates as resources, so a large architecture is built from reusable, modular pieces instead of one monolithic file. Each child is its own stack the parent provisions, which keeps templates within size limits and lets common patterns (a standard VPC, a logging bucket) be reused across stacks.

Trap Reaching for cross-stack references (Export/Fn::ImportValue) when the goal is to provision and manage child templates as one unit. That lifecycle composition is what nested stacks provide, while exports only share values between independently managed stacks.

Drift detection reports where a stack diverged from its template

Drift detection compares a stack's actual resource configuration against what its template declares and reports any differences caused by out-of-band changes, for example, someone editing a resource directly in the console or CLI. It is the tool for catching configuration that no longer matches the source-of-truth template, not for previewing a planned update.

Trap Reaching for a change set to find manual console edits. Change sets preview intended updates, while drift detection surfaces unintended out-of-band changes.

1 question tests this
Know where each artifact type is stored

Lambda .zip artifacts and CloudFormation/SAM templates are stored in Amazon S3; container images live in Amazon ECR; and your language packages (the npm, PyPI, or Maven dependencies a build pulls in) can be hosted privately in AWS CodeArtifact, AWS's managed package repository. CodeArtifact gives builds a controlled, versioned source for dependencies rather than pulling them from public registries directly.

Trap Reaching for S3 or ECR to host private npm, PyPI, or Maven packages. Hosting those package registries is CodeArtifact's job, while S3 holds .zip/templates and ECR holds container images.

Use AppConfig to change behavior without redeploying code

AWS AppConfig (a feature of AWS Systems Manager) decouples configuration from code so you can change application behavior (a feature flag, an operational setting) without a code deployment. Its defining feature is validators that check the configuration is syntactically and semantically correct before a rollout, so a bad config never reaches the running application. It can source data from its own hosted store, SSM Parameter Store, Secrets Manager, or S3. Reach for AppConfig when the stem stresses changing behavior at runtime or validating config before it goes live.

Trap Choosing plain Parameter Store when the question stresses validating config before rollout or toggling a feature flag without a deploy. That validation-and-rollout machinery is AppConfig, not a bare named value.

Use SSM Parameter Store for simple named config values

SSM Parameter Store holds plain configuration values an application fetches at runtime by name, such as an endpoint URL or a numeric setting. A SecureString parameter additionally encrypts the value with AWS KMS, which is how you store a sensitive value without Secrets Manager. Choose Parameter Store when you just need a named value retrieved at runtime, not the validation-and-rollout machinery of AppConfig.

Trap Reaching for Secrets Manager for any sensitive value when no rotation or generation is needed. A KMS-encrypted SecureString parameter stores a secret in Parameter Store at lower cost.

Build the artifact once; inject per-environment differences at deploy and run time

Bake environment-specific values into the artifact and you must build a separate bundle per environment, which breaks the guarantee that what you tested is byte-for-byte what you ship. Instead build the artifact once and vary it externally: CloudFormation/SAM Parameters supply the deploy-time differences, while AppConfig and Parameter Store supply the run-time differences. The artifact and its template stay identical across dev, test, and prod.

Never package secrets into the artifact: reference them at runtime

Database passwords, API keys, and other secrets are never packaged into the deployment artifact; the bundle carries only the code that fetches the secret. At runtime the application reads the value from AWS Secrets Manager or an SSM SecureString parameter. Embedding the secret in the package leaks it into every copy of the artifact and ties the build to one environment.

Trap Putting a database password or API key in an environment variable baked into the package. It leaks into the artifact; fetch it at runtime from Secrets Manager or a SecureString instead.

Elastic Beanstalk config files must be .config files inside an .ebextensions folder at the bundle root

To customize an Elastic Beanstalk environment (install packages, set options), place YAML or JSON configuration files with the .config extension in a folder named exactly .ebextensions (leading dot required) at the root of the source bundle. Files outside that folder, without the .config extension, or in a folder misnamed 'ebextensions' are silently ignored. When zipping, include hidden dot-folders (for example zip -r app.zip * .[^.]*) and produce a bundle with no parent folder so .ebextensions sits at the root.

Trap Naming the folder 'ebextensions' without the leading dot, or zipping with a wrapping parent folder. Elastic Beanstalk then ignores the config files entirely.

6 questions test this
Elastic Beanstalk uses a Procfile for multiple processes and cron.yaml for worker periodic tasks

An Elastic Beanstalk source bundle is a single ZIP (or a WAR for Java) with special files at its root. A Procfile declares multiple long-running processes (the main one must be named 'web' and is listed first) and Elastic Beanstalk restarts any that exit; it is also how you pass JVM options per Java process. A worker environment's periodic background jobs are defined in a cron.yaml at the root, each with a name, URL path, and a CRON schedule. The multi-container ECS-managed Docker platform instead uses a Dockerrun.aws.json v2 at the root (with a .dockercfg in S3 for private-registry auth).

Trap Hand-rolling a startup script for several processes. Elastic Beanstalk monitors and restarts processes only when they are declared in a root Procfile (web first).

4 questions test this
An Elastic Beanstalk application-version source bundle must be in-region and readable

When you create an Elastic Beanstalk application version from a source bundle in S3, the bucket must be in the same AWS Region as the environment, or the call fails with an invalid-S3-location error. If the bundle lives in a custom bucket with a restrictive policy, also grant the IAM identity creating the version s3:GetObject (and s3:GetObjectVersion) on the bundle object, or the create fails with access denied.

Trap Assuming a valid, existing S3 bundle is enough. A different-Region bucket fails as 'invalid S3 location', and a restrictive bucket policy fails as access denied without explicit s3:GetObject.

2 questions test this
An Elastic Beanstalk application version lifecycle policy prunes old versions automatically

Elastic Beanstalk caps application versions per Region; to avoid hitting the quota, configure an application version lifecycle policy that auto-deletes old versions by a MaxCount rule (keep the N most recent) or a MaxAge rule. Set DeleteSourceFromS3 to true to also remove the source bundles from S3, or false to keep them for compliance. Elastic Beanstalk never deletes a version currently deployed to any environment, and it applies the policy each time you create a new version.

Trap Worrying a lifecycle policy will remove an in-use version. Elastic Beanstalk protects versions deployed to any environment; DeleteSourceFromS3 only controls whether the S3 bundle is also deleted.

4 questions test this
A Python Lambda layer must put dependencies under a python/ directory at the .zip root

Lambda extracts layer contents into /opt, and the Python runtime searches /opt/python for imports, so a Python layer .zip must contain a top-level 'python/' directory with the libraries inside it. A layer whose modules sit at the archive root (or under any other folder) extracts to the wrong place and the function fails with 'Unable to import module' / ModuleNotFoundError even though the libraries are present.

Trap Zipping the dependencies at the layer root. Python only finds them under /opt/python, so they must be wrapped in a top-level python/ directory.

4 questions test this
Override a CloudFormation stack policy for one update with --stack-policy-during-update-body

A stack policy that denies updates to a protected resource (such as an RDS database) blocks even an authorized urgent change. To update the protected resource for a single operation without permanently editing the stack policy, run update-stack with --stack-policy-during-update-body (or --stack-policy-during-update-url) specifying a temporary policy that allows the needed action. The override applies only to that update; the original stack policy stays intact afterward.

Trap Calling SetStackPolicy to permanently loosen the policy for one urgent change. The temporary --stack-policy-during-update-body override leaves the original policy unchanged.

4 questions test this
CloudFormation stack policies gate on Update:Modify, Update:Replace, and Update:Delete

Stack policy statements use granular update actions: Update:Modify (in-place changes that keep the physical ID), Update:Replace (changes that recreate the resource with a new physical ID), and Update:Delete (removal). To protect a resource from being recreated or removed while still allowing harmless in-place edits, Deny Update:Replace and Update:Delete (or Allow Update:Modify and Deny Update:Replace). When a stack policy is set, all resources are protected by default, so guarding one resource means an Allow for the rest plus a targeted Deny.

Trap Denying all Update:* to protect a resource when in-place changes must still be allowed. Deny only Update:Replace and Update:Delete so Update:Modify edits still go through.

3 questions test this
Continuous CloudFormation drift detection uses the AWS Config managed rule plus EventBridge and SNS

For scheduled, continuous drift detection across stacks and accounts, enable the AWS Config managed rule cloudformation-stack-drift-detection-check: it runs DetectStackDrift, marking a stack COMPLIANT when IN_SYNC and NON_COMPLIANT when DRIFTED. Pair it with an EventBridge rule matching the Config compliance-change events to notify via SNS. This is the low-overhead automated answer, versus the manual CLI sequence detect-stack-drift then poll describe-stack-drift-detection-status then describe-stack-resource-drifts.

Trap Scripting repeated detect-stack-drift CLI calls for ongoing monitoring. The low-overhead continuous solution is the AWS Config managed rule with EventBridge and SNS.

4 questions test this
Protect versioned S3 artifacts with Object Lock, MFA Delete, and noncurrent-version lifecycle rules

On a versioning-enabled artifact bucket, choose the protection by requirement. Object Lock governance mode blocks deletes for most users but lets holders of s3:BypassGovernanceRetention override; compliance mode blocks deletes for everyone including root until retention expires, and supports legal holds. MFA Delete (enabled only by the root account via CLI/API) requires MFA to permanently delete a version or change versioning state. To prune by version, NoncurrentVersionExpiration with NewerNoncurrentVersions keeps the N most recent noncurrent versions and expires older ones after NoncurrentDays, and NoncurrentVersionTransition moves them to a cheaper class first.

Trap Reaching for governance mode when the rule says even the root account must never delete. That requires compliance mode; governance mode is overridable with the bypass permission.

5 questions test this
ECR enhanced scanning uses Amazon Inspector for continuous OS + language-package vulnerability scans

ECR basic scanning covers only operating-system packages. To scan both OS and programming-language dependencies (Python, Java, etc.) and to keep re-scanning images as new CVEs are published, enable enhanced scanning on the private registry with a continuous scan filter. It integrates with Amazon Inspector. Inspector emits scan-finding events to EventBridge, so an EventBridge rule to SNS gives automated alerts when new findings appear.

Trap Choosing basic scanning for language-dependency CVEs or ongoing monitoring. Basic scans only OS packages once; continuous OS+language scanning requires enhanced scanning via Inspector.

4 questions test this

Development Environment Testing

Read full chapter
  • Test serverless code on a fidelity ladder: unit, then SAM local, then a deployed stage
  • Every sam local subcommand runs the function in Docker, so Docker is a hard prerequisite
  • Use sam local invoke for a one-shot scripted run of a single function
  • Use sam local start-api when the test driver is an HTTP request to your API Gateway routes
  • Use sam local start-lambda when an SDK or the CLI invokes the function, not an HTTP call
  • Generate the test event with sam local generate-event instead of hand-writing the JSON
  • Save event JSON as a named test event in the Lambda console to re-run a deployed function
  • Run sam validate --lint to catch template errors before a deploy rolls back
  • cfn-lint runs standalone against any CloudFormation template, not just SAM ones
  • Mock (stub) the AWS SDK client so unit tests stay fast, offline, and deterministic
  • Isolate deployed test traffic with a separate API Gateway stage or a separate AWS account
  • Use the IAM policy simulator to check whether a policy allows an action without a real request
  • SAM local can't emulate IAM, real latency, or account quotas. Those need a deployed environment
  • Run the test ladder automatically in CodeBuild via buildspec.yml phases
  • Run the X-Ray daemon with -o (local mode) off EC2 to skip the metadata check
  • Point the SDK at a non-default daemon with AWS_XRAY_DAEMON_ADDRESS; bind it with -b for other containers
  • X-Ray annotations are indexed and searchable; metadata is not
  • Set AWS_XRAY_CONTEXT_MISSING to LOG_ERROR to avoid context-missing exceptions outside a request
  • Use sam local -d/--debug-port to attach an IDE debugger to a function
  • Use sam local --warm-containers EAGER to reuse containers between invocations
  • Point the AWS SDK at LocalStack via endpoint_url http://localhost:4566 with placeholder credentials
  • LocalStack does not enforce IAM, so validate permissions against real AWS
  • Cloud9 managed temporary credentials are restricted and disabled when a non-owner shares the environment

Unlock with Premium — includes all practice exams and the complete study guide.

Automated Deployment Testing

Read full chapter
  • Automate deployment testing by wiring tests into the release so a failure stops promotion
  • Gate a pipeline with a CodePipeline test action placed before the deploy action
  • Read the four strategies as one model with a different increment size
  • In-place vs blue/green is an orthogonal axis from the increment size
  • In-place is EC2/on-premises only; Lambda and ECS are blue/green only
  • Lambda all-at-once shifts 100 percent immediately, leaving no validation window
  • Canary configs shift a 10 percent slice first, then the rest after a fixed hold
  • Linear configs shift equal slices on a fixed interval, gating every step
  • ECS offers parallel canary and linear configs to Lambda's
  • EC2/on-premises configs are minimum-healthy-host counts, not traffic percentages
  • A Lambda deployment has exactly two scriptable AppSpec hooks: BeforeAllowTraffic and AfterAllowTraffic
  • An ECS deployment adds install hooks plus AfterAllowTestTraffic for a pre-prod test listener
  • An AppSpec hook function must report back with putLifecycleEventHookExecutionStatus
  • Enable automatic rollback to reverse a bad release without a human in the loop
  • Rollback redeploys the last good revision as a new deployment, it does not restore the old one
  • CodeDeploy shifts a Lambda alias's weighted traffic between immutable versions
  • Point staging at an immutable version so production promotes the exact tested artifact
  • Elastic Beanstalk immutable deployment gives the fastest, safest rollback
  • Elastic Beanstalk all-at-once is the quickest policy but drops service during the deploy
  • Elastic Beanstalk rolling deployments update instances in batches, so both versions run at once
  • Elastic Beanstalk traffic splitting is the canary policy: a traffic percentage for an evaluation period
  • Elastic Beanstalk blue/green is a swap of environment URLs, and the database must be decoupled first
  • SAM DeploymentPreference with Alarms is how a Lambda canary auto-rolls-back
  • A CodeDeploy/SAM pre-traffic hook needs lambda:InvokeFunction to test the new version
  • CodeDeploy alarm-based rollback needs cloudwatch:DescribeAlarms on the service role
  • CodeDeploy keeps EC2 instances available during a deploy via MinimumHealthyHosts

Unlock with Premium — includes all practice exams and the complete study guide.

CI/CD Deployment

Read full chapter
  • A CodePipeline pipeline is ordered stages of actions that pass artifacts
  • CodePipeline action categories are a fixed vocabulary the exam maps to services
  • Pipelines exchange files through an S3 artifact store, not direct copies
  • A source change starts a pipeline; an EventBridge rule beats polling
  • Use a manual approval action to pause a pipeline for sign-off
  • CodePipeline runs under IAM service roles, not as a user
  • CodeBuild compiles, tests, and packages with no build servers to manage
  • buildspec.yml lives at the source root by default
  • CodeBuild runs four build phases strictly in order
  • Pull build secrets from Secrets Manager or Parameter Store, never plaintext env vars
  • Enable CodeBuild caching when builds re-download the same dependencies
  • CodeDeploy deploys a built artifact onto compute and never compiles
  • The AppSpec file manages a CodeDeploy deployment as lifecycle event hooks
  • The Lambda/ECS AppSpec may be YAML or JSON and names a version to deploy
  • The CodeDeploy agent is required only for EC2/on-premises deployments
  • The source action reads a versioned Git revision, so branches and tags drive releases
  • Connect GitHub, GitLab, or Bitbucket sources through CodeConnections
  • Know the CodeDeploy EC2 in-place lifecycle event order
  • CodeDeploy's ApplicationStop runs scripts from the PREVIOUS revision, so it is skipped on a new instance
  • CodeDeploy only fails a lifecycle hook when its script returns a non-zero exit code
  • A CodePipeline Lambda invoke action must call PutJobSuccessResult or PutJobFailureResult
  • CodeCommit triggers: event types, no FIFO SNS, and the Lambda resource policy
  • Use a buildspec reports section to surface test results in the CodeBuild console

Unlock with Premium — includes all practice exams and the complete study guide.

Troubleshooting and Optimization

Root Cause Analysis

Read full chapter

Cheat sheet

Sharp facts the exam loves — scan these before test day.

Match the symptom to the question, and the right telemetry signal falls out

AWS root cause analysis reads four complementary signals, each answering exactly one question: CloudWatch Logs answers "what happened in this one request?", CloudWatch metrics answer "how often, how much, is it trending?", X-Ray answers "where in the call chain did time or errors build up?", and CloudTrail answers "who called which AWS API, when, from where?". The DVA-C02 reflex is this mapping itself, so when a stem describes a symptom, name the question first and the service follows. A spike on a dashboard is a metrics question, a single failed transaction is a logs question, slow end-to-end latency across services is a trace question, and an unexplained config or permission change is a CloudTrail question.

Trap Grepping raw logs by hand to judge whether errors are trending up. That is a metrics job, and scanning log text for a rate is slow and error-prone.

Reach for CloudWatch Logs when you need the exact error on one request

CloudWatch Logs holds the line-level event record: the timestamped text or JSON your code and managed services emit into a log group such as /aws/lambda/<function-name>. It is the signal that tells you what exactly happened inside a single failed transaction, down to the stack trace or message. Use it when you already know which request failed and need its precise cause, rather than when you are trying to spot a trend across many requests, which is a metrics question.

Trap Reaching for CloudWatch Logs to spot a trend across many requests, when a rate or count over time is a metrics question and Logs answers what happened inside one specific request.

1 question tests this
Use Logs Insights as the reactive hunt for one request's failure

CloudWatch Logs Insights is an interactive query engine over your log groups with a purpose-built query language built from fields, filter, stats, sort, and limit commands, run ad hoc during an investigation to surface the exact events behind a failure. It can search multiple log groups in one query, and a running query is stopped after 60 minutes if it has not completed. Reach for it when the question is "what was the error on request X?", the reactive hunt, in contrast to a metric filter, which is a standing rule that watches every new log event.

Trap Standing up a Logs Insights query to continuously watch for and alert on new failures, when Insights is an on-demand reactive hunt and a metric filter plus an alarm is the proactive standing rule.

7 questions test this
Convert a log pattern into a metric with a metric filter, then alarm on it

A metric filter is a standing rule on a log group that scans every incoming event for a pattern (say the term ERROR or a 5xx status) and emits a numeric CloudWatch metric you can alarm on. This is how you turn unstructured log text into a measurable trend that pages you proactively, rather than discovering the problem only when you go hunting. Logs Insights is the reactive query you run after the fact; a metric filter plus an alarm is the proactive trigger set up in advance.

Trap Trying to detect a rising error rate by repeatedly running a Logs Insights query: use a metric filter feeding an alarm so the trend pages you instead of you polling for it.

A CloudWatch metric is identified by its namespace plus dimensions

A metric is a numeric time series (error count, p99 latency, throttle count) identified by a namespace that isolates one service's metrics (for example AWS/Lambda) plus a set of dimensions, which are name/value pairs such as FunctionName=checkout that scope the series to one resource. Dimensions are how you narrow a chart from "all functions" down to the single function that is misbehaving. You set alarms on metrics, and a metric spike bounds the time window you then drill into with logs and traces.

Trap Treating a namespace/dimension pair as interchangeable with another after the fact, forgetting that a dimension is part of a metric's identity, so an alarm created on a specific dimension combination does not retroactively cover series you never published with those exact dimensions.

4 questions test this
A flat Lambda Errors line with rising Throttles means concurrency is exhausted, not broken code

Lambda's Errors metric counts only function errors (an unhandled exception or a timeout raised inside your handler) so a rising Errors line points at your code. Throttles is a separate metric that counts invocations rejected because concurrency was exhausted, surfaced as TooManyRequestsException / HTTP 429, and a throttle does NOT increment Errors. So if Errors stays flat while Throttles climb, the fix is to raise reserved or provisioned concurrency or the account concurrency limit, not to read your handler code.

Trap Debugging handler logic because invocations are failing, when the Errors metric is flat and only Throttles is rising. A throttle is a concurrency limit, not a code fault.

Watch Lambda Duration climbing toward the timeout before it turns into errors

Lambda's Duration metric measures execution time per invocation, and it is the early-warning signal for a performance regression: a Duration line trending up toward the configured timeout is what eventually turns into function errors once invocations actually hit the ceiling and time out. Treat a rising Duration as the metric to investigate proactively, since by the time it shows up as timeouts in the Errors metric the user impact has already begun.

Trap Waiting for the Errors metric to rise before investigating a slowdown, since by then invocations are already timing out, whereas a Duration line climbing toward the configured timeout warns you while there is still headroom to act.

Distinguish a function error from an invocation error before you touch code

A function error is raised inside your handler (an unhandled exception or a timeout) and is the case where you actually debug your code. An invocation error happens before or around your code: a throttle when concurrency is exhausted (TooManyRequestsException / 429), or a permission/configuration failure such as the caller lacking lambda:InvokeFunction. Read the error type and the metric first, because the two classes point at different layers: fix the handler for a function error, but fix concurrency limits or IAM permissions for an invocation error.

Trap Reading application logs to fix a "not authorized to perform lambda:InvokeFunction" failure. That is an invocation-level permission problem, not a bug in your handler.

Use X-Ray to find where in a multi-service call chain the time or errors accumulate

X-Ray records each request as a segment (the work one service did) and breaks that service's downstream calls into subsegments, then stitches all segments sharing one trace ID into a service map, the visual graph of services and the edges between them, color-coded for errors, faults, and throttles. Reach for it when end-to-end latency is high across microservices but no single component looks broken: the service map shows which hop accumulates the time, which a single service's logs cannot reveal.

Trap Reading one service's logs to explain high end-to-end latency across services: logs from a single hop cannot show where across the chain the time was spent.

1 question tests this
X-Ray default sampling traces the first request each second plus 5% of the rest

To control cost and overhead X-Ray does not trace every request: the default sampling rule records the first request each second (the reservoir) plus five percent of any additional requests that second. The consequence for root cause analysis is that a low-volume intermittent error may simply not have been sampled, so the absence of a trace is not proof the request succeeded. When you need fuller capture, raise the sampling rate or add a targeted sampling rule for the affected path.

Trap Concluding an intermittent request succeeded because no X-Ray trace exists for it, when default sampling captures only the first request each second plus 5% of the rest, so a missing trace may just mean it was not sampled.

1 question tests this
Use CloudTrail to find who called which AWS API, when, and from where

CloudTrail is the control-plane audit trail: it records management-event API activity, the principal who made the call, the API action, the timestamp, and the source IP. Reach for it when behavior changed without a deployment you initiated, or a call suddenly started being denied, for example a security group was edited or an IAM role's permissions changed. Logs, metrics, and traces observe your application, so none of them captures a control-plane change; CloudTrail is the only one of the four signals that answers "who or what changed it?".

Trap Hunting through CloudWatch Logs or X-Ray for the cause of an unexplained permission or config change, when those observe your application and only CloudTrail records who called the AWS API that changed it.

CloudTrail is an API audit, not an application debug log

CloudTrail records that a principal called an API (say ModifySecurityGroupRules at 14:02 from a given IP) but it does not contain your function's internal log lines, error messages, or request payloads. For those you read CloudWatch Logs. Keep the boundary sharp: CloudTrail answers the "who changed the configuration" question only, and using it to debug application behavior will turn up nothing because it never sees inside your code.

Trap Searching CloudTrail for an application's runtime error or request payload: it logs AWS API calls, not your code's internal events; read CloudWatch Logs for those.

Triage a status code by side: 4xx blames the caller, 5xx blames the server

A 4xx status generally means the caller (your application or the client) sent a bad request (malformed input, missing auth, a 429 throttle), while a 5xx means the serving side faulted. This split tells you which direction to investigate first. On a multi-service path do not assume a 5xx is your code: narrow it by checking which hop reported the 5xx on the X-Ray service map, since the failing service may be a downstream dependency rather than your own.

Trap Assuming every 5xx on a multi-service path is your own service's fault, when the X-Ray service map may show the 5xx originated at a downstream dependency you call rather than in your code.

Correlate signals on a shared time window rather than trusting one alone

The disciplined RCA loop aligns timestamps across signals: when a metric spikes, note the exact window, then pull the X-Ray traces, the CloudWatch Logs lines, and any CloudTrail event inside that same window. A 5xx spike whose window lines up with a CloudTrail ModifySecurityGroupRules event is a configuration regression, not a code bug, a conclusion no single signal could reach. Metrics localize the blast radius and bound the window; logs and traces then explain exactly what failed inside it.

Trap Declaring a code bug from a 5xx spike alone, when correlating its time window against CloudTrail can reveal a ModifySecurityGroupRules event that points at a configuration regression rather than your code.

Read an ECS task failure from stopCode and stoppedReason first, and treat exit code 137 as out-of-memory

When ECS tasks start then stop, run describe-tasks and read the stopCode (the category, such as ResourceInitializationError or EssentialContainerExited) and stoppedReason (the human-readable detail) before anything else. The most common container-level cause is a SIGKILL: a container exit code of 137 (128 + 9) means the OOM killer terminated it for exceeding its task-definition memory limit, so the fix is to raise the allocated memory, not to debug code. A ResourceInitializationError points instead at setup failing before the app ran (logging-driver or secrets connectivity).

Trap Reading container application logs to explain a 137 exit when there are none: the process was killed by the OS for exceeding its memory limit, which surfaces in stopCode/stoppedReason, not in app output.

4 questions test this
A Fargate awslogs ResourceInitializationError is missing connectivity or task-execution-role permission, not bad code

Fargate tasks using the awslogs driver fail at startup with ResourceInitializationError / 'failed to initialize logging driver' for one of two reasons. First, connectivity: a task in a private subnet with no NAT gateway needs an interface VPC endpoint for CloudWatch Logs (com.amazonaws.region.logs), and that same private-subnet rule causes silently missing logs even when the task stays up. Second, permission: the task execution role must allow logs:CreateLogStream and logs:PutLogEvents, which the AmazonECSTaskExecutionRolePolicy managed policy grants. The identical 'unable to pull secrets or registry auth' failure needs the task's security group to allow outbound HTTPS (443) to Secrets Manager / ECR.

Trap Assuming a NAT gateway is enough on its own: the task's security group must still permit outbound 443 to reach CloudWatch Logs, Secrets Manager, or ECR endpoints.

6 questions test this
Lambda emits two X-Ray segments: AWS::Lambda is the service, AWS::Lambda::Function is your code

Active tracing makes Lambda record two segments per invocation, so the X-Ray service map shows two nodes for one function. AWS::Lambda is the Lambda service overhead (scheduling, creating or unfreezing the execution environment, and downloading function code) and an error or latency here is a platform/init problem (including throttling), not your code. AWS::Lambda::Function is the handler running, so an error or high latency on that node is in your application logic. Decide which node shows the symptom before you touch anything.

Trap Debugging handler code because the function's X-Ray node shows latency, when the slow node is AWS::Lambda, which is service overhead like cold-start init or throttling rather than your application logic.

5 questions test this
Create a field index to make Logs Insights lookups on a high-volume field fast and cheap

When Logs Insights queries that filter on a field such as requestId scan huge volumes and run slow and expensive, create a field index policy for that field (PutIndexPolicy / console) without changing the log format. Queries written as filter requestId = 'value' (equality) then skip log events known not to contain the indexed value, cutting scan volume and cost. Across hundreds of log groups, the filterIndex command restricts a query to only the groups indexed on that field.

Trap Expecting a field index to speed up a non-equality filter such as a wildcard or range on requestId, when the index only lets an equality match skip events known not to contain the indexed value.

5 questions test this
Build Logs Insights trend queries with stats and bin, and match timeouts on 'Task timed out'

To turn logs into a time series, use stats with aggregations grouped by bin(5m) (for example stats count(*) by endpoint, bin(5m)); bin() creates the time buckets a chart needs. Filter HTTP errors by numeric range (filter status>=400 and status<=499 for 4XX). Lambda timeouts do NOT log as ERROR (they emit a 'Task timed out' message) so capturing all failures needs a pattern like filter @message like /ERROR/ or @message like /Task timed out/.

Trap Filtering only on /ERROR/ to count all Lambda failures, when a timeout emits a 'Task timed out' message rather than ERROR, so it slips past that filter unless you match it explicitly.

6 questions test this
Use VPC Reachability Analyzer to find the blocking component without sending traffic

VPC Reachability Analyzer performs static configuration analysis between a source and destination (it sends no packets) making it the tool to proactively prove a path or pinpoint what blocks it (security group, network ACL, or route table) before any traffic flows. It reports the blocking component with explanation codes. Because it snapshots configuration at analysis time, after you change a rule you must run a NEW analysis; re-reading the old result still shows the stale, pre-change state.

Trap Expecting an existing Reachability Analyzer result to update after you fix a security group: it is a point-in-time snapshot, so you must analyze the path again.

6 questions test this
A VPC Flow Log REJECT means a security group or NACL denied the traffic

An action=REJECT record means a security group or network ACL blocked the traffic; check the security group first since it is evaluated first and is the most common cause. Because NACLs are stateless, return traffic is evaluated independently and needs explicit ephemeral-port rules. This is why one direction can show ACCEPT while the reply shows REJECT, or why an SG that allows a port still fails when the subnet's NACL does not. Flow logs only appear once there is real traffic on the interface (the log group is created on first traffic), and custom formats can add fields like instance-id, subnet-id, flow-direction, and tcp-flags.

Trap Blaming the security group for a REJECT on return traffic when the outbound request showed ACCEPT, when NACLs are stateless, so the reply needs its own explicit ephemeral-port rule that the SG, being stateful, does not require.

6 questions test this
API Gateway logging needs an account CloudWatch role, and some client errors are never logged

Before any API Gateway logs appear, the account must have an IAM role (trusting apigateway.amazonaws.com, with AmazonAPIGatewayPushToCloudWatchLogs) configured in API Gateway Settings; without it, requests succeed but produce no logs. Even with logging on, API Gateway does not write certain client errors to execution logs (such as 403 from a wrong resource path, and excessive 429 or 413 errors), so enable custom access logging to capture them. To surface integration failures behind 500 errors, add $context.integrationErrorMessage to the access log format.

Trap Assuming execution logging captures every failed request, then concluding a missing 403 means the call never reached API Gateway, when certain client errors like a wrong-path 403 are absent from execution logs unless you turn on custom access logging.

4 questions test this
Read RDS ReplicaLag carefully: -1 means replication is broken, and an undersized replica lags

A ReplicaLag value of -1 does not mean zero lag: it means replication is not active or the lag can't be determined (for MySQL/MariaDB, equivalent to Seconds_Behind_Master = NULL), so check the replication state and event log for errors. Sustained positive lag often comes from a read replica with a smaller instance class than the primary: a replica replays the primary's full write load and must match or exceed its compute/storage to keep up. For consistent reads, the app can check ReplicaLag and route time-sensitive queries only to replicas below an acceptable threshold.

Trap Reading a ReplicaLag of -1 as zero lag and a perfectly healthy replica, when the value actually signals replication is not active or cannot be measured, so the replica may be receiving nothing.

4 questions test this

Code Observability

Read full chapter
  • Instrumentation emits telemetry; observability is the ability to ask new questions
  • Logs, metrics, and traces map to CloudWatch Logs, CloudWatch metrics, and X-Ray
  • Emit structured JSON logs so Logs Insights can query fields directly
  • Put a correlation ID on every log line to join one request's signals
  • EMF publishes a custom metric from a log line with no PutMetricData call
  • PutMetricData is the synchronous API for publishing a custom metric
  • Custom metrics are standard 1-minute resolution unless you set StorageResolution to 1
  • Keep metric dimensions low-cardinality; every unique combination is a billed metric
  • The X-Ray SDK captures downstream AWS SDK and HTTP calls as subsegments
  • Use X-Ray annotations for anything you filter on, metadata for bulky context
  • Distributed tracing needs the trace-context header propagated across every hop
  • CloudWatch ServiceLens / Application Signals correlates traces, metrics, and logs on one map
  • Alarm on a metric and publish to SNS to turn telemetry into a notification
  • New CloudWatch log groups never expire until you set a retention policy
  • X-Ray traces need Active tracing plus write permission, and API Gateway must be redeployed
  • X-Ray annotations follow strict key rules, cap at 50 per trace, and cannot go on Lambda's parent segment
  • Set awslogs-stream-prefix so ECS log streams trace back to a task and container
  • When EMF metrics never appear, check the AWS/Logs error metrics and the timestamp window
  • Use single_metric to emit EMF metrics that need different dimension sets in one invocation
  • Enable Container Insights enhanced observability for task- and container-level metrics
  • Use CloudWatch Application Insights to auto-correlate problems and fan out via EventBridge

Unlock with Premium — includes all practice exams and the complete study guide.

Application Optimization

Read full chapter
  • Cache only pays off when reads vastly outnumber writes
  • Pick the cache by what it caches and where it sits
  • Use CloudFront to cache HTTP responses at the edge
  • Cache content based on request headers by adding them to the cache key
  • Turn on API Gateway stage caching to skip repeat backend calls
  • Use ElastiCache when the data source is not DynamoDB
  • Use DAX for microsecond DynamoDB reads with no app rewrite
  • A strongly consistent read bypasses the cache entirely
  • Lambda memory is the single dial. It sets CPU too
  • A cold start is the one-time init of a fresh execution environment
  • Provisioned concurrency removes cold starts but bills idle
  • Reserved concurrency caps capacity: it does NOT pre-warm
  • SnapStart is free only for Java runtimes
  • Initialize clients and connections outside the handler to reuse them
  • Batch DynamoDB operations instead of looping single-item calls
  • Batch SQS messages to cut request count on both ends
  • Paginate large result sets; never Scan a whole table on the hot path
  • Keep the SDK's default exponential backoff with jitter for AWS calls
  • Right-size with measurement first, then the right tool for the scope
  • Commit to ElastiCache Reserved Nodes only for steady, always-on caches
  • Trade ongoing cost for lower latency only when traffic is predictable
  • Prefer managed and serverless services to delete operational overhead
  • Reduce calls before scaling the backend or adding a cache
  • CloudFront Minimum TTL overrides shorter origin Cache-Control, and Default TTL applies when the origin sends none
  • Version file names to push updates immediately instead of paying for CloudFront invalidations
  • Combine write-through with lazy loading and a TTL to keep ElastiCache fresh and lean
  • Rising ElastiCache Evictions with near-100% memory means scale up the node or add shards
  • High EngineCPUUtilization with low CPUUtilization is Redis's single command thread saturating
  • Measure API Gateway caching with CacheHitCount/CacheMissCount, and add query strings as cache key parameters
  • Multi-AZ DB cluster gives sub-35s failover and readable standbys; read replicas scale reads

Unlock with Premium — includes all practice exams and the complete study guide.