Other services (Step Functions and smaller services) — DVA-C02 Mastery Guide
- Sources: course chapters 21 (other serverless services: Step Functions, AppSync, Amplify) and 24 (other services: SES, OpenSearch, Athena, MSK, ACM and Private CA, Macie, AppConfig). The course serves as the syllabus only; all wording, examples and questions here are original.
- Written 2026-10-12; values checked against AWS docs on 2026-10-12 (Step Functions Developer Guide and API Reference, AppSync Developer Guide and API Reference, Amplify Hosting and Amplify docs, AppConfig User Guide and quotas, SES Developer Guide, OpenSearch Service, Athena, MSK, ACM, AWS Private CA and Macie guides, AWS pricing and What's New pages). The marker
⚠️ Changed: <current>. Older material may say <old>.flags a value or behaviour that differs from the course or older practice tests: learn the current value.⚠️ unverifiedmarks a value that could not be confirmed; (volatile) marks a value AWS changes often (quotas, prices). Every change is listed in Changed vs older material; rows you confirm are added tonotes/current-facts.md.- Tier 2 (deployment and messaging), bank target 35 questions. Domains: Development with AWS Services (primary), Deployment, Security.
- How to use it:
/explain <topic>teaches from these sections (concept → exam facts → developer angle → traps → 3 check questions);/quiz other-servicesasks bank questions whosesource_notelinks point at the## N.headings below;/drill other-servicesfires short recall prompts fromnotes/other-services-cheat-sheet.md. Suggested loop: read a section and answer its practice questions before opening the answers →/explainwhat is still unclear →/quiz other-services→/drill other-services.
How Other services (Step Functions and smaller services) appears on DVA-C02
This module pairs one service that DVA-C02 tests in depth, AWS Step Functions, with a set of services that mostly appear as the right answer, or as a tempting distractor, in "which service or setting" questions:
- Orchestration (Step Functions, §1–§6): reading and writing Amazon States Language, choosing the integration pattern (
.sync,.waitForTaskToken), handling failures withRetryandCatch, shaping data withResultPathor JSONata, and choosing Standard or Express under a duration, rate, cost or exactly-once constraint. - APIs and hosting for front ends (AppSync §7–§8, Amplify §9): GraphQL resolvers and data sources, real-time subscriptions, the five AppSync authorization modes, Git-based hosting with previews and Cypress end-to-end tests.
- Runtime configuration (AppConfig §10): changing feature flags and settings without a code deployment, validators, gradual deployment strategies with alarm-based rollback, and how code fetches configuration.
- Pick the managed service (§11–§14): SES for application email, OpenSearch for search, Athena for SQL over S3, MSK when Kafka compatibility matters, ACM and Private CA for certificates, Macie for sensitive data in S3.
Typical question shapes: a scenario with one deciding constraint (LEAST operational overhead, no code changes, exactly-once, lowest cost); an ASL snippet or error name to diagnose (States.DataLimitExceeded, ExecutionAlreadyExists, TaskTimedOut, UnauthorizedException); "Which TWO…" with five options; the exact Resource suffix, API action, header or directive that does the job.
Domain map: Development with AWS Services → §1–§8, §10–§13 · Deployment → §6 (versions, aliases, testing), §9 (Amplify CI/CD and tests), §10 (AppConfig rollouts) · Security → §8 (AppSync authorization), §11 (SES IAM conditions), §13 (MSK authentication and encryption), §14 (ACM, Private CA, Macie).
Out of scope here (owned by other modules): Lambda invocation types, destinations and limits → see notes/lambda-guide.md; API Gateway integrations, custom domains and WebSocket APIs → see notes/api-gateway-guide.md; Kinesis Data Streams shards, the KCL and Amazon Data Firehose → see notes/sqs-sns-kinesis-guide.md; SAM AWS::Serverless::StateMachine → see notes/sam-guide.md §3; Cognito user and identity pools → see notes/advanced-identity-guide.md; KMS, Parameter Store and Secrets Manager → see notes/security-encryption-guide.md; ACM certificates on load balancers and SNI → see notes/ec2-mastery-guide.md §9; DynamoDB Streams → see notes/dynamodb-guide.md §11; EventBridge, X-Ray, CloudWatch Logs subscription filters and CloudWatch Evidently → see notes/monitoring-guide.md; CloudFront → see notes/cloudfront-guide.md; S3 → see notes/s3-guide.md; CodePipeline and CodeBuild → see notes/cicd-guide.md. No OS administration or network design.
| # | Topic | Why it is on the exam |
|---|---|---|
| 1 | Step Functions state machines and state types | ASL structure, Choice/Map/Parallel behaviour, idempotent starts, versions, aliases and redrive |
| 2 | Step Functions task states and service integrations | The Resource URI decides what runs and how long the task waits; direct integrations replace glue code |
| 3 | Step Functions error handling with Retry and Catch | Retry fields and defaults, backoff math, States.* error names and what States.ALL misses |
| 4 | Step Functions input and output processing | ResultPath and friends, JSONata, variables and the 256 KiB payload limit |
| 5 | Step Functions callbacks with task tokens and activities | Pausing for humans or external systems, heartbeats, activity workers and their timeouts |
| 6 | Step Functions Standard vs Express workflows | Duration, semantics, history, pricing and supported patterns decide the type |
| 7 | AppSync GraphQL APIs and resolvers | GraphQL over many data sources, JavaScript resolvers, subscriptions, caching |
| 8 | AppSync authorization modes | Five modes, directives, Lambda authorizer responses, custom domains |
| 9 | Amplify hosting and end to end tests | Git-based hosting with previews, amplify.yml, Cypress tests, Amplify Gen 2 |
| 10 | AppConfig feature flags validators and deployment strategies | Config changes without redeploying, safe rollouts, how code reads configuration |
| 11 | SES for application email | Sandbox, identities, SMTP vs API, IAM conditions, event publishing |
| 12 | OpenSearch and Athena for search and analytics | Search next to a database; serverless SQL on S3 and how to cut its cost |
| 13 | MSK vs Kinesis Data Streams | Kafka compatibility vs AWS-native streams; KRaft, Express brokers, Serverless, IAM auth |
| 14 | ACM Private CA and Macie | Where certificates must live, renewal, exportable and private certificates; PII discovery in S3 |
1. Step Functions state machines and state types
Concept
- Step Functions runs workflows described as state machines: a JSON document in the Amazon States Language (ASL) with
StartAt(the first state) andStates(a map of named states). Each run is an execution with its own input, output and event history; the console draws the graph and colours each step of a run. Retries, timeouts, branching and waiting move out of application code into the definition, where they are visible and auditable. - Each state does one job:
Taskcalls a service or a worker;Choicebranches on the data;Parallelruns a fixed set of branches at the same time;Maprepeats a sub-workflow for every item of a collection;Waitpauses;Passpasses or reshapes data (handy for stubs and debugging);SucceedandFailend the run. - The state machine runs under an execution role (trusted by
states.amazonaws.com) that needs permission for every service it calls. Whoever starts it needsstates:StartExecution; whoever creates or updates it also needsiam:PassRolefor that role. - Ways to start an execution: SDK or CLI
StartExecution;StartSyncExecution(Express only, §6); an API Gateway service integration; an EventBridge rule or pipe; a parent state machine (nested workflow); any code that holds credentials.
Exam facts
- Every non-terminal state needs
Nextor"End": true. AChoicestate has neither at state level (each rule carriesNext) and can never end a workflow;SucceedandFailare terminal, andFailsetsErrorandCause. - Choice: rules are checked in order and the first match wins. JSONPath rules use
Variableplus an operator (StringEquals,NumericGreaterThan,BooleanEquals,TimestampLessThan,IsPresent,IsNull,StringMatches, combined withAnd/Or/Not; the…Pathvariants compare two input fields). JSONata rules useCondition. If no rule matches and there is noDefault, the execution fails, so always setDefault. - Wait:
Seconds,Timestamp,SecondsPathorTimestampPath, bounded by the execution limit (1 year Standard, 5 minutes Express). Parallel: the output is an array with one element per branch, in branch order; an unhandled failure in any branch fails the state and stops the other branches. - Map, Inline mode (default): iterates over an array in the state input (
ItemsPathin JSONPath,Itemsin JSONata) with up to 40 concurrent iterations; every iteration's events land in the parent's history.MaxConcurrency0(default) means "as parallel as possible",1means one at a time in order. ⚠️ Changed:ItemProcessorandItemSelectordefine the iteration. Older material may sayIteratorandParameters(still accepted, deprecated). - Map, Distributed mode: each iteration (or batch of items) runs as a child execution with its own history, up to 10,000 in parallel.
ItemReaderreads items straight from S3 (JSON or CSV files, object listings);ToleratedFailureCountorToleratedFailurePercentagesets how many items may fail beforeStates.ExceedToleratedFailureThreshold;ResultWriterwrites results to S3. The parent must be a Standard workflow (children may be Express). - Runtime errors can occur in every state type except
PassandWait;RetryandCatchexist only onTask,ParallelandMap(§3). - Quotas (hard unless noted): definition size 1 MB; API request size 1 MB; state machine, execution and activity names up to 80 characters; state machine and activity names are unique per account and Region, Standard execution names unique per state machine (reuse rules below); execution history 25,000 events per Standard execution (reaching it fails the run); 1,000,000 open Standard executions per account and Region (
ExecutionLimitExceeded, adjustable). Payload size: §4. - Idempotent starts (Standard only):
StartExecutionwith the samenameand the same input as a running execution returns the original response; a different input, or a name whose execution already closed, returnsExecutionAlreadyExists(HTTP 400). A name can be reused 90 days after its execution closes. Express starts are not idempotent and names can be reused at once. - Versions and aliases:
PublishStateMachineVersionfreezes a numbered, immutable version (…:stateMachine:orders:3). An alias (…:stateMachine:orders:PROD) points to one version or splits executions between two versions by weight (weights add up to 100). Quotas: 1,000 published versions and 100 aliases per state machine. Starting with the unqualified ARN runs the latest revision. - Redrive:
RedriveExecution(states:RedriveExecution) restarts a failed, aborted or timed-out Standard execution from the failed step within 14 days of its end, with the same execution ARN, definition and input; successful steps are not rerun and retry counters reset. Express executions can't be redriven. ⚠️ Changed: redrive exists for executions started on or after 15 November 2023. Older material may say a failed execution can only be started again from the beginning.
Developer angle
orders.asl.json: large orders wait for review, then a notification and a database write run in parallel through direct integrations (§2):
{
"StartAt": "CheckAmount",
"States": {
"CheckAmount": { "Type": "Choice", "Default": "Fulfil",
"Choices": [{ "Variable": "$.total", "NumericGreaterThan": 1000, "Next": "HoldForReview" }] },
"HoldForReview": { "Type": "Wait", "Seconds": 3600, "Next": "Fulfil" },
"Fulfil": { "Type": "Parallel", "End": true, "Branches": [
{ "StartAt": "Notify", "States": { "Notify": { "Type": "Task", "End": true,
"Resource": "arn:aws:states:::sns:publish",
"Parameters": { "TopicArn": "arn:aws:sns:eu-west-1:111122223333:order-events",
"Message.$": "States.Format('Order {} accepted', $.orderId)" } } } },
{ "StartAt": "Record", "States": { "Record": { "Type": "Task", "End": true,
"Resource": "arn:aws:states:::dynamodb:putItem",
"Parameters": { "TableName": "Orders",
"Item": { "pk": { "S.$": "$.orderId" }, "status": { "S": "ACCEPTED" } } } } } }
] }
}
}
- CLI:
aws stepfunctions create-state-machine --name orders --type STANDARD --definition file://orders.asl.json --role-arn <role>, thenstart-execution --state-machine-arn <arn> --name order-8812 --input '{"orderId":"8812","total":42.5}',describe-execution --query statusandredrive-execution --execution-arn <arn>. - The execution role's trust policy allows
sts:AssumeRoleforstates.amazonaws.com; addaws:SourceArn(arn:aws:states:<region>:<account>:stateMachine:*) andaws:SourceAccountconditions to block confused-deputy use. - In boto3:
sfn.start_execution(stateMachineArn=arn, name=order_id, input=json.dumps(payload)); catchsfn.exceptions.ExecutionAlreadyExiststo detect a duplicate with different input. - Use an alias ARN in callers (
…:orders:PROD) and move the alias, so a new version can take 10 % of executions before it takes all of them.
Traps
- Trap: "Add
"End": trueto the Choice state." → Reality: a Choice state can't end a workflow; route to aSucceedorFailstate. - Trap: "Without a matching rule the Choice state is simply skipped." → Reality: the execution fails; set
Default. - Trap: "Use an Inline Map over a million S3 objects." → Reality: Inline Map runs 40 iterations at a time inside a 25,000-event history; use Distributed Map with an
ItemReader. - Trap: "A client retry with the same execution name starts a second run." → Reality: for Standard, same name and input while running returns the first execution; a different input fails with
ExecutionAlreadyExists. - Trap: "Redrive the failed Express execution." → Reality: redrive exists only for Standard executions, within 14 days.
Practice questions
-
A payments team starts a Standard workflow from an API each time a customer submits an order. Client network retries sometimes repeat the same request, and the payment steps must never run twice for one order. Which solution meets this requirement with the LEAST custom code?
- A. Switch to an Express workflow and pass the order ID in the execution input.
- B. Use the order ID as the execution
namewhen callingStartExecution. - C. Add a
Retryfield withMaxAttemptsset to0on the paymentTaskstate. - D. Write each order ID to a DynamoDB table from the client and check it before starting the execution.
-
A data team must process 3 million JSON objects stored under one S3 prefix with high parallelism. A few malformed objects are expected and must not fail the whole job. Which TWO configurations meet these requirements? (Select TWO.)
- A. A Map state in Distributed mode with an
ItemReaderthat lists the S3 prefix. - B. A Map state in Inline mode with
MaxConcurrencyset to0. - C. A Parallel state with one branch for each object.
- D. A
ToleratedFailurePercentageon the Map state that allows a small share of failed items. - E. An Express workflow as the parent so that the job runs at a higher rate.
- A. A Map state in Distributed mode with an
Answers
- B. Standard
StartExecutionis idempotent by name: a repeat with the same name and input returns the original execution instead of starting another. A fails because Express starts are not idempotent and run at least once; C only stops retries inside one execution, not duplicate executions; D adds custom code and still races between the write and the start. - A, D. Distributed Map reads the objects from S3 and runs up to 10,000 child executions, and a tolerated failure threshold keeps a few bad items from failing the run. B is limited to 40 concurrent iterations and the 25,000-event history; C needs a static branch per object; E fails because a Distributed Map parent must be a Standard workflow and Express runs stop at 5 minutes.
2. Step Functions task states and service integrations
Concept
- A
Taskstate'sResourcefield decides what is called and how long the state waits. Task types: Lambda functions, other AWS services, activities (workers you host, §5) and HTTP Tasks (any HTTPS API). - Optimized integrations (
arn:aws:states:::<service>:<action>) cover the most used services (Lambda, DynamoDB, SNS, SQS, ECS/Fargate, EKS, Batch, Glue, Athena, CodeBuild, EventBridge, API Gateway, EMR, SageMaker AI, Bedrock and Step Functions itself) and add conveniences such as waiting for jobs and shaped results. - AWS SDK integrations (
arn:aws:states:::aws-sdk:<service>:<apiAction>) call API actions of more than two hundred services directly, with the API's own parameter names. They are the answer when a Lambda function would only forward one API call. - The suffix on
Resourcepicks the integration pattern: none = Request Response (move on as soon as the service answers),.sync= Run a Job (wait for the job to finish),.waitForTaskToken= Callback (wait until someone returns a token, §5). - Calling services directly removes functions you would otherwise deploy, monitor, secure and pay for, and keeps every call visible in the execution history.
Exam facts
- Lambda: the recommended form is
arn:aws:states:::lambda:invokewithFunctionName(name, ARN, version or alias) andPayload. It returns a wrapper (Payload,StatusCode,ExecutedVersion, …), so select the function's own result with"OutputPath": "$.Payload"orResultSelector(JSONata:$states.result.Payload). The legacy form puts the function ARN directly inResourceand returns the raw function output. - Pattern support (Standard workflows): Request Response everywhere.
.syncfor jobs: Batch, ECS/Fargate, EKS, Glue, Glue DataBrew, Athena, CodeBuild, EMR, SageMaker AI, Bedrock, MediaConvert and nested Step Functions; not Lambda, DynamoDB, SNS, SQS, EventBridge or API Gateway..waitForTaskToken: Lambda, SQS, SNS, ECS/Fargate, EKS, EventBridge, API Gateway, Bedrock, nested Step Functions and SDK integrations; not DynamoDB, Athena, Batch, Glue or CodeBuild. DynamoDB supports Request Response only; SDK integrations never support.sync. Express workflows support Request Response only: no.sync, no.waitForTaskToken(§6). - How
.syncwaits: Step Functions follows the job through EventBridge events plus polling (polling only, for cross-account calls), which uses your API quota; the execution role needs the extra permissions the console generates (for examplestates:DescribeExecution,states:StopExecutionandevents:PutTargets,events:PutRule,events:DescribeRuleon the managed rule for nested workflows). If a.synctask is aborted (execution stopped, a sibling branch failed), Step Functions tries, best effort, to stop the job. - Nested workflows:
arn:aws:states:::states:startExecution.syncreturns the child's output as an escaped string,…startExecution.sync:2returns it as JSON; passAWS_STEP_FUNCTIONS_STARTED_BY_EXECUTION_ID(from$$.Execution.Id) in the child input to link the two in the console. - Timeouts:
TimeoutSecondsdefaults to 99,999,999 (effectively none), so a task can wait until the execution limit; set it on every task.HeartbeatSecondsmust be lower thanTimeoutSeconds. JSONPath also acceptsTimeoutSecondsPathandHeartbeatSecondsPathfrom the input. - HTTP Task:
arn:aws:states:::http:invokewithApiEndpoint,Methodand an EventBridge connection (Authentication.ConnectionArn) for credentials; each call has a hard 60-second limit (States.Http.Socket) and HTTP Tasks are throttled at a bucket of 300 with 300 refills per second. - Cross-account: the
Credentialsfield ({"RoleArn": "arn:aws:iam::222233334444:role/…"}, or a path) makes the execution role assume another role for that one task.ResourceARNs can't cross Regions or partitions. Request fields go in JSONPathParametersor JSONataArguments(§4).
Developer angle
Optimized Lambda call, an SDK integration with no Lambda in between, and a container job that the workflow waits for:
{
"StartAt": "ScoreOrder",
"States": {
"ScoreOrder": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "score-order:live", "Payload.$": "$" },
"OutputPath": "$.Payload", "TimeoutSeconds": 30, "Next": "TagInvoice" },
"TagInvoice": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:s3:putObjectTagging",
"Parameters": { "Bucket": "invoices-111122223333", "Key.$": "$.invoiceKey",
"Tagging": { "TagSet": [{ "Key": "scored", "Value": "true" }] } },
"ResultPath": null, "TimeoutSeconds": 30, "Next": "RunReport" },
"RunReport": { "Type": "Task", "Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": { "Cluster": "reports", "TaskDefinition": "monthly-report:7", "LaunchType": "FARGATE",
"NetworkConfiguration": { "AwsvpcConfiguration": { "Subnets": ["subnet-0abc1234def567890"] } } },
"TimeoutSeconds": 3600, "End": true }
}
}
- Least-privilege execution role for the first two states:
lambda:InvokeFunctionon thescore-order:livealias ARN ands3:PutObjectTaggingonarn:aws:s3:::invoices-111122223333/*; the.syncstate also needsecs:RunTask,ecs:DescribeTasksandecs:StopTask,iam:PassRolefor the task roles, andevents:PutTargets,events:PutRule,events:DescribeRuleon the managed rule. - The console generates complete policies for optimized integrations but only partial permissions for SDK integrations, and uses
"Resource": "*"when the resource arrives in the input: complete and scope them yourself. - An
AccessDeniedorStates.Permissionserror on a task almost always means the execution role (not the caller) lacks the action.
Traps
- Trap: "Write a small Lambda function that calls DynamoDB
PutItem." → Reality:arn:aws:states:::dynamodb:putItem(or an SDK integration) does it with no function. - Trap: "Append
.syncto the Lambda invoke so the workflow waits for the function." → Reality: Lambda doesn't support.sync; the defaultlambda:invokecall already returns the function's result. - Trap: "Poll a Glue job with a Wait and Choice loop." → Reality:
glue:startJobRun.syncwaits for completion with no loop. - Trap: "The optimized Lambda integration passes the function's object straight to the next state." → Reality: it is wrapped in
Payload; select it withOutputPathorResultSelector. - Trap: "An HTTP Task can wait 5 minutes for a slow partner API." → Reality: each HTTP Task call is capped at 60 seconds; use a callback or an asynchronous API.
Practice questions
-
A workflow must start a container job on Amazon ECS with Fargate and continue to the next state only after the container exits. Which
Resourcevalue meets this requirement with the LEAST code?- A.
arn:aws:states:::ecs:runTask - B.
arn:aws:states:::lambda:invokecalling a function that pollsDescribeTasksuntil the task stops - C.
arn:aws:states:::ecs:runTask.sync - D.
arn:aws:states:::aws-sdk:ecs:runTaskfollowed by a Wait and Choice loop that checks the task status
- A.
-
A state machine uses one Lambda function whose only job is to write an item to a DynamoDB table and another whose only job is to publish a message to an SNS topic. The team wants to remove both functions without changing workflow behaviour. Which TWO changes meet this requirement? (Select TWO.)
- A. Replace the first function with an activity whose worker writes the item.
- B. Replace the first function with a Task state that uses
arn:aws:states:::dynamodb:putItem. - C. Replace the second function with an HTTP Task that calls the SNS API endpoint.
- D. Append
.waitForTaskTokento both Lambda Task states. - E. Replace the second function with a Task state that uses
arn:aws:states:::sns:publish.
Answers
- C. The
.syncpattern makes Step Functions wait until the ECS task stops. A returns as soon as ECS accepts the request; B and D work but add a polling function or loop that.syncmakes unnecessary. - B, E. The optimized DynamoDB and SNS integrations call
PutItemandPublishdirectly from the workflow. A replaces a function with a worker you must host; C is meant for third-party HTTPS APIs authenticated through EventBridge connections, not SigV4-signed AWS APIs; D keeps both functions and adds callbacks nobody sends.
3. Step Functions error handling with Retry and Catch
Concept
- Errors are identified by case-sensitive names. Step Functions defines names that start with
States.; your code adds its own (a Lambda exception's type, such asPaymentDeclined); service integrations report names such asLambda.ServiceException. Your own error names can't start withStates.. - When a state fails: Step Functions tries the matching retrier first; when no retrier matches or its attempts are used up, the first matching catcher sends the run to a fallback state; with neither, the whole execution fails. Declaring this in the definition keeps functions small, applies backoff consistently and shows every attempt in the execution history.
Exam facts
RetryandCatcharrays exist onTask,ParallelandMapstates (the course mentions Task and Parallel). There are none onChoice,Wait,Pass,SucceedorFail.- Retrier fields and defaults:
ErrorEquals(required, non-empty);IntervalSeconds1;MaxAttempts3 (0= never retry, maximum 99,999,999);BackoffRate2.0;MaxDelaySecondscaps the growing interval (1–31,622,400; unset = no cap);JitterStrategyFULLorNONE(defaultNONE). - Wait before retry n =
IntervalSeconds×BackoffRate^(n−1), capped byMaxDelaySeconds; withFULLjitter each wait is a random value between 0 and that number, which spreads out retries from many executions. - Evaluation: retriers and catchers are scanned top to bottom and the first match applies.
States.ALLmust be alone in itsErrorEqualsarray and sit in the last retrier or catcher. A retrier'sMaxAttemptsis shared by every error it matches while the state runs:ErrorAthenErrorBin one retrier use up the same attempts. - Catcher fields:
ErrorEqualsandNext(required); in JSONPathResultPath(default$, which replaces the state input with the error output, so use"$.error"to keep the input); in JSONataOutputandAssignwith$states.errorOutput. The error output is{"Error": "…", "Cause": "…"};Causeis often an escaped JSON string (States.StringToJsonturns it back into JSON). - Predefined error names:
| Name | Meaning |
|---|---|
States.ALL |
Wildcard for any known error, except States.DataLimitExceeded and States.Runtime |
States.TaskFailed |
Wildcard in Retry/Catch for any task error except States.Timeout |
States.Timeout |
The task exceeded TimeoutSeconds or went silent past HeartbeatSeconds; also raised when a whole execution exceeds its timeout. A timed-out nested state machine reaches the parent as States.TaskFailed. States.HeartbeatTimeout (usable in Retry and Catch) names the missed-heartbeat case |
States.Permissions |
Task lacked privileges to run the specified code |
States.DataLimitExceeded |
Input or output over the payload quota; terminal for States.ALL, so name it explicitly to catch or retry it |
States.Runtime |
Unprocessable exception (for example InputPath on a null payload); never retried or caught |
States.ExceedToleratedFailureThreshold, States.ItemReaderFailed, States.ResultWriterFailed |
Distributed Map failures |
States.Http.Socket |
HTTP Task exceeded 60 seconds |
States.QueryEvaluationError |
A JSONata expression failed (§4) |
- Lambda errors: retry transient service errors
Lambda.ServiceException,Lambda.AWSLambdaException,Lambda.SdkClientExceptionandLambda.ClientExecutionTimeoutException(AWS's example: interval 2 s, 6 attempts, backoff 2); throttling arrives asLambda.TooManyRequestsException; unhandled function errors appear asLambda.Unknown, and newer runtimes report function timeouts asSandbox.Timedout. Every retry is a state transition: it counts toward Standard billing and the 25,000-event history; redrive resets retry counters (§1).
Developer angle
- A business error raised by the function becomes the error name to match: in Python,
class PaymentDeclined(Exception)raised from the handler reaches the workflow asPaymentDeclined.
Retry transient Lambda failures with capped, jittered backoff; route the business error and everything else to different fallbacks while keeping the order data:
{
"ChargeCard": {
"Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "charge-card", "Payload.$": "$" },
"OutputPath": "$.Payload", "TimeoutSeconds": 20, "Next": "ShipOrder",
"Retry": [{ "ErrorEquals": ["Lambda.ServiceException", "Lambda.AWSLambdaException",
"Lambda.SdkClientException", "Lambda.TooManyRequestsException"],
"IntervalSeconds": 2, "MaxAttempts": 4, "BackoffRate": 2.0,
"MaxDelaySeconds": 20, "JitterStrategy": "FULL" }],
"Catch": [
{ "ErrorEquals": ["PaymentDeclined"], "ResultPath": "$.error", "Next": "NotifyCustomer" },
{ "ErrorEquals": ["States.DataLimitExceeded"], "ResultPath": null, "Next": "AlertSupport" },
{ "ErrorEquals": ["States.ALL"], "ResultPath": "$.error", "Next": "AlertSupport" }
]
}
}
- Check which handler fires without deploying (§6): save the
ChargeCardobject (without its name) ascharge-card-state.jsonand runaws stepfunctions test-state --definition file://charge-card-state.json --input '{"orderId":"8812"}' --mock '{"errorOutput": {"error": "PaymentDeclined", "cause": "card refused"}}' --inspection-level DEBUG; the response showsstatusCAUGHT_ERROR,nextStateNotifyCustomerand thecatchIndex.
Traps
- Trap: "A
States.ALLcatcher handles every failure." → Reality: it missesStates.DataLimitExceededandStates.Runtime; nameStates.DataLimitExceededexplicitly. - Trap: "Put
States.ALLfirst so nothing slips through." → Reality: it must be alone and last; otherwise the definition is invalid. - Trap: "
States.TaskFailedalso covers timeouts." → Reality: it matches everything exceptStates.Timeout. - Trap: "
MaxAttempts: 0retries forever." → Reality: zero means never retry. - Trap: "The fallback state still sees the order fields." → Reality: with the default
ResultPaththe error output replaces the input; use"ResultPath": "$.error". - Trap: "Retry with
time.sleep()inside the function." → Reality: that burns Lambda duration; declareRetrywith backoff and jitter.
Practice questions
-
A Task state has one retrier with
IntervalSeconds2,BackoffRate3.0,MaxAttempts3,MaxDelaySeconds10 andJitterStrategyNONE. The task fails on every attempt. How long does Step Functions wait before the first, second and third retries?- A. 2, 4 and 6 seconds
- B. 2, 6 and 18 seconds
- C. 3, 9 and 10 seconds
- D. 2, 6 and 10 seconds
-
A workflow passes a growing JSON document between its states. Executions now fail with
States.DataLimitExceeded, even though the failing Task state has a catcher forStates.ALL. Which TWO changes fix the problem or handle it gracefully? (Select TWO.)- A. Store the document in S3 and pass only the bucket name and object key between states.
- B. Request a Service Quotas increase for the maximum state input and output size.
- C. Add a catcher whose
ErrorEqualsnamesStates.DataLimitExceededexplicitly. - D. Move the
States.ALLcatcher to the top of theCatcharray. - E. Recreate the state machine as an Express workflow.
Answers
- D. The waits are 2 × 3⁰ = 2 s, 2 × 3¹ = 6 s and 2 × 3² = 18 s, capped at
MaxDelaySeconds10. A ignores the backoff rate; B ignores the cap; C starts from the wrong interval. - A, C. Passing an S3 pointer keeps payloads under the 256 KiB quota, and
States.DataLimitExceededis only caught when named explicitly. B fails because the payload size is a hard quota; D is invalid (States.ALLmust be last) and still wouldn't catch it; E fails because Express has the same 256 KiB limit.
4. Step Functions input and output processing
Concept
- Every state receives JSON input and hands JSON output to the next state. Step Functions offers two query languages for selecting and reshaping that data:
- JSONPath (the default): up to five fields, applied in this order:
InputPath(pick part of the input) →Parameters(build the request for the task) → the task runs →ResultSelector(pick from the raw result) →ResultPath(decide where the result goes in the original input) →OutputPath(filter what moves on). - JSONata: two fields,
Arguments(the request) andOutput(the state's output), filled with{% … %}expressions that can read the reserved$statesvariable.
- JSONPath (the default): up to five fields, applied in this order:
- Variables:
Assignstores values in one state that any later state reads as$name, so data no longer has to be carried through states that don't need it. - ⚠️ Changed: JSONata and variables have been available since 22 November 2024 (JSONPath stays the default). Older material may say JSONPath and its five fields are the only way to shape data.
- The context object (
$$in JSONPath,$states.contextin JSONata) exposes run metadata:$$.Execution.Id,$$.Execution.StartTime,$$.State.Name,$$.Task.Token,$$.Map.Item.Value.
Exam facts
ResultPath(Pass, Task, Parallel and Map states; reference paths only): default$→ the result replaces the whole input;"$.payment"→ the result is added underpayment(or replaces that node if it exists);null→ the result is discarded and the original input passes through unchanged. In a catcher,"ResultPath": "$.error"keeps the original input and adds{"Error", "Cause"}undererror(§3).- Payload templates: in
Parameters,ResultSelectorandItemSelector, a key ending in.$takes a path ("orderId.$": "$.id"), a context path ("token.$": "$$.Task.Token") or an intrinsic function ("msg.$": "States.Format('Order {}', $.id)"). Without the suffix the value is sent as a literal string. - Intrinsic functions (JSONPath states only):
States.Format,States.StringToJson,States.JsonToString,States.Array,States.ArrayPartition,States.ArrayRange,States.Hash,States.MathRandom,States.UUID, among others. The JSONata equivalents are$partition,$range,$hash,$random,$uuidand$parse($evalis not available). - JSONata rules:
- Opt in with
"QueryLanguage": "JSONata"at the top level (the default isJSONPath) or per state, which allows a gradual migration. - A JSONata state can't use
InputPath,Parameters,ResultSelector,ResultPathorOutputPath, and keys no longer end in.$;…Pathfields disappear ("TimeoutSeconds": "{% $timeout %}",Itemsinstead ofItemsPath); Choice rules useCondition. $states.input(original state input),$states.result(the task's raw result),$states.errorOutput(only insideCatch),$states.context. A failing expression (type error, undefined field, more than 1 second, memory or recursion limit) raisesStates.QueryEvaluationError, whichRetryandCatchcan handle.
- Opt in with
- Variables:
Assignworks on Pass, Task, Map, Parallel, Choice (also per rule) and Wait states and inside catchers, not on Succeed or Fail; all its expressions use the values from state entry, and new values are visible from the next state.- Map and Parallel iterations read outer variables but keep their own; an inner scope can't reuse an outer variable's name; Distributed Map can't read outer variables.
- Limits: 256 KiB per variable, 256 KiB per
Assignblock, 10 MiB per execution. Variables work in JSONPath states too, except inResultPath. In JSONata,AssignandOutputare evaluated side by side from the same inputs, so a transformation done inAssignis not visible inOutput.
- Payload limit: ⚠️ Changed: 256 KiB of UTF-8 data for any task, state or execution input or output, Standard and Express alike, a hard quota (unchanged since 3 September 2020). Older material may say 32,768 characters. Exceeding it raises
States.DataLimitExceeded; keep large data in S3 and pass the bucket and key.
Developer angle
JSONPath state: read a DynamoDB item, keep two attributes and add them to the input under customer. With input {"customerId": "c-17", "orderId": "8812"} the output is that input plus "customer": {"email": "…", "tier": "gold"}:
{
"GetCustomer": {
"Type": "Task", "Resource": "arn:aws:states:::dynamodb:getItem", "End": true,
"Parameters": { "TableName": "Customers", "Key": { "pk": { "S.$": "$.customerId" } } },
"ResultSelector": { "email.$": "$.Item.email.S", "tier.$": "$.Item.tier.S" },
"ResultPath": "$.customer"
}
}
The same state in JSONata, also saving the tier in a variable for later states:
{
"GetCustomer": {
"Type": "Task", "QueryLanguage": "JSONata", "Resource": "arn:aws:states:::dynamodb:getItem", "End": true,
"Arguments": { "TableName": "Customers", "Key": { "pk": { "S": "{% $states.input.customerId %}" } } },
"Assign": { "tier": "{% $states.result.Item.tier.S %}" },
"Output": "{% $merge([$states.input, {'customer': {'email': $states.result.Item.email.S}}]) %}"
}
}
- Run the state object alone through
aws stepfunctions test-state … --inspection-level DEBUG(§3, §6) to seeafterInputPath,afterParameters,afterResultSelectorandafterResultPathbefore deploying.
Traps
- Trap: "After the task the next state still sees
orderId." → Reality: the defaultResultPath$replaced the input with the task result; use"ResultPath": "$.something"ornull. - Trap: "Use
OutputPathto add the result to the input." → Reality:OutputPathonly filters;ResultPathdecides where the result goes. - Trap: "
"orderId": "$.id"passes the order ID." → Reality: without.$the literal string$.idis sent. - Trap: "Return the full 2 MB report from Lambda to the next state." → Reality: 256 KiB hard limit; write the report to S3 and return its key.
- Trap: "A variable assigned in a state can be read in that same state." → Reality: expressions use values from state entry; the new value appears in the next state.
Practice questions
-
A Task state calls a Lambda function through the optimized integration. The next state needs all of the original input fields and also the function's
statusvalue under apaymentkey. Which configuration meets this requirement?- A.
"ResultSelector": {"status.$": "$.Payload.status"}and"ResultPath": "$.payment" - B.
"ResultSelector": {"status.$": "$.Payload.status"}and"OutputPath": "$.payment" - C.
"InputPath": "$.Payload"and"ResultPath": nullto keep the original input - D.
"ResultPath": "$"with"OutputPath": "$.Payload.status"so only the status moves on
- A.
-
A team migrates a JSONPath state machine to JSONata one state at a time to limit risk. Which TWO statements describe how each converted state must be written? (Select TWO.)
- A. Each converted state keeps its
ResultPathfield to place the task result in the input. - B. Each converted state appends
.$to every key whose value is an expression. - C. Each converted state uses
Argumentsinstead ofParametersto build the service request. - D. The whole state machine must switch to JSONata in a single deployment.
- E. Each converted state sets
"QueryLanguage": "JSONata"and wraps expressions in{% %}.
- A. Each converted state keeps its
Answers
- A.
ResultSelectorpicksstatusfrom the wrapped Lambda result andResultPathadds it underpaymentwithout touching the input. B replaces the input with the selected result and then filters on apaymentkey that doesn't exist; C discards the result; D throws the input away and keeps only the status value. - C, E. JSONata states build requests with
Arguments, and the language can be set per state with expressions inside{% %}. A and B describe JSONPath-only features that are invalid in JSONata states; D is wrong becauseQueryLanguagecan be set state by state.
5. Step Functions callbacks with task tokens and activities
Concept
- Some work happens outside Step Functions: a person approves an order, a partner system replies hours later, a legacy job runs on a server you own. Two mechanisms let that work report back to a paused execution:
- Callback (push): a Task state with the
.waitForTaskTokensuffix sends a request that includes a unique task token (in an SQS message, an SNS notification, a Lambda payload, an ECS task's environment, an EventBridge event, and so on) and then waits. Whatever finishes the work callsSendTaskSuccessorSendTaskFailurewith that token. - Activity (pull): you register an activity (
CreateActivity) and use its ARN as a Task'sResource. Workers you host anywhere (EC2, ECS, on premises, even Lambda) long-pollGetActivityTask, receive the input and a token, do the work and report with the sameSendTask*calls.
- Callback (push): a Task state with the
- In both cases the token identifies the waiting task, and heartbeats (
SendTaskHeartbeat) prove that a slow worker is still alive.
Exam facts
- Callback syntax:
"Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken"with"TaskToken.$": "$$.Task.Token"somewhere inParameters(JSONata:"{% $states.context.Task.Token %}"). With an SDK integration the API must have a parameter that can carry the token. A.synctask can also be finished early by passing it a token and callingSendTaskSuccessorSendTaskFailure. - Who supports callbacks: Lambda, SQS, SNS, ECS/Fargate, EKS, EventBridge, API Gateway, Bedrock, nested Step Functions and SDK integrations, in Standard workflows only. Not DynamoDB, Athena, Batch, Glue or CodeBuild.
- How long it waits: until the execution limit (1 year for Standard). Without
TimeoutSecondsorHeartbeatSeconds, a lost token leaves the execution stuck that long. - Heartbeats:
HeartbeatSeconds(lower thanTimeoutSeconds) requires aSendTaskHeartbeatcall within every interval; a missed heartbeat fails the task withStates.Timeout(Step Functions also definesStates.HeartbeatTimeout: handle both). - Expired tokens: after a timeout the token is invalidated (a retried state gets a new one);
SendTask*calls with the old token fail withTaskTimedOut("expired or already closed"). - API limits:
taskToken1–2,048 characters;SendTaskSuccessoutputup to 262,144 bytes (256 KiB); errorsInvalidToken,InvalidOutput,TaskDoesNotExist,TaskTimedOut; tokens must be sent back by principals in the same AWS account. - Activities:
Resourceisarn:aws:states:<region>:<account>:activity:<name>; Standard workflows only; activities aren't versioned (create a new activity for a breaking change).GetActivityTaskholds the connection for up to 60 seconds and returns a nulltaskTokenwhen no work arrived, so the worker's client socket or read timeout must be at least 65 seconds (boto3's default read timeout is 60 seconds).- Optional
workerNameappears in the execution history; too many concurrent pollers →ActivityWorkerLimitExceeded. For production AWS suggests at least 100 open polls per activity ARN, with polling threads separate from work threads. The task'sTimeoutSecondsstarts when a worker receives the token (ActivityStartedevent); with a long timeout and regular heartbeats, an activity task can stay open for as long as a year.
- IAM: workers need
states:GetActivityTaskon the activity plusstates:SendTaskSuccess,states:SendTaskFailureandstates:SendTaskHeartbeat; the component that answers a callback needs theSendTask*actions. - Choosing: an AWS job with a
.syncintegration →.sync; a human, a partner or a queue-based system → callback; workers you host that should pull work → activity.
Developer angle
Wait up to three days for a manager's decision delivered through an SQS queue:
{
"StartAt": "RequestApproval",
"States": {
"RequestApproval": { "Type": "Task", "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
"Parameters": { "QueueUrl": "https://sqs.eu-west-1.amazonaws.com/111122223333/approvals",
"MessageBody": { "orderId.$": "$.orderId", "taskToken.$": "$$.Task.Token" } },
"TimeoutSeconds": 259200, "ResultPath": "$.approval", "Next": "Approved",
"Catch": [{ "ErrorEquals": ["States.Timeout", "Rejected"], "ResultPath": "$.error", "Next": "Expired" }] },
"Approved": { "Type": "Succeed" },
"Expired": { "Type": "Fail", "Error": "ApprovalNotGranted", "Cause": "Approval timed out or was rejected" }
}
}
- The approval service answers with
sfn.send_task_success(taskToken=token, output=json.dumps(result)), orsfn.send_task_failure(taskToken=token, error="Rejected", cause="Manager declined"), which theRejectedcatcher above routes toExpired. - An activity worker in boto3: create the client with
Config(read_timeout=70)(65 seconds or more, 5 seconds above the 60-second long poll), loop onget_activity_task(activityArn=…, workerName=…), skip responses without ataskToken, callsend_task_heartbeatduring long work, and finish withsend_task_successor, in anexceptblock,send_task_failure.
Traps
- Trap: "Send the request; Step Functions knows which task to resume." → Reality: the token must travel with the request and come back in
SendTaskSuccess/SendTaskFailure. - Trap: "Use a callback in the Express workflow." → Reality:
.waitForTaskTokenand activities are Standard-only. - Trap: "No timeout needed; the approver always answers." → Reality: without
TimeoutSecondsthe execution can hang for up to a year. - Trap: "The partner account calls
SendTaskSuccesswith the token." → Reality: tokens work only from principals in the same account; relay the answer through your own account. - Trap: "The activity worker's default SDK timeout is fine." → Reality:
GetActivityTaskcan hold 60 seconds; set the client timeout to 65 seconds or more.
Practice questions
-
An order workflow must pause until a manager clicks an approval link in an email, which can take up to two days. Which solution meets this requirement with the LEAST operational overhead?
- A. A Wait state of 10 minutes followed by a Choice state that reads an approval flag from DynamoDB, repeated in a loop until the flag is set.
- B. A
.waitForTaskTokentask whose emailed link carries the token, an approval endpoint callingSendTaskSuccess, and a 2-day timeout. - C. An activity whose worker on an EC2 instance reads the manager's mailbox and calls
SendTaskSuccesswhen a reply arrives. - D. An Express workflow started with
StartSyncExecutionthat waits for the approval.
-
An activity worker runs on premises. Long jobs sometimes fail with a timeout while the job is still running, and idle pollers log client read timeouts. Which TWO changes fix both problems? (Select TWO.)
- A. Call
SendTaskHeartbeatregularly, withinHeartbeatSeconds, while a job runs. - B. Recreate the state machine as an Express workflow.
- C. Append
.syncto the activity ARN in theResourcefield. - D. Set the worker's client read timeout to at least 65 seconds.
- E. Lower the Task state's
TimeoutSecondsto 60 seconds.
- A. Call
Answers
- B. The callback pattern pauses the execution at no cost until the token comes back, and the timeout ends abandoned requests. A builds a polling loop that adds state transitions; C needs a worker you host and maintain; D fails because Express workflows stop at 5 minutes and don't support callbacks.
- A, D. Heartbeats keep a long job's task alive, and a read timeout of at least 65 seconds, 5 seconds above the 60-second long poll, stops idle polls from erroring. B removes activity support entirely; C is not valid for activities; E makes long jobs time out even sooner.
6. Step Functions Standard vs Express workflows
Concept
- The workflow type is chosen when the state machine is created and can't be changed later.
- Standard: durable, auditable workflows that run for up to a year; each step runs exactly once unless you add
Retry; the full history is available through the API and console; priced per state transition. Fits orders, payments, approvals, anything non-idempotent or long-running. - Express: high-volume, short workflows (up to 5 minutes) such as IoT ingestion, stream processing or mobile back ends; priced by the number of executions, their duration and memory; history only through CloudWatch Logs. Asynchronous Express runs at least once, synchronous Express at most once, so steps must be idempotent.
- They combine well: a Standard parent handles the long or non-idempotent steps and starts nested Express children for high-volume idempotent work, which cuts cost.
Exam facts
| Standard | Express | |
|---|---|---|
| Maximum duration | 1 year | 5 minutes |
| Execution semantics | Exactly-once (unless you add Retry) |
Asynchronous: at-least-once · synchronous: at-most-once |
| How to start | StartExecution (idempotent by name) |
Async: StartExecution (not idempotent) · sync: StartSyncExecution (API Gateway, Lambda, SDK) |
| Execution history | API and console for 90 days after close (can be reduced to 30 days by a support request); CloudWatch Logs optional | CloudWatch Logs only: enable logging |
| History size | 25,000 events | Unlimited |
| Pricing | Per state transition (retries count) | Per execution, duration and memory |
| Integration patterns | Request Response, .sync, .waitForTaskToken |
Request Response only |
| Activities, Distributed Map parent, redrive | Supported | Not supported |
StartExecution throttle (volatile, soft) |
Bucket 1,300, refill 300 per second in us-east-1, us-west-2 and eu-west-1; 800 and 150 elsewhere | Bucket 6,000, refill 6,000 per second |
| State transitions (volatile, soft) | 5,000 per second in the three Regions above, 800 elsewhere | No limit |
- ⚠️ Changed: AWS documents start and transition rates as the soft quotas above. Older material may say "over 2,000 executions per second" for Standard and "over 100,000 per second" for Express. New accounts start with reduced state-transition quotas that AWS raises automatically.
- Synchronous Express:
StartSyncExecutionreturnsstatus(SUCCEEDED,FAILED,TIMED_OUT) andoutput; from the console the request expires after 60 seconds, so use the SDK or CLI for runs up to 5 minutes. These calls don't use the account'sStartExecutioncapacity, but sudden surges can be throttled. - An execution that exceeds its maximum duration fails with
States.Timeoutand emits theExecutionsTimedOutmetric; throttled state transitions show up asExecutionThrottled. - Logging: name log groups with the
/aws/vendedlogs/states/prefix to avoid the CloudWatch Logs resource policy size limit (5,120 characters). Express logging costs extra. - Moving to Express is safe only when: the run always ends within 5 minutes, every step tolerates being run more than once, and no state uses
.syncor.waitForTaskToken. - Testing: the
TestStateAPI runs a single state (or one state of a full definition) with inspection levelsINFO(default: output or error),DEBUG(each data-processing step) andTRACE(raw HTTP request and response, HTTP Tasks only; secrets needrevealSecretsplusstates:RevealSecrets). Since November 2025 it can mock service results and errors (role optional when mocking) and test Map, Parallel, activity,.syncand.waitForTaskTokenstates, which need a mock. Callers needstates:TestStateand, without a mock,iam:PassRole. ⚠️ Changed: Step Functions Local is unsupported and lacks feature parity; test withTestState. Older material may say to test locally with Step Functions Local. - Releases: publish versions and shift an alias between them (§1); with SAM,
AWS::Serverless::StateMachinesetsType(STANDARDorEXPRESS) and can useAutoPublishAliaswithDeploymentPreference(seenotes/sam-guide.md§3). X-Ray tracing and EventBridge execution-status events: seenotes/monitoring-guide.md.
Developer angle
Create an Express workflow with full logging and call it synchronously:
aws stepfunctions create-state-machine --name price-quote --type EXPRESS \
--definition file://quote.asl.json \
--role-arn arn:aws:iam::111122223333:role/QuoteStateMachineRole \
--logging-configuration '{"level":"ALL","includeExecutionData":true,"destinations":[{"cloudWatchLogsLogGroup":{"logGroupArn":"arn:aws:logs:eu-west-1:111122223333:log-group:/aws/vendedlogs/states/price-quote:*"}}]}'
aws stepfunctions start-sync-execution \
--state-machine-arn arn:aws:states:eu-west-1:111122223333:stateMachine:price-quote \
--input '{"sku":"A-100","qty":3}' --query '{status:status,output:output}'
StartSyncExecutionanswers HTTP 200 even when the workflow fails: checkstatusanderror/causein the response.- API Gateway can call
StartSyncExecutionthrough an AWS service integration and return the workflow output to the client (seenotes/api-gateway-guide.md§3). - To keep a Standard parent cheap, let it start a nested Express child (
arn:aws:states:::states:startExecution) for the high-volume, idempotent part of the work; when the parent must wait for a child and use its result,startExecution.sync:2returns the child output as JSON (§2).
Traps
- Trap: "Use Express for payment processing to save money." → Reality: at-least-once execution can charge twice; non-idempotent steps belong in Standard.
- Trap: "Check the Express execution history with
GetExecutionHistory." → Reality: Express history exists only in CloudWatch Logs, and only if logging is on. - Trap: "Call
StartSyncExecutionon the Standard workflow." → Reality: synchronous starts exist only for Express workflows; a Standard one returnsStateMachineTypeNotSupported. - Trap: "An Express workflow can run the 20-minute report." → Reality: Express executions stop at 5 minutes with
States.Timeout. - Trap: "Edit the type field of the existing state machine." → Reality: the type is immutable; create a new state machine and move callers.
Practice questions
-
An IoT platform receives 4,000 sensor events per second. Each event is transformed in under 2 seconds and written to DynamoDB with an idempotent put by key. Producers don't wait for a result. Which workflow choice is the MOST cost-effective?
- A. A Standard workflow execution per event, started by an EventBridge rule.
- B. A Standard workflow per minute with an Inline Map over that minute's events.
- C. A synchronous Express workflow started with
StartSyncExecutionfor each event. - D. An asynchronous Express workflow started for each event.
-
A mobile back end must combine the results of three microservice calls and return them to the caller within 3 seconds, with no custom orchestration code. Which TWO components meet these requirements? (Select TWO.)
- A. A Standard workflow started with
StartExecution, with the client pollingDescribeExecution. - B. An Express workflow that calls the three microservices.
- C. An API Gateway integration that calls
StartSyncExecutionand returns the workflow output. - D. An activity whose worker calls the three microservices.
- E. A
.waitForTaskTokenTask state for each microservice call.
- A. A Standard workflow started with
Answers
- D. Asynchronous Express suits high-volume, short, idempotent work and is billed per execution and duration instead of per state transition. A pays per state transition and exceeds the default Standard start rate; B packs too many items into one execution's history and also pays per transition; C makes callers wait for results they don't need.
- B, C. A synchronous Express workflow returns the combined result in the same request, and API Gateway can start it with
StartSyncExecution. A adds polling and latency; D adds a worker you must host; E is unsupported in Express workflows and waits for callbacks no service sends.
7. AppSync GraphQL APIs and resolvers
Concept
- GraphQL exposes one endpoint described by a typed schema (object types plus the
Query,MutationandSubscriptionoperations). A client asks for exactly the fields it needs, possibly drawn from several back ends, in one round trip, and gets JSON shaped like its query. - AWS AppSync is the managed GraphQL service: you define the schema, attach resolvers to fields, and connect resolvers to data sources. AppSync parses and validates requests, authorizes them (§8), calls the data sources, caches results and keeps WebSocket connections for real-time clients.
- Resolvers: a unit resolver is one request handler and one response handler against one data source; a pipeline resolver runs a before step, then an ordered list of functions (each with its own data source), then an after step, sharing data through
ctx.stash. Runtimes:APPSYNC_JS(JavaScript, runtime version1.0.0) or the older VTL mapping templates. - Real time: subscriptions push changes to connected clients over WebSockets; they fire when a mutation that runs through AppSync completes.
- AppSync Events is a separate API type: serverless WebSocket publish/subscribe channels grouped in namespaces, published to over HTTP or WebSocket, with optional handlers that run on publish and on subscribe, for broadcasts that need no GraphQL schema.
Exam facts
- Data sources: DynamoDB; Lambda (any custom source or logic); OpenSearch Service; HTTP endpoints; EventBridge (
PutEvents); relational databases (Aurora MySQL or PostgreSQL clusters through the RDS Data API);NONE(a local resolver that only transforms data or triggers subscriptions). AppSync reaches them with an IAM role that trustsappsync.amazonaws.com; scope it to the one table, function or domain. - Lambda data source: the request names an
operation:Invoke, orBatchInvoke, which sends the resolutions of one field for many parent objects in one call and expects a list back in the same order (the fix for the N+1 problem).invocationTypeisRequestResponse(default) orEvent(asynchronous; the field resolves to null, and caching should stay off). - The
ctxobject:ctx.args(field arguments),ctx.identity(caller details: Cognitosub,usernameandclaims; IAM account and ARN; a Lambda authorizer'sresolverContext),ctx.source(the parent object),ctx.result,ctx.error,ctx.stash,ctx.prev.result(pipeline functions). - Endpoints:
https://<api-id>.appsync-api.<region>.amazonaws.com/graphqlfor queries and mutations;wss://<api-id>.appsync-realtime-api.<region>.amazonaws.com/graphqlfor subscriptions (custom domains: §8). - Subscriptions:
- Declared with
@aws_subscribe(mutations: ["addPost", …]); the field must return the mutation's type; arguments filter events (an argument set tonullis a filter, not the same as omitting it). - Clients receive only fields that are in both the mutation's and the subscription's selection sets; a field missing from the mutation's selection arrives as
null. - A change written directly to the database, outside an AppSync mutation, notifies nobody.
- ⚠️ Changed: pure WebSockets are the only subscription protocol since 1 January 2022, with payloads up to 240 KB. Older material may say MQTT over WebSockets is also available.
- Declared with
- Server-side caching:
- Behaviours: none; full request caching (every resolver cached, keyed on
context.argumentsandcontext.identity); per-resolver caching (opt in per resolver; TTL required; keys chosen fromcontext.arguments,context.source,context.identityor their string fields); operation-level caching (whole query responses up to 15 MB). - TTL up to 3,600 seconds; new caches are always encrypted in transit and at rest; clear everything with
FlushApiCache, or evict one entry from a resolver withextensions.evictFromApiCache.
- Behaviours: none; full request caching (every resolver cached, keyed on
- Responses are gzip-compressed when the client sends
Accept-Encoding: gzip(for payloads between 1,000 and 10,000,000 bytes). - Offline access and sync for mobile apps came from Amplify DataStore (Gen 1) on top of AppSync (§9). Cognito Sync is closed to new customers, and AppSync is its replacement for cross-device data (see
notes/advanced-identity-guide.md§1). - AppSync or API Gateway: GraphQL, many sources per request, or managed real-time subscriptions → AppSync; REST, HTTP or WebSocket APIs with usage plans, API keys per customer and stage deployments → API Gateway (see
notes/api-gateway-guide.md).
Developer angle
Schema with a query, a mutation and a filtered subscription:
type Post { id: ID! title: String! author: String ups: Int }
type Query { getPost(id: ID!): Post }
type Mutation { addPost(id: ID!, title: String!, author: String): Post }
type Subscription {
onAddPost(author: String): Post @aws_subscribe(mutations: ["addPost"])
}
JavaScript unit resolver for Query.getPost on a DynamoDB data source:
import { util } from '@aws-appsync/utils';
import { get } from '@aws-appsync/utils/dynamodb';
export function request(ctx) {
return get({ key: { id: ctx.args.id } });
}
export function response(ctx) {
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
return ctx.result;
}
- CLI:
aws appsync create-data-source --api-id <id> --name PostsTable --type AMAZON_DYNAMODB --dynamodb-config tableName=Posts,awsRegion=eu-west-1 --service-role-arn <role>, thencreate-resolver --type-name Query --field-name getPost --data-source-name PostsTable --runtime name=APPSYNC_JS,runtimeVersion=1.0.0 --code file://getPost.js;flush-api-cache --api-id <id>empties the cache after a bulk update. - A back end that must notify subscribers calls an AppSync mutation (often one attached to a
NONEdata source and protected with@aws_iam) instead of writing straight to the table. - In a pipeline, the first function can check permissions and stash data, and the next function writes; any function can stop the pipeline with
util.error.
Traps
- Trap: "A Lambda function updates DynamoDB directly and subscribers are notified." → Reality: subscriptions fire only for AppSync mutations; publish through a mutation.
- Trap: "A list query that calls Lambda once per item is fine." → Reality: use
BatchInvokeor per-resolver caching to avoid N+1 invocations. - Trap: "Cache a per-user field keyed only on
context.arguments." → Reality: one user's cached data would be served to others; includecontext.identityin the key. - Trap: "Build GraphQL subscriptions on an API Gateway WebSocket API." → Reality: AppSync manages GraphQL subscriptions and connections for you.
- Trap: "Use the
Eventinvocation type and cache the field." → Reality: cache hits never reach Lambda; disable caching for asynchronous resolvers.
Practice questions
-
A collaborative to-do app stores items in DynamoDB. When one user edits an item, the other users' web and mobile clients must update in near real time. Which solution requires the LEAST custom infrastructure?
- A. An API Gateway REST API that clients poll every 2 seconds.
- B. DynamoDB Streams with a Lambda function that sends every change to the clients through SNS mobile push notifications.
- C. An AppSync API whose mutations write to DynamoDB and whose subscription uses
@aws_subscribeon those mutations. - D. An API Gateway WebSocket API with a Lambda function that stores connection IDs in DynamoDB and pushes each change to them.
-
An AppSync query returns 100 orders, and the
Order.customerfield is resolved by a Lambda function that is invoked once per order, which causes high latency and cost. Customer records change rarely. Which TWO changes reduce the number of Lambda invocations? (Select TWO.)- A. Change the API's default authorization mode to
API_KEY. - B. Increase the memory configured for the Lambda function.
- C. Move subscription clients to the pure WebSockets protocol.
- D. Use the
BatchInvokeoperation in theOrder.customerresolver. - E. Enable per-resolver caching on
Order.customerwith a TTL and a key based on the customer ID.
- A. Change the API's default authorization mode to
Answers
- C. AppSync subscriptions push each mutation's result to connected clients over managed WebSockets. A adds polling load and delay; B and D need custom fan-out code and connection tracking.
- D, E. Batching resolves many orders' customers in one invocation, and caching serves repeated customers without calling Lambda. A, B and C don't change how often the resolver invokes the function.
8. AppSync authorization modes
Concept
- Every request must pass the API's default authorization mode or one of its additional modes; schema directives decide which modes may reach which types and fields.
- ⚠️ Changed: AppSync offers five authorization types:
API_KEY,AWS_LAMBDA,AWS_IAM,OPENID_CONNECTandAMAZON_COGNITO_USER_POOLS. Older material may say four (withoutAWS_LAMBDA). - Authorization decides who may call an operation; row-level rules ("only the author may edit") live in resolvers that compare
ctx.identitywith the data, for example in a DynamoDB condition. - ⚠️ Changed: AppSync serves custom domain names itself. Older material may say you must put your own CloudFront distribution in front of AppSync.
Exam facts
API_KEY: sent in thex-api-keyheader; default expiry 7 days after creation, configurable from 1 to 365 days (expiresin epoch seconds, rounded down to the hour) and extendable withUpdateApiKeyby up to 365 days from the update; values outside that range fail withApiKeyValidityOutOfBoundsException. Meant for development, public read-only data and throttling unauthenticated access: it identifies an application, not a user.AWS_LAMBDA: the client'sAuthorizationheader goes to your function; one Lambda authorizer per API. The function returnsisAuthorized(required) and optionallyresolverContext(key-value pairs readable asctx.identity.resolverContext, up to 5 MB),deniedFields(fields forced tonull, asType.fieldor a full field ARN) andttlOverride(seconds to cache the decision;0disables caching). Caching is off by default, is enabled at API level or throughttlOverride, and applies only to responses smaller than 1,048,576 bytes. The authorizer has a 10-second timeout (aim for under 1 s); an optional regular expression rejects malformed tokens before the call; an exception or an empty response means unauthorized; AppSync needslambda:InvokeFunctionpermission forappsync.amazonaws.com; authorizers can't be shared across accounts.AWS_IAM: SigV4-signed requests; policies grantappsync:GraphQLonarn:aws:appsync:<region>:<account>:apis/<api-id>/types/<Type>/fields/<field>(orapis/<api-id>/*). Best for back-end services, Lambda functions and Cognito identity pool users, including guests.OPENID_CONNECT: an issuer URL (AppSync reads/.well-known/openid-configurationand its JWKS), an optionalclientIdregular expression matched againstaudorazp(set it for shared issuers), and optionaliatTTLandauthTTLlimits.AMAZON_COGNITO_USER_POOLS: the user pool JWT in theAuthorizationheader; Cognito groups usable in directives;defaultEffectALLOWorDENYwhen this is the only mode; pools from other accounts are allowed.- Several modes on one API: the default mode is
authenticationTypeand the extra ones areadditionalAuthenticationProviders.API_KEY,AWS_LAMBDAandAWS_IAMcan each appear only once across both lists; several Cognito user pools or OIDC providers are allowed, but not the same one as default and additional. - Directives:
@aws_api_key,@aws_iam,@aws_oidc,@aws_cognito_user_pools(cognito_groups: ["Admins"])and@aws_lambdaon types and fields. A field without directives accepts only the default mode. A directive on a query field doesn't cover the type it returns: annotate the type (or its fields) as well.@aws_auth(cognito_groups: …)works only when Cognito user pools is the sole mode; with additional modes use@aws_cognito_user_pools. - A failed authorization (an expired API key, a bad token,
isAuthorized: false) returns anUnauthorizedExceptionerror. - Subscriptions are authorized when the client connects and subscribes (IAM, Lambda, Cognito user pools or identity pools); resolvers on subscription fields and enhanced filtering add per-caller rules.
- Custom domains: provide a public or imported ACM certificate in us-east-1 that covers the name (wildcards such as
*.example.comwork); AppSync creates a CloudFront distribution that uses SNI, and you point a CNAME at it. Endpoints becomehttps://api.example.com/graphqlandwss://api.example.com/graphql/realtime; only TLS 1.2 and 1.3 are accepted; whoever configures it needscloudfront:UpdateDistributionandacm:DescribeCertificate. - Authorization configuration changes propagate gradually; while they do, either the old or the new setting may answer.
Developer angle
Default mode Cognito user pools, API_KEY added for the public catalog, admin-only cost data and price changes:
type Product @aws_api_key @aws_cognito_user_pools {
id: ID!
name: String!
price: Float!
costPrice: Float @aws_cognito_user_pools(cognito_groups: ["Admins"])
}
type Query { listProducts: [Product] @aws_api_key @aws_cognito_user_pools }
type Mutation {
updatePrice(id: ID!, price: Float!): Product @aws_cognito_user_pools(cognito_groups: ["Admins"])
}
- Enable the extra mode with
aws appsync update-graphql-api … --authentication-type AMAZON_COGNITO_USER_POOLS --user-pool-config userPoolId=…,awsRegion=eu-west-1,defaultEffect=ALLOW --additional-authentication-providers '[{"authenticationType":"API_KEY"}]', thenaws appsync create-api-key --api-id <id> --expires $(( $(date +%s) + 30*24*3600 ))for a 30-day key instead of the 7-day default. - Before AppSync can call a Lambda authorizer:
aws lambda add-permission --function-name appsync-authorizer --statement-id appsync --principal appsync.amazonaws.com --action lambda:InvokeFunction --source-arn <API ARN>.
A Lambda authorizer for custom tenant tokens:
def lookup_tenant(token): # placeholder for a real lookup, e.g. GetItem on a token table
return {"id": "t-42", "plan": "free"} if token.startswith("tk_") else None
def handler(event, context):
tenant = lookup_tenant(event.get("authorizationToken", ""))
if tenant is None:
return {"isAuthorized": False}
denied = [] if tenant["plan"] == "pro" else ["Product.costPrice"]
return {"isAuthorized": True, "resolverContext": {"tenantId": tenant["id"]},
"deniedFields": denied, "ttlOverride": 300}
- Least privilege for a back-end role that may call one mutation only: allow
appsync:GraphQLonarn:aws:appsync:eu-west-1:111122223333:apis/abcdefghijklmnopqrstuvwxyz/types/Mutation/fields/publishStockLeveland sign requests with SigV4.
Traps
- Trap: "Protect user data with an API key." → Reality: an API key carries no user identity and is usually embedded in the client; use Cognito user pools, OIDC, IAM or Lambda.
- Trap: "Adding
@aws_api_keyto the query is enough." → Reality: the returned type needs the directive too, or its fields fail authorization. - Trap: "Configure
API_KEYas the default mode and again as an additional mode." → Reality:API_KEY,AWS_LAMBDAandAWS_IAMcan be used only once per API. - Trap: "Use
@aws_authwhile an additional mode is enabled." → Reality: use@aws_cognito_user_poolswith the same arguments. - Trap: "Return
principalIdand apolicyDocumentfrom the AppSync Lambda authorizer." → Reality: that is API Gateway's format; AppSync expectsisAuthorized. - Trap: "Requests started failing with
UnauthorizedExceptiona week after launch, so the schema broke." → Reality: API keys expire after 7 days by default; create keys with a longerexpiresor rotate them. - Trap: "Request the custom-domain certificate in the API's own Region." → Reality: AppSync custom domains need the ACM certificate in us-east-1.
Practice questions
-
A retail AppSync API uses Cognito user pools as its default authorization mode. The product catalog query must also be readable by anonymous visitors of the public website, while price changes stay limited to the
Adminsgroup. Which solution meets these requirements with the LEAST effort?- A. Add
API_KEYas an additional mode and put@aws_api_keyplus@aws_cognito_user_poolson the catalog query andProduct. - B. Create a second AppSync API that uses only
API_KEYauthorization, and copy the schema and resolvers to it for the website. - C. Change the default mode to
AWS_IAMand give the anonymous website visitors IAM user access keys embedded in the page. - D. Put an API Gateway REST API with a Lambda proxy integration in front of the catalog query and leave it unauthenticated.
- A. Add
-
A SaaS team protects an AppSync API with a Lambda authorizer that validates custom tokens. Resolvers must read the caller's tenant ID, and tenants on the free plan must never receive the
Invoice.marginfield. Which TWO values should the authorizer return? (Select TWO.)- A.
deniedFieldsthat listsInvoice.marginfor free-plan tenants. - B. A
principalIdand an IAMpolicyDocumentthat denies the field. - C. A
ttlOverrideof3600so that every decision is cached for an hour. - D. An
@aws_authdirective inside the response. - E.
resolverContextthat contains the tenant ID, read in resolvers asctx.identity.resolverContext.
- A.
Answers
- A. An additional
API_KEYmode plus directives on both the query and its return type opens the catalog to anonymous callers, while the default Cognito mode and the group directive still protect price changes. B duplicates the API; C hands out long-term credentials, which is insecure; D adds a second API layer to maintain. - A, E.
deniedFieldsnulls the field for free-plan callers andresolverContextpasses the tenant ID to resolvers. B is the API Gateway authorizer format; C only changes how long decisions are cached and doesn't meet either requirement; D is a schema directive, not an authorizer response field.
9. Amplify hosting and end to end tests
Concept
- AWS Amplify is a toolkit for web and mobile front ends: Amplify Hosting (Git-based CI/CD and hosting for single-page apps, server-side-rendered apps such as Next.js and static sites, served from the AWS CDN), a backend definition layer (Cognito for auth, AppSync and DynamoDB for data, S3 for storage, Lambda functions), front-end libraries and UI components.
- Hosting: connect a repository branch (GitHub, Bitbucket, GitLab or CodeCommit); every push runs the build described in
amplify.ymland deploys atomically, so visitors never see a half-uploaded site. Other features: an environment per feature branch, a preview URL per pull request, password-protected branches, custom domains, redirects and rewrites, custom headers and monitoring. - Testing: an optional test phase runs end-to-end tests during the build, between build and deploy, so a regression stops the release instead of reaching users.
- ⚠️ Changed: Amplify Gen 2 (generally available since 6 May 2024) is the recommended way to build new apps: the backend is TypeScript code in an
amplify/folder, built on the AWS CDK. Older material may say to build with the Gen 1 Amplify CLI wizard, Amplify Studio and DataStore, as the course does.
Exam facts
- Gen 2: resources are declared with functions such as
defineAuth,defineDataanddefineStoragefrom@aws-amplify/backendand combined inamplify/backend.ts.npx ampx sandboxdeploys a personal cloud sandbox per developer and writesamplify_outputs.jsonfor the front end; in CI,npx ampx pipeline-deployreplaces Gen 1'samplify push, and the sandbox replacesamplify mock. Gen 2 has no Studio visual data or storage modeling and no DataStore. Gen 1 (theamplifyCLI wizard withamplify init,amplify add auth,amplify push; Studio's visual builder; DataStore offline sync on AppSync) remains for existing apps. - Build specification (
amplify.yml):frontend(and, for Gen 2,backend) withphases(preBuild,build,postBuild),artifacts(baseDirectory,files) andcache(paths); plus an optionaltestsection withphasespreTest,testandpostTestand its ownartifacts. - End-to-end tests:
- The integrated framework is Cypress; Amplify builds a UI report from mochawesome JSON.
preTestinstalls dependencies and starts the app locally (for example withpm2andwait-on);testruns Cypress;postTestmerges the reports.- Test
artifacts:baseDirectory(where tests run),configFilePath(the merged report) andfiles(screenshots and videos to download). - Once configured, tests run on every build of every branch. To skip them without editing
amplify.yml, set the environment variableUSER_DISABLE_TESTStotruefor all branches; to skip one branch only, set it tofalsefor all branches and add atrueoverride for that branch.
- Start a build from automation:
StartJobwith job typeRELEASE(latest commit of a connected branch) orRETRY(needs thejobIdto retry). - Custom domains: by default Amplify provisions and renews a managed certificate; you can switch to your own certificate issued by or imported into ACM (§14).
- When Amplify is the answer: a front end in Git that needs managed builds, previews and hosting with the least setup. A pipeline you design yourself → CodePipeline and CodeBuild (see
notes/cicd-guide.md); a web server or worker application → Elastic Beanstalk (seenotes/beanstalk-guide.md); containers → seenotes/containers-guide.md.
Developer angle
amplify.yml for a Vite build with a Cypress test phase:
version: 1
frontend:
phases:
preBuild: { commands: ["npm ci"] }
build: { commands: ["npm run build"] }
artifacts: { baseDirectory: dist, files: ["**/*"] }
test:
phases:
preTest:
commands:
- npm ci && npm install -g pm2 wait-on
- npm install mochawesome mochawesome-merge mochawesome-report-generator
- pm2 start npm -- run preview && wait-on http://localhost:4173
test:
commands:
- 'npx cypress run --reporter mochawesome --reporter-options "reportDir=cypress/report/parts,overwrite=false,html=false,json=true"'
postTest:
commands:
- npx mochawesome-merge cypress/report/parts/*.json > cypress/report/mochawesome.json
- pm2 kill
artifacts: { baseDirectory: cypress, configFilePath: '**/mochawesome.json', files: ['**/*.png', '**/*.mp4'] }
- A Gen 2 backend with email sign-in:
amplify/auth/resource.tsexportsdefineAuth({ loginWith: { email: true } })(imported from@aws-amplify/backend),amplify/backend.tscallsdefineBackend({ auth }), andnpx ampx sandboxdeploys it for one developer and writesamplify_outputs.json. - Trigger a release from a script:
aws amplify start-job --app-id d1a2b3c4d5e6f7 --branch-name main --job-type RELEASE.
Traps
- Trap: "Run
amplify pushto deploy the Gen 2 backend." → Reality: Gen 2 deploys withnpx ampx sandboxlocally andnpx ampx pipeline-deployin CI. - Trap: "Model data visually in Studio and use DataStore for offline sync in the new Gen 2 app." → Reality: both are Gen 1 features; Gen 2 is code-first.
- Trap: "Delete the test section in one branch to skip tests there." → Reality: give that branch a
USER_DISABLE_TESTSoverride oftrue(withfalsefor all branches); the build spec stays the same. - Trap: "Amplify shows end-to-end reports for any framework." → Reality: the integrated end-to-end testing and reports are for Cypress (mochawesome).
- Trap: "Request and renew an ACM certificate yourself for the Amplify custom domain." → Reality: by default Amplify provisions and renews a managed certificate; your own ACM certificate is optional.
Practice questions
-
A front-end team keeps a React single-page app in GitHub. Every pull request must get its own temporary URL for review, and every merge to
mainmust deploy automatically, without the team managing build servers. Which solution meets these requirements with the LEAST operational overhead?- A. An S3 static website with a GitHub Actions workflow that runs
aws s3 syncfor each branch. - B. A CodePipeline pipeline with a CodeBuild stage and a CloudFront distribution for each environment.
- C. An Elastic Beanstalk environment per pull request, created by a Lambda function.
- D. Amplify Hosting connected to the repository with pull request previews turned on.
- A. An S3 static website with a GitHub Actions workflow that runs
-
A team hosts a Vue app on Amplify Hosting and is adding Cypress end-to-end tests to its builds. On every branch except
experiment, the tests must run on each build so that a failing test stops the release. Theexperimentbranch must skip them, and all branches must keep the sameamplify.yml. Which TWO actions meet these requirements? (Select TWO.)- A. Run the tests from a CloudWatch Synthetics canary after each deployment.
- B. Add a
testsection withpreTest,testandpostTestphases toamplify.yml. - C. Remove the
testsection fromamplify.ymlin theexperimentbranch. - D. Set
USER_DISABLE_TESTStofalsefor all branches with atrueoverride forexperiment. - E. Turn on password protection for the
experimentbranch.
Answers
- D. Amplify Hosting builds each branch from Git and creates preview URLs for pull requests with no servers to manage. A and B need you to build and maintain the pipeline and per-branch hosting; C is a heavyweight server platform for a static site.
- B, D. The
testphase runs Cypress between build and deploy, andUSER_DISABLE_TESTSset tofalsefor all branches with atrueoverride forexperimentskips it on that branch only, while every branch keeps the sameamplify.yml. A tests after the release, too late to stop it; C gives theexperimentbranch its own build specification; E restricts who can view the branch but still runs the tests.
10. AppConfig feature flags validators and deployment strategies
Concept
- AWS AppConfig, a capability of Systems Manager, stores, validates and gradually deploys configuration data (feature flags, operational tuning, allow and block lists) separately from code. Applications pick up a newly deployed version at run time, without a restart or a new code deployment.
- Model: an application → environments (such as beta and prod, each optionally monitored by CloudWatch alarms) → configuration profiles (type feature flags or freeform, a source location and validators) → versions → a deployment (one profile version to one environment with a deployment strategy).
- Safety net: validators reject bad data before anything changes; the deployment strategy exposes clients gradually; an alarm during the rollout or the bake time triggers an automatic rollback.
- Reading configuration: the AppConfig Agent (a Lambda extension, an ECS or EKS sidecar, or an agent on EC2) polls and caches for you, or code calls the data plane APIs directly.
Exam facts
- ⚠️ Changed: freeform profiles can read from six locations: the AppConfig hosted configuration store (recommended; YAML, JSON or text; size quota 2,048 KB per version by default, adjustable up to 4,000 KB), S3 objects, Parameter Store parameters (
StringorSecureString), Systems Manager documents (theApplicationConfigurationtype, validated against a JSON Schema document), Secrets Manager secrets and CodePipeline. Older material may say only Parameter Store, SSM documents and S3. - Feature flags: profile type
AWS.AppConfig.FeatureFlags; each flag has an enabled state and optional typed attributes with constraints, and AppConfig validates flag data against its own JSON Schema automatically. Multi-variant flags return different values depending on the context the application sends and the rules you define. - A/B tests: ⚠️ Changed: CloudWatch Evidently was discontinued on 17 October 2025; flags and launches move to AppConfig feature flags, and AppConfig experimentation (A/B and multivariate tests) has been generally available since 1 July 2026 (details:
notes/monitoring-guide.md). Older material may say to run launches and A/B tests in CloudWatch Evidently. - Validators (up to two per profile, created before the profile):
- JSON Schema (draft 4) checks the structure of freeform data;
- Lambda runs your semantic checks for freeform data and flags: the event carries the base64-encoded
contentplus application, profile and version IDs; raising an exception rejects the version; the function gets 15 seconds; grantappconfig.amazonaws.compermission to invoke it. - Validators run on
StartDeploymentandValidateConfiguration; a failure stops the deployment before any target changes.
- Predefined deployment strategies:
| Strategy | Rollout | Bake time (alarm watch) | Intended use |
|---|---|---|---|
AppConfig.Linear20PercentEvery6Minutes |
+20 % of targets every 6 minutes (30 minutes) | 30 minutes | Production (AWS recommended) |
AppConfig.Canary10Percent20Minutes |
Exponential, 10 % growth factor, over 20 minutes | 10 minutes | Production (AWS recommended) |
AppConfig.AllAtOnce |
All targets immediately | 10 minutes | Quick changes |
AppConfig.Linear50PercentEvery30Seconds |
50 % every 30 seconds (1 minute) | 1 minute | Testing and demos only |
- Custom strategies: deployment time, growth type
LINEARorEXPONENTIAL(step G × 2^N), step percentage (GrowthFactor) and final bake time; quota 20 strategies per account and Region (adjustable). - Rollback: alarms listed as environment monitors are watched during the deployment and its bake time; an alarm in
ALARMrolls the deployment back automatically. AppConfig needs an IAM role that allows it to read those alarms. By hand,StopDeploymentrolls back a deployment in progress (ROLLED_BACK); withAllowRevertit also reverts a completed one within 72 hours (REVERTED). - Data plane (
appconfigdata):StartConfigurationSession(application, environment and profile names or IDs, optionalRequiredMinimumPollIntervalInSecondsfrom 15 to 86,400) returns anInitialConfigurationToken.GetLatestConfigurationreturns the configuration plusNextPollConfigurationToken; an empty body means nothing changed since the last call, so keep the cached copy. Each token is single-use and valid for up to 24 hours; an expired token returnsBadRequestException.- IAM:
appconfig:StartConfigurationSessionandappconfig:GetLatestConfiguration. Default throughput 500 TPS for sessions and 1,000 TPS forGetLatestConfiguration(adjustable). - ⚠️ Changed:
GetConfigurationis deprecated (each configuration received was billed, and clients had to trackClientConfigurationVersionthemselves). Older material may say to callGetConfiguration.
- AppConfig Agent Lambda extension: added as a layer; serves
http://localhost:2772/applications/<app>/environments/<env>/configurations/<profile>; environment variablesAWS_APPCONFIG_EXTENSION_POLL_INTERVAL_SECONDS(default 45),AWS_APPCONFIG_EXTENSION_HTTP_PORT(default 2772),AWS_APPCONFIG_EXTENSION_POLL_TIMEOUT_MILLIS(default 3000),AWS_APPCONFIG_EXTENSION_PREFETCH_LIST(fetch at start-up to cut cold-start time) andAWS_APPCONFIG_EXTENSION_ROLE_ARN(read from another account). AWS recommends the agent over direct API calls. - Choosing a store: validation, gradual rollout and rollback of flags or settings → AppConfig; plain settings or simple secrets → Parameter Store; rotated secrets → Secrets Manager (see
notes/security-encryption-guide.md§8–§9). CodePipeline has an AppConfig deploy action (seenotes/cicd-guide.md§10).
Developer angle
Create a flags profile, store a version and roll it out with alarm protection:
aws appconfig create-application --name checkout
aws appconfig create-environment --application-id "$APP_ID" --name prod \
--monitors AlarmArn=arn:aws:cloudwatch:eu-west-1:111122223333:alarm:checkout-5xx,AlarmRoleArn=arn:aws:iam::111122223333:role/AppConfigAlarmRole
aws appconfig create-configuration-profile --application-id "$APP_ID" --name flags \
--location-uri hosted --type AWS.AppConfig.FeatureFlags
aws appconfig create-hosted-configuration-version --application-id "$APP_ID" \
--configuration-profile-id "$PROFILE_ID" --content-type application/json \
--content fileb://flags.json version.json
aws appconfig start-deployment --application-id "$APP_ID" --environment-id "$ENV_ID" \
--configuration-profile-id "$PROFILE_ID" --configuration-version 3 \
--deployment-strategy-id AppConfig.Linear20PercentEvery6Minutes
- With the agent layer attached (and the two data plane actions in the role), a Lambda function reads its flags with a plain local HTTP GET of
http://localhost:2772/applications/checkout/environments/prod/configurations/flags, which returns JSON such as{"new-checkout": {"enabled": true}}from the agent's cache. - Without the agent: call
start_configuration_session(...)once, then on each pollget_latest_configuration(ConfigurationToken=token), replacetokenwithNextPollConfigurationToken, and keep the cached copy wheneverConfigurationis empty. - A Lambda validator for a rule JSON Schema can't express decodes
event["content"]from base64, checks the rule (for examplemaxRetriesbetween 1 and 10) and raises an exception to reject the version.
Traps
- Trap: "Redeploy the application to turn a feature on." → Reality: deploy a new flag version with AppConfig; code reads it at run time.
- Trap: "Call
GetConfigurationin a loop." → Reality: deprecated and billed per configuration received; use the agent orStartConfigurationSessionwithGetLatestConfiguration. - Trap: "Reuse the initial token on every poll." → Reality: tokens are single-use; pass
NextPollConfigurationTokeneach time. - Trap: "An empty
Configurationmeans the configuration was deleted." → Reality: it means nothing changed; keep the cached copy. - Trap: "A JSON Schema validator can compare values with a database." → Reality: JSON Schema checks structure; business rules need a Lambda validator.
- Trap: "Use
AppConfig.Linear50PercentEvery30Secondsin production." → Reality: AWS meant it for tests and demos; production strategies have longer rollouts and bake times.
Practice questions
-
A developer must turn on a new checkout flow for production users gradually: 20 % more users every 6 minutes, rolled back automatically if the checkout error alarm fires during or shortly after the rollout, and without redeploying code. Which solution meets these requirements?
- A. Store the flag in Parameter Store and change it from a scheduled Lambda function.
- B. Use an AppConfig feature flag, a CloudWatch alarm monitor on the environment and
AppConfig.Linear20PercentEvery6Minutes. - C. Publish a new Lambda version and shift traffic with the CodeDeploy configuration
CodeDeployDefault.LambdaLinear10PercentEvery1Minute. - D. Deploy an AppConfig freeform profile with the
AppConfig.AllAtOncedeployment strategy.
-
A team keeps JSON configuration in the AppConfig hosted configuration store. A deployment must be rejected when the JSON doesn't have the expected structure, and also when its
discountPercentexceeds the maximum stored in a pricing database. Which TWO validators should the configuration profile use? (Select TWO.)- A. A Parameter Store parameter policy that expires invalid parameter versions.
- B. An S3 bucket policy that denies uploads of malformed JSON.
- C. A JSON Schema validator that defines the required properties and their types.
- D. A Lambda validator that compares
discountPercentwith the value in the pricing database. - E. A CloudWatch alarm monitor on the environment.
Answers
- B. The linear strategy adds 20 % every 6 minutes, the environment's alarm monitor triggers automatic rollback during the rollout and bake time, and the application reads the flag at run time. A has no gradual exposure or rollback; C requires a code deployment; D exposes everyone at once.
- C, D. JSON Schema rejects structural errors, and a Lambda validator applies the semantic rule against the database, both before the deployment starts. A and B act on other services, not AppConfig versions; E rolls back after a bad deployment instead of rejecting it beforehand.
11. SES for application email
Concept
- Amazon SES sends email from applications (transactional messages such as receipts and password resets, marketing and bulk mail) and can also receive email. You send through the SES API v2 (
sesv2:SendEmailwith simple, raw or templated content,SendBulkEmail), the older v1 API, or the SMTP interface for existing mail libraries and servers. - Before sending you verify identities: an email address or, better, a domain. Domain authentication with DKIM (Easy DKIM signs every message with a 2048-bit key by default), SPF, DMARC and an optional custom MAIL FROM domain improves deliverability.
- New accounts start in a per-Region sandbox; production access is requested per Region.
- Configuration sets publish sending events (deliveries, bounces, complaints, opens, clicks) to other services; the account-level suppression list, dedicated IPs (standard or managed pools, or bring your own) and the Virtual Deliverability Manager help protect your sender reputation.
- Receiving: receipt rules (deliver to S3, SNS or Lambda, among other actions) or the newer Mail Manager.
Exam facts
- Sandbox (per Region): send only to verified addresses or domains (or the mailbox simulator), at most 200 messages per 24 hours and 1 message per second. Request production access in the console or with
aws sesv2 put-account-details --production-access-enabled …. In production you can send to anyone, but "From", "Source", "Sender" and "Return-Path" identities must still be verified. - Typical errors:
MessageRejectedwith "Email address is not verified. The following identities failed the check in region …" (unverified sender, or an unverified recipient while in the sandbox); throttling with "Daily message quota exceeded" or "Maximum sending rate exceeded" (back off and retry, or request a higher quota); "Account is paused" after a sending review. - Quotas: sending quotas count recipients, not messages; up to 50 recipients per message (To, CC and BCC); maximum message size 10 MB with the v1 API and 40 MB with the v2 API or SMTP (after base64 encoding); 10,000 verified identities per Region; 10,000 configuration sets with up to 10 event destinations each; 20,000 templates of up to 500 KB per Region.
- SMTP interface: endpoint
email-smtp.<region>.amazonaws.com; TLS is required, with STARTTLS on ports 25, 587 or 2587 or TLS Wrapper on 465 or 2465. EC2 throttles outbound port 25 by default, so use 587 or 2587 (or ask AWS to lift the limit). - SMTP credentials: a user name and password specific to one Region, created for an IAM user (the console creates the user for you); the SMTP password is not the secret access key, and it can't be derived from temporary credentials. The user needs
ses:SendRawEmail. - IAM: sending actions
ses:SendEmailandses:SendRawEmail(resource: identity ARNs or*). Condition keys:ses:FromAddress,ses:Recipients(useForAllValues:StringLike),ses:FromDisplayName,ses:FeedbackAddress(the Return-Path) andses:ApiVersion. Only sending authorization policies on an identity can let another account send as that identity. - Event publishing: reference a configuration set with
ConfigurationSetName(API) or theX-SES-CONFIGURATION-SETheader (raw email or SMTP), and add message tags withEmailTagsorX-SES-MESSAGE-TAGS. Event types: Send, Reject, Delivery, Bounce, Complaint, DeliveryDelay, Subscription, Open, Click, RenderingFailure. Destinations: CloudWatch, Amazon Data Firehose, Pinpoint, SNS and EventBridge. - A timed-out or failed
SendEmailcall may already have been accepted, so a retry can send a second copy; correlate events with message tags rather than only the message ID. - Receiving limits: up to 40 MB per message stored in S3, 150 KB per message published through SNS.
- SES or SNS: SNS email subscriptions deliver plain notifications to subscribers of a topic; branded transactional or bulk mail to customers is SES. Cognito user pools can send their emails through SES (see
notes/advanced-identity-guide.md§2).
Developer angle
Send through the v2 API with a configuration set and tags for event tracking:
import boto3
ses = boto3.client("sesv2")
ses.send_email(
FromEmailAddress="noreply@example.com",
Destination={"ToAddresses": ["customer@example.net"]},
Content={"Simple": {"Subject": {"Data": "Your order 8812 has shipped"},
"Body": {"Text": {"Data": "Thanks for your order. It is on its way."}}}},
ConfigurationSetName="order-mail",
EmailTags=[{"Name": "campaign", "Value": "shipping-notice"}],
)
Least-privilege policy: send only from one address and only to one domain:
{ "Version": "2012-10-17",
"Statement": [{ "Effect": "Allow", "Action": ["ses:SendEmail", "ses:SendRawEmail"], "Resource": "*",
"Condition": { "StringEquals": { "ses:FromAddress": "noreply@example.com" },
"ForAllValues:StringLike": { "ses:Recipients": ["*@example.com"] } } }] }
- CLI:
aws sesv2 create-email-identity --email-identity example.comreturns the DKIM tokens to publish in DNS;aws sesv2 get-accountshows sandbox status and quotas;aws sesv2 put-account-details --production-access-enabled --mail-type TRANSACTIONAL --website-url https://example.com --contact-language ENrequests production access.
Traps
- Trap: "Emails to customers fail, so verify the sending domain again." → Reality: in the sandbox recipients must be verified too; request production access.
- Trap: "Use the IAM secret access key as the SMTP password." → Reality: SMTP credentials are a separate, Region-specific password derived for an IAM user.
- Trap: "Send through port 25 from an EC2 instance." → Reality: EC2 throttles port 25 by default; use 587 or 2587.
- Trap: "Let the partner account send as our domain with an IAM policy in our account." → Reality: cross-account sending needs a sending authorization policy on the identity.
- Trap: "Use SNS email subscriptions for order confirmations." → Reality: SNS email is for topic notifications; customer email goes through SES.
- Trap: "Ignore bounces and complaints." → Reality: high rates trigger a review and can pause sending; capture them through event destinations or notifications.
Practice questions
-
A developer sends order confirmations from a new AWS account with the SES API v2. Messages to colleagues whose addresses were verified arrive, but messages to customers fail with an error saying the address is not verified. What should the developer do?
- A. Create SMTP credentials and send through the SMTP interface instead of the API.
- B. Verify the sending domain with Easy DKIM.
- C. Request production access to move the account out of the SES sandbox in that Region.
- D. Add a configuration set with an SNS event destination so that bounce notifications for the failed messages are captured.
-
A Lambda function sends notifications through SES. Security requires least privilege: the function may send only from
noreply@example.comand only to addresses in theexample.comdomain. Which TWO policy elements meet these requirements? (Select TWO.)- A. A condition on
ses:FromAddresswithStringEqualsnoreply@example.com. - B. A condition on
ses:RecipientswithForAllValues:StringLike*@example.com. - C. A condition on
ses:FeedbackAddressset tonoreply@example.com. - D. A sending authorization policy on the identity that names the function's role.
- E. An
ses:ApiVersioncondition set to2.
- A. A condition on
Answers
- C. In the sandbox, SES delivers only to verified recipients; production access removes that restriction. A and B don't lift the recipient rule (the sender is already verified); D only reports events.
- A, B.
ses:FromAddresspins the sender andses:RecipientswithForAllValueslimits every recipient to the domain. C restricts the bounce address, not recipients; D is for delegating an identity to other accounts and adds no recipient limit; E controls the API version only.
12. OpenSearch and Athena for search and analytics
Concept
- Amazon OpenSearch Service runs OpenSearch (successor of the Elasticsearch-based service; legacy Elasticsearch OSS versions up to 7.10 are still offered) for full-text search, partial matches and relevance ranking on any field, log analytics and vector search. It usually sits next to a primary database such as DynamoDB, which can only query by keys and indexes. Two forms: provisioned domains and OpenSearch Serverless collections; OpenSearch Dashboards for visualisation; OpenSearch Ingestion for managed ingestion pipelines.
- Amazon Athena is serverless, interactive SQL over data in S3: tables are defined in the AWS Glue Data Catalog (schema on read), you pay per data scanned, and there is nothing to provision. Typical uses: ad hoc analysis and reporting, querying ALB, CloudFront, CloudTrail or VPC Flow Logs, and dashboards in Amazon Quick Sight. Federated queries reach other sources through connectors.
- ⚠️ Changed: the dashboard service is now Amazon Quick Sight, a capability of Amazon Quick (existing QuickSight APIs and SDKs keep working). Older material may say Amazon QuickSight.
Exam facts
- Getting data into OpenSearch:
- DynamoDB: Streams → a Lambda function that indexes changes (custom code), or the DynamoDB zero-ETL integration through an OpenSearch Ingestion pipeline (initial export via point-in-time recovery to S3, then DynamoDB Streams with
NEW_IMAGEorNEW_AND_OLD_IMAGES; same account and Region; one table per pipeline). - CloudWatch Logs: a subscription filter to OpenSearch (the console creates a Lambda function for near-real-time delivery; Standard log class only) or to Amazon Data Firehose. Kinesis Data Streams → Lambda or Firehose → OpenSearch; Firehose delivers to domains and Serverless collections. ⚠️ Changed: Amazon Data Firehose since 9 February 2024 (name only). Older material may say Kinesis Data Firehose.
- DynamoDB: Streams → a Lambda function that indexes changes (custom code), or the DynamoDB zero-ETL integration through an OpenSearch Ingestion pipeline (initial export via point-in-time recovery to S3, then DynamoDB Streams with
- Querying OpenSearch: the JSON query DSL; JDBC and ODBC drivers. ⚠️ Changed: SQL and PPL run through the built-in SQL plugin (
POST _plugins/_sql;_opendistro/_sqlon Elasticsearch domains) on domains running OpenSearch or Elasticsearch 6.5 or later. Older material may say OpenSearch doesn't support SQL unless a plugin is enabled. - OpenSearch security: domain access policies and IAM (actions keep the
es:prefix, such ases:ESHttpGet; resourcesarn:aws:es:<region>:<account>:domain/<name>/*), fine-grained access control, Cognito or SAML sign-in for Dashboards, KMS encryption at rest, node-to-node encryption and HTTPS. Public or VPC access is chosen at creation and can't be changed later. Requests are SigV4-signed with service nameesfor domains andaossfor Serverless. - OpenSearch Serverless: collection types search, time series and vector search, fixed at creation; capacity in OpenSearch Compute Units (6 GiB of memory plus vCPU each), scaled separately for indexing and search; access through encryption, network and data access policies (permissions such as
aoss:CreateIndex,aoss:WriteDocument); no cross-account access. - Athena basics: formats include CSV, TSV, JSON, Parquet, ORC and Avro. ⚠️ Changed: engine version 3 tracks the open-source Trino and Presto projects. Older material may say Athena is built on Presto. Queries are asynchronous:
StartQueryExecution→ pollGetQueryExecution(QUEUED,RUNNING,SUCCEEDED,FAILED,CANCELLED) → page throughGetQueryResults. Step Functions can run them witharn:aws:states:::athena:startQueryExecution.sync. - Athena results and workgroups: ⚠️ Changed: results go to an S3 output location or, with Athena managed query results, to AWS-managed storage with no bucket to create (kept 24 hours at no cost, encrypted with an AWS owned or customer managed key). Older material may say results must go to an S3 bucket you provide. Workgroups separate users and teams, set the results location, encryption and engine version, and enforce per-query data scanned limits.
- Athena pricing (volatile): $5 per TB scanned (the price shown on the Athena pricing page; Regional prices can differ), at least 10 MB per query, rounded up to the next MB; with per-query pricing failed queries aren't charged, while a cancelled query is charged for the data scanned until it stopped (Athena FAQ); capacity reservations cost $0.30 per DPU-hour instead. Older material says DDL statements are free (⚠️ unverified: neither the pricing page nor the FAQ says).
- Cutting Athena cost and time: convert to a columnar format (Parquet or ORC) with a
CREATE TABLE AS SELECT(CTAS) query or an AWS Glue job; compress files (gzip, zstd, Snappy and others); partition by the columns you filter on (s3://bucket/logs/year=2026/month=10/day=12/) and filter on those columns; use partition projection for many partitions; avoid many small files (which can also trigger S3SlowDownerrors; a minimum such as "files over 128 MB" is ⚠️ unverified: the docs give no threshold, though Parquet row groups default to 128 MB); register partitions withALTER TABLE ADD PARTITION, a Glue crawler or partition projection rather than slowMSCK REPAIR TABLEruns. - Federated query: connectors for DynamoDB, CloudWatch Logs and Metrics, RDS and Aurora engines, Redshift, DocumentDB, OpenSearch and many third-party sources, traditionally running as Lambda functions in your account; ⚠️ Changed: since 21 April 2026 some newly created AWS Glue Data Catalog federated connectors register automatically and don't use a Lambda function in your account. Older material may say every connector is a Lambda function. Federated sources are read-only (
INSERT INTOisn't supported); results can be written to S3 with CTAS orUNLOAD. - IAM for Athena callers:
athena:StartQueryExecution,athena:GetQueryExecution,athena:GetQueryResultson the workgroup, Glue catalog reads (glue:GetDatabase,glue:GetTable,glue:GetPartitions),s3:GetObjectands3:ListBucketon the data, and write access to the results location (not needed with managed results).
Developer angle
Run an Athena query from code (asynchronous: start, poll, read):
import time, boto3
athena = boto3.client("athena")
qid = athena.start_query_execution(
QueryString="SELECT status, count(*) AS hits FROM alb_logs WHERE year = '2026' GROUP BY status",
QueryExecutionContext={"Database": "weblogs"}, WorkGroup="analytics")["QueryExecutionId"]
state = "QUEUED"
while state in ("QUEUED", "RUNNING"):
time.sleep(1)
state = athena.get_query_execution(QueryExecutionId=qid)["QueryExecution"]["Status"]["State"]
rows = athena.get_query_results(QueryExecutionId=qid)["ResultSet"]["Rows"] # check state first
Convert raw JSON logs to partitioned, compressed Parquet once, so later queries scan far less:
CREATE TABLE weblogs.alb_logs_parquet
WITH (format = 'PARQUET', parquet_compression = 'SNAPPY',
external_location = 's3://analytics-111122223333/alb-parquet/',
partitioned_by = ARRAY['year', 'month'])
AS SELECT request_url, status, elb_status_code, year, month FROM weblogs.alb_logs_json;
- SQL on an OpenSearch domain:
POST https://<domain-endpoint>/_plugins/_sqlwith a body such as{"query": "SELECT name, price FROM products WHERE name LIKE '%lamp%' LIMIT 10"}, signed with SigV4 for servicees(for examplecurl --aws-sigv4 "aws:amz:eu-west-1:es").
Traps
- Trap: "Search product descriptions with a DynamoDB
Scanandcontains()." → Reality: expensive and slow at scale; index the table in OpenSearch (zero-ETL integration or Streams with Lambda). - Trap: "Use OpenSearch as the system of record." → Reality: it complements the primary database, which stays the source of truth.
- Trap: "
LIMIT 10makes an Athena query cheap." → Reality: you pay for data scanned; partitions, columnar formats and compression cut the scan. - Trap: "Load the S3 data into a database before running SQL on it." → Reality: Athena queries it where it is, with nothing to provision.
- Trap: "Athena returns results in the
StartQueryExecutionresponse." → Reality: it returns a query ID; poll the status, then read the results. - Trap: "Sign OpenSearch Serverless requests with service name
es." → Reality: Serverless usesaoss; domains usees.
Practice questions
-
An e-commerce app stores its products in DynamoDB keyed by product ID. Customers must be able to search by any word in a product's name or description, including partial matches, and search results must reflect changes within seconds. Which solution meets these requirements with the LEAST custom code?
- A. Stream the table into OpenSearch Service with the DynamoDB zero-ETL integration (OpenSearch Ingestion).
- B. Run a DynamoDB
Scanwith acontains()filter expression on the name and description for each search. - C. Add a global secondary index on the product name and query it with
begins_with. - D. Export the table to S3 every night and run Athena queries with
LIKEfilters against the exported files.
-
An analytics team uses Athena to query 5 TB of gzip-compressed JSON access logs in S3. Most queries filter on a single day, and query costs are high. Which TWO changes reduce the amount of data scanned? (Select TWO.)
- A. Add
LIMIT 100to every query. - B. Convert the logs to Parquet with a CTAS query or an AWS Glue job.
- C. Raise the per-query data usage limit in the workgroup.
- D. Move the log objects to the S3 Intelligent-Tiering storage class.
- E. Partition the data by date in S3 and filter on the partition column.
- A. Add
Answers
- A. The zero-ETL integration keeps an OpenSearch index in sync from the table without custom code, and OpenSearch handles full-text and partial matches. B scans the whole table on every search; C only matches prefixes of one attribute; D is batch and hours out of date.
- B, E. Columnar Parquet lets Athena read only the needed columns, and date partitions let it skip other days entirely. A still scans the files; C only changes a cost guardrail; D changes storage pricing, not the bytes Athena reads.
13. MSK vs Kinesis Data Streams
Concept
- Amazon MSK is managed Apache Kafka: you work with topics, partitions, producers, consumers and consumer groups using standard Kafka clients and tools, while MSK runs the brokers and the cluster metadata. Cluster options: provisioned clusters with Standard brokers (you size broker storage) or Express brokers (storage managed for you), and MSK Serverless (no capacity to manage). MSK Connect runs Kafka Connect connectors.
- Kinesis Data Streams (KDS) is the AWS-native streaming service built on shards and AWS APIs (
PutRecord,PutRecords, the KCL, Lambda event source mappings); seenotes/sqs-sns-kinesis-guide.md§10–§12. - Choosing: existing Kafka applications, the Kafka ecosystem (Kafka Connect, Kafka Streams, Kafka APIs), or very long or unlimited retention → MSK. The least operations and native AWS integrations, with no Kafka requirement → KDS. Records over 1 MiB no longer point to MSK on their own: KDS accepts records of up to 10 MiB once a stream opts in (meant for occasional large records), MSK Serverless up to 8 MiB, and provisioned MSK as much as a raised
message.max.bytesallows.
Exam facts
| Kinesis Data Streams | Amazon MSK | |
|---|---|---|
| Unit of scale | Shard: 1 MB/s or 1,000 records/s in, 2 MB/s out | Partition of a topic, spread over brokers |
| Scaling | Split and merge shards, or on-demand capacity | Add partitions (never remove them), add brokers; Express brokers scale up to 20 times faster |
| Record or message size | 1 MiB by default, up to 10 MiB when raised per stream | About 1 MB by default (Kafka message.max.bytes); raise it in a custom configuration; MSK Serverless up to 8 MiB |
| Retention | 24 hours by default, up to 365 days | As long as configured (unlimited with tiered storage or MSK Serverless) |
| Encryption in transit | TLS (HTTPS APIs) | TLS-only between clients and brokers by default (TLS plus plaintext, or plaintext only, can be chosen); TLS between brokers by default |
| Encryption at rest | KMS (optional server-side encryption) | Always on, with an AWS managed or customer managed KMS key |
| Client authentication | IAM | IAM access control, SASL/SCRAM (credentials in Secrets Manager), mutual TLS (AWS Private CA) or unauthenticated; MSK Serverless: IAM only |
| Consumers | KCL, Lambda, Firehose, Managed Service for Apache Flink | Kafka clients on EC2, ECS or EKS, Lambda, Managed Service for Apache Flink, Glue streaming ETL, MSK Connect |
- ⚠️ Changed: KDS records can be up to 10 MiB since 28 October 2025, opt-in per stream (
UpdateMaxRecordSize); the default stays 1 MiB and shard throughput is unchanged. Older material may say 1 MB is the hard limit, so larger messages need MSK. - ⚠️ Changed: MSK supports KRaft metadata mode from Apache Kafka 3.7.x (May 2024): controllers inside Kafka replace ZooKeeper nodes, at no extra cost and fully managed. Version 3.9 is the last to support both modes, Kafka 4.x runs KRaft only, and ZooKeeper clusters can migrate with
UpdateClusterKafkaVersion. Connect with--bootstrap-server; the old--zookeeperflag was removed in Kafka 3.0. Older material may say MSK always runs ZooKeeper nodes for you. - ⚠️ Changed: Express brokers have pay-as-you-go storage that scales automatically with nothing to size or provision, up to 3 times the throughput of a Standard broker, up to 20 times faster scaling and 90 % faster recovery; they run only in 3 Availability Zones. Standard brokers keep data on EBS volumes from 1 GiB to 16,384 GiB per broker, optionally with tiered storage, in 2 or 3 Availability Zones. Older material may say MSK data always lives on EBS volumes that you size.
- MSK Serverless quotas (per cluster): 200 MBps in, 400 MBps out, 5 MBps in and 10 MBps out per partition, 2,400 partitions (non-compacted), 8 MiB messages, unlimited retention, 3,000 client connections. IAM access control is required; Kafka ACLs aren't supported.
- Defaults on provisioned clusters:
auto.create.topics.enablefalse,default.replication.factor3 (2 for a 2-AZ cluster),min.insync.replicas2 (1 for 2 AZs),num.partitions1. Brokers per cluster: 30 with ZooKeeper, 60 with KRaft. - IAM access control for clients:
security.protocol=SASL_SSLwithsasl.mechanism=AWS_MSK_IAM(Java,aws-msk-iam-authlibrary) orOAUTHBEARER(Java and other languages through the MSK IAM SASL signer libraries). Policies usekafka-cluster:actions such asConnect,DescribeTopic,WriteData,ReadData,DescribeGroupandAlterGroupon cluster, topic and group ARNs (arn:aws:kafka:<region>:<account>:topic/<cluster>/<uuid>/<topic>). A producer needsConnect,DescribeTopicandWriteData; a consumer also needsReadDataplusDescribeGroupandAlterGroup. - Lambda reads MSK through an event source mapping (see
notes/lambda-guide.md§3). ⚠️ Changed: the Flink consumer service is Amazon Managed Service for Apache Flink since 30 August 2023. Older material may say Kinesis Data Analytics for Apache Flink.
Developer angle
aws kafka get-bootstrap-brokers --cluster-arn <arn>returns the IAM, TLS or SCRAM bootstrap strings for--bootstrap-server;aws kafka describe-cluster-v2 --cluster-arn <arn> --query ClusterInfo.Stateshows when the cluster isACTIVE.- Java client properties for IAM access control (with the
aws-msk-iam-authJAR on the class path):security.protocol=SASL_SSL,sasl.mechanism=AWS_MSK_IAM,sasl.jaas.config=software.amazon.msk.auth.iam.IAMLoginModule required;andsasl.client.callback.handler.class=software.amazon.msk.auth.iam.IAMClientCallbackHandler.
Least-privilege producer policy for one topic:
{ "Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": "kafka-cluster:Connect",
"Resource": "arn:aws:kafka:eu-west-1:111122223333:cluster/orders-cluster/*" },
{ "Effect": "Allow", "Action": ["kafka-cluster:DescribeTopic", "kafka-cluster:WriteData"],
"Resource": "arn:aws:kafka:eu-west-1:111122223333:topic/orders-cluster/*/orders" } ] }
Traps
- Trap: "Rewrite the Kafka applications for Kinesis to move them to AWS." → Reality: MSK runs Kafka, so existing clients and connectors keep working with configuration changes only.
- Trap: "Reduce a topic's partitions after a traffic peak." → Reality: Kafka can only add partitions; KDS can merge shards.
- Trap: "Use SCRAM users or Kafka ACLs on MSK Serverless." → Reality: MSK Serverless requires IAM access control.
- Trap: "Point admin tools at the ZooKeeper connection string." → Reality: use
--bootstrap-server; KRaft clusters have no ZooKeeper, and--zookeeperis gone from Kafka 3.0. - Trap: "Run Express brokers in two Availability Zones." → Reality: Express brokers require three.
Practice questions
-
A company runs Kafka producers and consumers on premises, using the Kafka client libraries and several Kafka Connect connectors. It wants a managed AWS streaming service and the fewest code changes. Which service should it choose?
- A. Amazon Kinesis Data Streams with the Kinesis Producer Library
- B. Amazon SQS FIFO queues
- C. Amazon Data Firehose
- D. Amazon MSK
-
A team is choosing between Kinesis Data Streams and Amazon MSK for a new event stream. Which TWO requirements point to MSK? (Select TWO.)
- A. Existing consumers use Kafka consumer groups and must keep using the Kafka client API.
- B. The stream must scale by splitting and merging shards.
- C. Topic data must be retained for three years.
- D. Consumers must use the Kinesis Client Library.
- E. Producers must send batches with
PutRecords.
Answers
- D. MSK is managed Apache Kafka, so the existing clients and connectors (through MSK Connect) work as they are. A and B require rewriting producers and consumers for other APIs; C delivers data to destinations and can't replace Kafka topics and consumer groups.
- A, C. Kafka client compatibility and multi-year retention favour MSK; KDS keeps data for at most 365 days. B, D and E are Kinesis Data Streams features.
14. ACM Private CA and Macie
Concept
- AWS Certificate Manager (ACM) issues, stores and renews TLS certificates and deploys them to integrated services: Elastic Load Balancing, CloudFront, API Gateway custom domains, AppSync custom domains, Amplify, App Runner, Elastic Beanstalk (through its load balancer), Cognito custom domains (through CloudFront), OpenSearch custom endpoints, Network Firewall, CloudFormation (
AWS::CertificateManager::Certificate) and EC2 instances that use Nitro Enclaves. Public certificates for integrated services cost nothing, and ACM keeps their private keys. - AWS Private CA runs private root and subordinate certificate authorities whose certificates are trusted only inside your organization: internal TLS between services, mutual TLS (for example MSK client authentication), device and user identity, code signing.
- Amazon Macie discovers sensitive data (personal data, financial data, credentials) in S3 with managed data identifiers (machine learning plus pattern matching), custom data identifiers and allow lists, and reports on bucket security posture such as public or unencrypted buckets.
Exam facts
- Regions: ACM certificates are Regional. CloudFront (and therefore edge-optimized API Gateway endpoints and AppSync custom domains) needs the certificate in us-east-1; a load balancer or a Regional API needs it in its own Region (see
notes/ec2-mastery-guide.md§9 andnotes/api-gateway-guide.md§2). - Validation: DNS (a CNAME record; enables automatic renewal as long as the record stays) or email (renewal needs someone to approve). A CloudFormation stack that requests an email-validated certificate waits in
CREATE_IN_PROGRESSuntil it is validated. - ⚠️ Changed: public certificates are valid for 198 days, and managed renewal starts 45 days before expiry (since February 2026). Older material may say 13 months (395 days) with renewal from 60 days before expiry.
- Managed renewal: applies to ACM-issued certificates that are in use with an integrated service or were exported, and to private certificates requested through ACM; not to imported certificates, certificates issued directly with Private CA's
IssueCertificate, or expired ones. The certificate ARN stays the same after renewal. - Expiry alerts: EventBridge receives
ACM Certificate Approaching Expirationevents daily, starting 45 days before expiry for private and imported certificates and 30 days before for public ones (adjustable withPutAccountConfiguration), plusACM Certificate Expired,ACM Certificate AvailableandACM Certificate Renewal Action Requiredevents. Imported certificates must be re-imported before they expire. - ⚠️ Changed: since June 2025 ACM also issues exportable public certificates (certificate, private key and chain) for EC2, containers or on-premises servers; they are charged $7 per fully qualified domain name and $79 per wildcard name at issuance and at each renewal (volatile), and you install each renewal yourself. Standard public certificates still can't be exported, and the course's "free of charge" applies only to them. Older material may say ACM public certificates are always free and never leave ACM.
- Certificates on EC2: an ACM-managed certificate can be attached directly to an instance only through Nitro Enclaves; otherwise use an exportable certificate, or ACM's ACME automation (45-day publicly trusted certificates issued to an ACME client such as Certbot, which keeps the private key and renews; these can't be used with integrated services).
- AWS Private CA pricing (volatile): $400 per CA per month in general-purpose mode, $50 per CA per month in short-lived certificate mode, plus a price per issued certificate; the first private CA in each Region has a 30-day free trial of the CA fee. Private certificates requested through ACM can be exported and renewed by ACM.
- Macie discovery: automated sensitive data discovery samples objects across your S3 general purpose buckets every day and builds sensitivity scores and a bucket heat map; sensitive data discovery jobs analyse a defined scope once or on a schedule. Only S3 data is analysed (not RDS or DynamoDB).
- Macie findings: policy findings (bucket security and access changes) and sensitive data findings (objects containing sensitive data). Both are published to EventBridge automatically (sensitive data findings at once; updates to existing policy findings every 15 minutes by default, or 1 or 6 hours); AWS Security Hub CSPM receives new and updated policy findings by default and sensitive data findings if you opt in. Macie keeps findings for 90 days, so ship them through EventBridge for longer retention. Suppression rules archive expected findings, which are then not published. Macie works per Region and supports a delegated administrator for an AWS Organization; the CLI namespace is
macie2. - Not Macie: threat detection from logs and activity → GuardDuty; software vulnerability scanning → Inspector; configuration compliance → AWS Config.
Developer angle
# Certificate for a CloudFront distribution: request it in us-east-1, validated through DNS
aws acm request-certificate --region us-east-1 --domain-name shop.example.com \
--subject-alternative-names www.shop.example.com --validation-method DNS
aws acm describe-certificate --region us-east-1 --certificate-arn "$CERT_ARN" \
--query 'Certificate.DomainValidationOptions[].ResourceRecord'
# Macie: enable it, turn on automated discovery, then run a one-time job on one bucket
aws macie2 enable-macie
aws macie2 update-automated-discovery-configuration --status ENABLED
aws macie2 create-classification-job --job-type ONE_TIME --name pii-scan-uploads \
--s3-job-definition '{"bucketDefinitions":[{"accountId":"111122223333","buckets":["customer-uploads"]}]}'
EventBridge rule pattern that sends Macie sensitive data findings to an SNS topic or a Lambda remediation function:
{ "source": ["aws.macie"], "detail-type": ["Macie Finding"], "detail": { "category": ["CLASSIFICATION"] } }
Traps
- Trap: "Request the CloudFront certificate in the origin's Region." → Reality: CloudFront uses certificates from us-east-1 only.
- Trap: "ACM renews every certificate it holds." → Reality: imported certificates are never renewed by ACM, and email-validated ones need approval.
- Trap: "Install the free ACM certificate on the EC2 web server." → Reality: standard certificates can't be exported; use the load balancer, Nitro Enclaves, an exportable (paid) certificate or ACME.
- Trap: "Use a public certificate for service-to-service mTLS inside the company." → Reality: private trust comes from AWS Private CA.
- Trap: "Turn on GuardDuty to find credit card numbers in S3." → Reality: that is Macie; GuardDuty detects threats.
- Trap: "Poll the Macie API for new findings." → Reality: Macie publishes findings to EventBridge automatically; react with a rule.
Practice questions
-
A company serves a web application through CloudFront with an Application Load Balancer origin in eu-west-1. It needs a free public certificate for
shop.example.comon the distribution that renews automatically. What should the developer do?- A. Request the certificate from ACM in eu-west-1 with DNS validation and attach it to the distribution.
- B. Request the certificate from ACM in us-east-1 with DNS validation and attach it to the distribution.
- C. Request an exportable public certificate and upload it with
aws iam upload-server-certificate. - D. Create a private CA in AWS Private CA and issue the certificate from it.
-
A security team must find S3 objects that contain credit card numbers across all buckets in an account and alert an on-call channel automatically for each new finding. Which TWO actions meet these requirements with the LEAST operational overhead? (Select TWO.)
- A. Enable Amazon GuardDuty S3 Protection for the account.
- B. Run Amazon Inspector scans against the buckets.
- C. Generate S3 Inventory reports and scan the objects with a Lambda function that uses regular expressions.
- D. Enable Amazon Macie with automated sensitive data discovery, or a discovery job, for the buckets.
- E. Create an EventBridge rule for Macie findings that targets an SNS topic.
Answers
- B. CloudFront only uses ACM certificates from us-east-1, and DNS validation lets ACM renew automatically at no cost. A puts the certificate in a Region CloudFront can't use; C costs money and needs manual redeployment at each renewal; D issues a certificate browsers don't trust.
- D, E. Macie finds sensitive data in S3 and publishes each finding to EventBridge, where a rule can notify SNS. A detects threats rather than data content; B scans compute for software vulnerabilities; C is custom code to build and run.
Quick decision rules
- If you see several services to coordinate with retries, branching and waits but no orchestration code, think Step Functions.
- If you see client retries that must not start a duplicate run, think a Standard workflow with a unique execution
name(new input under a reused name →ExecutionAlreadyExists). - If you see millions of S3 objects to process in parallel or the 25,000-event history limit, think Distributed Map with
ItemReaderand a tolerated failure threshold. - If you see a Lambda function that only forwards one AWS API call, think an optimized or AWS SDK integration.
- If you see "wait until the Batch, ECS, Glue, Athena or CodeBuild job finishes", think the
.syncsuffix. - If you see human approval, a third-party reply or a legacy system behind a queue, think
.waitForTaskTokenwith$$.Task.Token,SendTaskSuccessand aTimeoutSeconds. - If you see workers you host that pull work, think activities:
GetActivityTaskwith a client timeout of at least 65 seconds, plusSendTaskHeartbeat. - If you see transient Lambda errors in a workflow, think a retrier on the
Lambda.*service exceptions with backoff andFULLjitter. - If you see a
States.ALLcatcher that didn't fire, thinkStates.DataLimitExceededorStates.Runtime. - If you see a fallback state that lost the original input, think
"ResultPath": "$.error"; to keep the input and drop the result,"ResultPath": null. - If you see state data growing past 256 KiB, think S3 plus a pointer in the state; for a value needed several states later,
Assignvariables. - If you see a non-idempotent step or a run longer than 5 minutes, think Standard; for a huge rate of short idempotent runs, asynchronous Express; for the result in the same request, synchronous Express (
StartSyncExecution). - If you see "restart the failed run from the failed step", think redrive (Standard only, within 14 days); for unit tests of one state,
TestStatewith mocks. - If you see GraphQL, several data sources per request or real-time updates for web and mobile clients, think AppSync with subscriptions.
- If you see subscribers not notified about a change, think the change bypassed AppSync mutations.
- If you see one Lambda invocation per list item in a GraphQL query, think
BatchInvokeor per-resolver caching. - If you see custom tokens, per-tenant context or per-caller hidden fields, think
AWS_LAMBDAauthorization withresolverContextanddeniedFields. - If you see public and signed-in access on one AppSync API, think an additional authorization mode plus directives on the field and its type.
- If you see a front end in Git that needs builds, pull request previews and hosting with the least setup, think Amplify Hosting; to block a release on end-to-end tests, a Cypress
testphase inamplify.yml. - If you see a flag or setting that must change without a code deployment, with validation, gradual rollout and rollback, think AppConfig, read through the agent on
localhost:2772orStartConfigurationSession+GetLatestConfiguration(notGetConfiguration). - If you see SES messages to customers rejected as unverified, think the account is still in the SES sandbox; to limit sender and recipients,
ses:FromAddressandses:Recipientsconditions. - If you see full-text or partial-match search over data kept in DynamoDB, think OpenSearch through the zero-ETL integration or Streams with Lambda.
- If you see ad hoc SQL on data in S3 with nothing to provision, think Athena; to cut its cost, Parquet, compression and partitions.
- If you see existing Kafka applications moving to AWS, think MSK; with no capacity planning, MSK Serverless with IAM access control.
- If you see a certificate for CloudFront or an AppSync custom domain, think ACM in us-east-1 with DNS validation; on your own servers, ACME automation or an exportable certificate; for internal mutual TLS, AWS Private CA.
- If you see personal data to discover in S3, think Macie with an EventBridge rule on its findings.
Changed vs older material
Learn the Current column; older practice material may still show the Older material may say value. Rows you confirm are added to notes/current-facts.md (rows that already exist there for other modules are only referenced).
| Topic | Older material may say | Current (verified 2026-10-12) | Source |
|---|---|---|---|
| §1 · Map state fields | Iterator and Parameters inside a Map state |
ItemProcessor and ItemSelector replace them (the old fields still work but are deprecated) |
AWS docs |
| §1 · Restarting failed executions | A failed execution can only be started again from the beginning | Redrive restarts failed, aborted or timed-out Standard executions from the failed step within 14 days (executions started on or after 15 Nov 2023) | AWS docs |
| §4 · Data transformation language | JSONPath (InputPath, Parameters, ResultSelector, ResultPath, OutputPath) is the only option |
Since 22 Nov 2024 JSONata (Arguments, Output, {% %}) and variables (Assign) are available; JSONPath remains the default |
AWS blog |
| §4 · State payload size | 32,768 characters | Unchanged since 3 Sep 2020: 256 KiB (UTF-8) per task, state or execution input or output, Standard and Express, hard quota | AWS docs |
| §6 · Execution start and transition rates | Standard: over 2,000 executions per second; Express: over 100,000 per second | Documented as soft quotas: Standard StartExecution bucket 1,300 / refill 300 per second (us-east-1, us-west-2, eu-west-1; 800 / 150 elsewhere) and 5,000 or 800 state transitions per second; Express StartExecution 6,000 / 6,000 per second and unlimited transitions |
AWS docs |
| §6 · Local testing | Test state machines with Step Functions Local | Step Functions Local is unsupported; use the TestState API, which can mock service integrations since November 2025 |
AWS docs |
| §7 · AppSync subscription protocol | WebSockets or MQTT over WebSockets | Pure WebSockets only since 1 Jan 2022 | AWS docs |
| §8 · AppSync authorization types | Four: API key, IAM, OIDC, Cognito user pools | Five: API_KEY, AWS_LAMBDA, AWS_IAM, OPENID_CONNECT, AMAZON_COGNITO_USER_POOLS |
AWS docs |
| §8 · AppSync custom domains | Put your own CloudFront distribution in front of AppSync for a custom domain and HTTPS | Native custom domain names: ACM certificate in us-east-1, AppSync-managed CloudFront distribution, /graphql and /graphql/realtime endpoints |
AWS docs |
| §9 · Building Amplify backends | Amplify CLI wizard, Amplify Studio visual builder and DataStore | Amplify Gen 2 (GA 6 May 2024) for new apps: TypeScript code-first backend, npx ampx sandbox, npx ampx pipeline-deploy; no Studio data modeling or DataStore in Gen 2 |
AWS docs · AWS blog |
| §10 · AppConfig configuration sources | Parameter Store, SSM documents and S3 | Six: hosted configuration store (recommended), S3, Parameter Store, SSM documents, Secrets Manager, CodePipeline | AWS docs |
| §10 · Reading AppConfig data from code | GetConfiguration with a client ID |
GetConfiguration is deprecated; use StartConfigurationSession + GetLatestConfiguration, or the AppConfig Agent (recommended) |
AWS docs |
| §10 · Feature experiments | CloudWatch Evidently runs launches and A/B tests | Evidently discontinued 17 Oct 2025; AppConfig feature flags, and AppConfig experimentation GA since 1 Jul 2026 (rows owned by the monitoring module) | What's New |
| §12 · SQL on OpenSearch | OpenSearch doesn't support SQL unless a plugin is enabled | SQL (and PPL) through the built-in SQL plugin on domains running OpenSearch or Elasticsearch 6.5+, endpoint _plugins/_sql; the native query language is still the JSON DSL |
AWS docs |
| §12 · Firehose name | Amazon Kinesis Data Firehose | Amazon Data Firehose since 9 Feb 2024 (name only; row owned by the sqs-sns-kinesis module) | What's New |
| §12 · Athena engine | Built on Presto | Engine version 3 tracks both Trino and Presto | AWS docs |
| §12 · Athena query results | Results must be written to an S3 bucket you provide | An S3 output location, or Athena managed query results (no bucket; kept 24 hours) | AWS docs |
| §12 · Athena federated connectors | Every data source connector runs as a Lambda function in your account | Since 21 Apr 2026 some newly created AWS Glue Data Catalog federated connectors register automatically and don't use a Lambda function in your account | AWS docs |
| §12 · Dashboard service name | Amazon QuickSight | Amazon Quick Sight, a capability of Amazon Quick; existing QuickSight APIs and SDKs unchanged | AWS docs |
| §13 · MSK cluster metadata | MSK runs and manages ZooKeeper nodes for every cluster | KRaft mode from Kafka 3.7.x; 3.9 is the last version with both modes; Kafka 4.x is KRaft-only; ZooKeeper clusters can migrate | AWS docs |
| §13 · MSK broker storage | Data always sits on EBS volumes you size | Standard brokers: EBS 1–16,384 GiB per broker plus optional tiered storage; Express brokers: pay-as-you-go storage with no sizing, 3 AZs only | AWS docs |
| §13 · Kinesis Data Streams record size | 1 MB maximum | Up to 10 MiB per record, opt-in per stream (default 1 MiB) since 28 Oct 2025 (row owned by the sqs-sns-kinesis module) | AWS docs |
| §13 · Flink service name | Kinesis Data Analytics for Apache Flink | Amazon Managed Service for Apache Flink since 30 Aug 2023 (row owned by the sqs-sns-kinesis module) | AWS blog |
| §14 · ACM public certificate pricing | Public certificates are free and never leave ACM | Non-exportable public certificates are free; exportable public certificates cost $7 per FQDN and $79 per wildcard at issuance and each renewal (row owned by the ec2 module) | AWS pricing |
| §14 · ACM public certificate validity | 13 months (395 days), renewal from 60 days before expiry | 198 days, managed renewal from 45 days before expiry (since February 2026; row owned by the ec2 module) | What's New |