AWS SAA-C03: Serverless & Event-driven Architectures / API Integration — Study Guide
Part of the AWS SAA-C03 Complete Study Guide. Practice these concepts with verified answers in the AWS/Amazon exam hub, or take timed practice tests on ExamRoll.io.
AWS Lambda: Execution Model, Runtime Lifecycle, and Cold Starts
Lambda executes code in isolated Firecracker micro-VMs that follow a two-phase lifecycle. The INIT phase provisions the container, bootstraps the runtime, downloads and unpacks the deployment package, and runs any module-level initialization — SDK client construction, KMS-decrypted secret loading, database connection pool setup, JVM class loading and JIT warm-up. The INVOKE phase runs the handler. A cold start incurs the full INIT cost; a warm invocation reuses the environment, so anything cached outside the handler (HTTP keep-alive pools, decrypted secrets, DB clients) persists across invocations on that container. This is why the canonical pattern is to construct expensive objects at module scope and reuse them:
import boto3, os
ddb = boto3.client('dynamodb') # reused across warm invokes
_secret = None
def _load_secret():
global _secret
if _secret is None:
_secret = kms.decrypt(...) # KMS call once per container, not per invoke
return _secret
def handler(event, ctx):
...
Cold start magnitude varies by runtime. Node.js and Python are tens to a few hundred milliseconds; the JVM and .NET can push past a second. For VPC-attached functions, Hyperplane ENIs have largely amortized the historical ENI-attachment penalty, but package size and heavy INIT code still dominate.
Three tools attack cold starts differently:
| Feature | Behavior | Cost | Best fit |
|---|---|---|---|
| On-demand concurrency | Default; scales by ~500–3,000 initial burst then +500/min | Per invocation + duration | Bursty, latency-tolerant |
| Provisioned concurrency | Pre-initializes N environments, INIT complete before traffic | Pay for provisioned units 24/7 + invocations | Strict p99 latency SLA |
| SnapStart (Java, Python, .NET) | Firecracker snapshot after INIT; restored on cold start | No extra charge on Java; small caching charge elsewhere | Java functions where full provisioning is wasteful |
SnapStart is the cost-effective sweet spot for Java workloads without hard latency contracts — cold starts drop by roughly an order of magnitude at no per-invocation surcharge. Provisioned concurrency is the correct answer when a synchronous API must hold a p99 under, say, 100 ms during bursts; sizing it to the p95 burst closes the tail-latency gap. Under-sizing it is a classic failure: on-demand only spins up new containers when a request arrives, so a spike produces multi-second waits for real users.
# SAM: SnapStart on a Java 17 function
MyJavaFn:
Type: AWS::Serverless::Function
Properties:
Runtime: java17
SnapStart: { ApplyOn: PublishedVersions }
AutoPublishAlias: live
Provisioned concurrency itself can be scaled via Application Auto Scaling — a scheduled action ramping from 10 to 200 at 07:45 and back at 10:00 avoids paying for warm capacity overnight while eliminating morning-surge cold starts.
Memory sizing is simultaneously a CPU dial: vCPU scales linearly with memory up to ~1,769 MB per vCPU. A function pinned at 128 MB can cost more overall than one at 1,024 MB because doubled runtime often exceeds doubled per-ms price. Do not guess — use AWS Lambda Power Tuning to sweep configurations against representative payloads.
Lambda Concurrency Controls and Downstream Protection
Three concurrency dials matter and each does a different job:
- Reserved concurrency caps and reserves how many concurrent executions a function may consume. It carves out capacity from the account pool and protects downstream systems (a small RDS instance, a partner API with rate limits) from being overrun.
- Provisioned concurrency pre-initializes environments — the latency lever, not a scaling lever.
- Unreserved account concurrency is the shared pool, defaulting to 1,000 per Region.
Assuming Lambda “scales infinitely” overlooks two ceilings. First, the account-level concurrent execution limit is real, and burst limits (initial 500–3,000 depending on Region, then +500/min) govern ramp rate. Once exceeded, synchronous callers receive 429 TooManyRequestsException, which API Gateway surfaces as 5xx. Second, downstream systems have their own limits: a db.t3.micro with max_connections=85 cannot survive a thousand concurrent Lambdas each opening a connection. PostgreSQL forks a backend process per connection consuming ~10 MB of RAM; even with CPU spare, connection setup and teardown alone can peg the instance.
The two mitigations are (1) RDS Proxy, which pools and multiplexes many client-side connections onto a small set of persistent backend connections, and (2) inserting a queue so processing rate is decoupled from arrival rate:
import psycopg2, os
conn = psycopg2.connect(
host=os.environ['PROXY_ENDPOINT'], # RDS Proxy, not the DB directly
dbname='orders', user='app', password=get_secret())
RDS Proxy is a minimal-change solution — the driver and connection string barely change — which makes it the right answer whenever the requirement is “least change to the application” plus connection exhaustion. DynamoDB does not have this problem: its HTTPS API is stateless, which is why DynamoDB pairs naturally with high-fanout Lambda workloads.
Lambda IAM, Environment Variables, and Networking
Every function assumes an execution role at invocation. The role’s trust policy grants sts:AssumeRole to lambda.amazonaws.com; its permission policy defines what the function may do. The runtime injects short-lived STS credentials into AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN automatically. Never embed IAM user access keys in environment variables or code — they are static, discoverable in leaked source or CloudTrail exports, must be rotated manually, and bypass the entire temporary-credential model.
FnRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal: { Service: lambda.amazonaws.com }
Action: sts:AssumeRole
Policies:
- PolicyName: ReadOrders
PolicyDocument:
Statement:
- Effect: Allow
Action: ["dynamodb:GetItem", "dynamodb:Query"]
Resource: !GetAtt OrdersTable.Arn
Environment variables carrying sensitive configuration should be encrypted with a customer-managed KMS key, using “helpers for encryption in transit” so the console encrypts client-side. Decrypt once at INIT and cache the plaintext in a module-level variable — otherwise every invocation pays a KMS API call.
By default a function runs in an AWS-managed VPC with unrestricted outbound internet. Attach it to your own VPC only when it must reach VPC-resident resources (RDS in private subnets, ElastiCache, on-prem via Direct Connect). Lambda then attaches Hyperplane ENIs into the subnets you specify and inherits their routing.
The classic trap is placing a Lambda in a private subnet with no route for AWS-service traffic except through a NAT gateway — or worse, a self-managed NAT instance. NAT instances saturate at a single NIC, are a SPOF, and bill per GB. Even NAT gateways charge per GB and are unnecessary for AWS-service traffic. The correct pattern:
- Gateway VPC endpoints for S3 and DynamoDB (free)
- Interface (PrivateLink) endpoints for KMS, Secrets Manager, SQS, SNS, STS, and similar
This keeps traffic on the AWS backbone, removes NAT throughput ceilings, and prevents surprise egress bills. Note that Lambda ENIs never get a public IP; placing the function in a “public” subnet does not give it internet access — you still need a NAT gateway in a separate public subnet with an appropriate route.
Amazon API Gateway: Endpoint Types, Authorization, and Delivery
API Gateway offers three API flavors:
| Feature | REST API | HTTP API | WebSocket |
|---|---|---|---|
| Latency | Higher | ~60% lower | Stateful, bidirectional |
| Cost | Higher | ~70% cheaper | Per-message |
| Usage plans / API keys | Yes | No | No |
| Request/response transforms (VTL) | Yes | Limited | — |
| JWT authorizers | Via Lambda | Native | Via Lambda |
| WAF | Yes | No (front with CloudFront) | Yes |
Choose REST when you need API keys and usage plans, JSON Schema request validation, VTL mapping templates, Cognito authorizers with per-method scopes, or WAF; choose HTTP for lightweight JWT-authenticated proxy patterns where cost and latency matter more.
Endpoint types govern where the API is fronted:
- Edge-optimized: fronted by a Gateway-managed CloudFront distribution; TLS terminates at the nearest POP. Best for globally distributed clients.
- Regional: exposed from a single Region without CloudFront. Best when clients are in-Region, when you want your own CloudFront in front, or when using Route 53 latency-based routing across multiple Regions.
- Private: reachable only through an interface VPC endpoint. Best for internal microservices that must not traverse the public internet.
Selecting edge-optimized for an internal, single-Region API adds an unnecessary CloudFront hop and slower deployment propagation. Selecting regional for a globally consumed public API forces every request across the public internet to one Region.
Custom domains require ACM certificates with a location that depends on endpoint type: us-east-1 for edge-optimized (CloudFront is global and terminates TLS there); the API’s own Region for regional endpoints. Confusing this is a routine misconfiguration. Base path mappings let one domain multiplex multiple APIs (/orders → orders API, /users → users API).
Authorization has four models, and reaching past the built-ins is a classic anti-pattern:
| Mechanism | When to use |
|---|---|
| IAM authorization | Callers are AWS principals that can sign SigV4 (other services, SDKs, cross-account) |
| Cognito user pool authorizer | Users authenticate against a Cognito user pool; Gateway validates the JWT |
| Lambda authorizer | Non-standard tokens, third-party IdPs without OIDC, complex per-request logic |
| API keys + usage plans | Metering, throttling, quotas — never used as authentication |
A custom Lambda authorizer adds an extra invocation per request (or per cache TTL), another function to patch and monitor, and a code path where signature-validation bugs can silently permit access. Use it only when built-ins truly cannot express the requirement.
Lambda proxy integration is the default pattern — the entire request is passed as an event and the function must return the response envelope shape:
{
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": "{\"orderId\":\"abc123\"}",
"isBase64Encoded": false
}
For fire-and-forget ingestion at very high TPS, use Gateway’s direct AWS service integration to push straight into SQS or Kinesis, skipping the Lambda hop entirely. This eliminates cold starts on the write path and decouples ingestion rate from processing capacity.
For safe rollouts, use canary deployments on a stage: a percentage of traffic goes to the new deployment while the majority stays on the stable version, per-canary CloudWatch metrics inform promotion or rollback.
aws apigateway update-stage --rest-api-id abc123 --stage-name prod \
--patch-operations \
op=replace,path=/canarySettings/percentTraffic,value=10 \
op=replace,path=/canarySettings/deploymentId,value=xyz789
For a simple webhook where API Gateway ceremony is overkill (single-tenant Slack callback, GitHub push handler), Lambda function URLs give a dedicated HTTPS endpoint directly on the function. Secure them with AuthType: AWS_IAM when callers are AWS principals; if NONE, you must validate the request signature in-function. A function URL with AuthType: NONE and no in-function verification is an anonymous compute endpoint on the public internet.
Invocation Types, Retries, and Idempotency
Event sources split into two categories with very different behavior. Push-based sources (API Gateway, ALB, S3, SNS, EventBridge, Cognito) invoke Lambda directly and require an AWS::Lambda::Permission resource-based policy granting lambda:InvokeFunction with the correct Principal (e.g., events.amazonaws.com) and SourceArn. Without it the rule matches events but every invocation is silently denied — the function never runs and failures appear only in CloudTrail. This is distinct from the execution role, which governs what the function can do, not who can call it.
Poll-based sources (SQS, Kinesis, DynamoDB Streams, MSK) are read by the Lambda service via an event source mapping — no resource-based policy needed, but the execution role must grant read permissions.
Invocation types further branch retry behavior:
- Synchronous (API Gateway, ALB,
RequestResponseSDK): errors returned immediately; the caller owns retries. - Asynchronous (S3, SNS, EventBridge): Lambda buffers on an internal queue and retries twice with delays on failure, then sends to a DLQ or on-failure destination.
- SQS event source mapping: SQS keeps redelivering until
maxReceiveCounttriggers a move to the source queue’s DLQ. - Kinesis / DynamoDB Streams: retries a single shard batch until success or expiry, blocking the shard in the meantime — a stuck batch stalls all downstream processing for that partition.
Because retries are baked into every layer, idempotency is mandatory, not optional. An SQS visibility timeout expiring during a slow write or an asynchronous retry after a downstream 5xx will produce duplicates. The canonical pattern uses a deterministic key and DynamoDB conditional write:
def handler(event, context):
msg_id = event['Records'][0]['messageId']
try:
ddb.put_item(
TableName='processed',
Item={'id': {'S': msg_id}, 'ttl': {'N': str(ttl)}},
ConditionExpression='attribute_not_exists(id)')
except ddb.exceptions.ConditionalCheckFailedException:
return # already processed
process(event)
The AWS Lambda Powertools @idempotent decorator implements exactly this pattern with DynamoDB backing.
Decoupling with SQS and SNS
Wiring high-fanout push sources directly to Lambda is fragile. S3 event notifications, for instance, are synchronous per event and subject to Lambda’s concurrency ceiling; if invocations exceed available concurrency during a bursty upload (a marketing campaign dropping thousands of documents in seconds), S3’s retry window is short and events can effectively be dropped. The remedy is a buffer:
| Pattern | When to use |
|---|---|
| S3 → Lambda direct | Low, predictable event rate; idempotent processing |
| S3 → SQS → Lambda | Bursty workloads; need retry/DLQ; downstream rate limits |
| S3 → SNS → multiple SQS | Fan-out to several independent consumers |
| S3 → EventBridge → many targets | Cross-account routing; content-based filtering |
SNS is pub/sub: one publish, many subscribers (SQS, Lambda, HTTPS, email). Message filtering is attribute-based. SQS is a durable point-to-point queue retaining messages up to 14 days. The workhorse combination is SNS → SQS fan-out, giving each consumer its own buffered queue for independent scaling and replay.
For strict ordering (e.g., orders per customer processed sequentially), use an SQS FIFO queue with MessageGroupId set to the ordering key. Messages within a group are delivered in order; different groups process in parallel. Standard SQS is best-effort ordering only.
The canonical decoupled ingestion pattern uses Gateway’s direct SQS integration to absorb bursts and a rate-limited processor:
Resources:
OrdersQueue:
Type: AWS::SQS::Queue
Properties:
FifoQueue: true
ContentBasedDeduplication: true
RedrivePolicy:
deadLetterTargetArn: !GetAtt OrdersDLQ.Arn
maxReceiveCount: 5
ProcessorFunction:
Type: AWS::Lambda::Function
Properties:
ReservedConcurrentExecutions: 20 # cap the DB write rate
Mapping:
Type: AWS::Lambda::EventSourceMapping
Properties:
EventSourceArn: !GetAtt OrdersQueue.Arn
FunctionName: !Ref ProcessorFunction
BatchSize: 10
The reserved concurrency is deliberate — it caps how fast the database sees writes, so the queue (not RDS) absorbs the burst. Failed messages route to a DLQ after maxReceiveCount for offline inspection.
EventBridge: Rules, Input Transformation, and API Destinations
EventBridge is a schema-aware event bus with rich JSON pattern matching, SaaS partner sources, schema registry, and archive/replay. Rules match on event patterns and fan out to 30+ target types (Lambda, Step Functions, SQS, Kinesis, ECS, Firehose), with content-based filtering on message body — not just attributes as in SNS:
{
"source": ["tenant.energy"],
"detail-type": ["UsageReported"],
"detail": { "kWh": [{ "numeric": [">", 100] }] }
}
A rule can have up to five targets, each receiving either the raw event or a transformed subset. Input transformers enforce loose coupling: an InputPathsMap extracts JSON paths from the event, and an InputTemplate reshapes them into exactly what the target expects:
EventPattern:
source: ["com.acme.orders"]
detail-type: ["OrderPlaced"]
Targets:
- Arn: !GetAtt PaymentValidator.Arn
InputTransformer:
InputPathsMap:
orderId: "$.detail.orderId"
amount: "$.detail.total"
card: "$.detail.payment.cardToken"
InputTemplate: |
{"orderId": <orderId>, "amount": <amount>, "cardToken": <card>}
Each validation Lambda receives only what it needs; the address validator never sees the card token. This is decisively superior to a monolithic Lambda that receives the full event and branches internally — a monolith concentrates IAM permissions (one role must hold every downstream permission), inflates the blast radius of a bug, couples deployment cadence, prevents per-responsibility memory/timeout tuning, and forces the entire function to scale to the noisiest branch’s rate.
API destinations invert direction: EventBridge calls an external HTTPS endpoint. Paired with a connection that stores Basic, API-key, or OAuth credentials in Secrets Manager, this is the serverless way to notify a third-party SaaS when, say, an AWS Batch job succeeds — no Lambda required. EventBridge captures the state-change event, a rule matches JobSucceeded, and the API destination target POSTs to the vendor with credentials injected from the connection.
Scheduled rules (cron/rate expressions) or the newer EventBridge Scheduler replace heartbeat EC2 instances for periodic tasks — nightly reports, hourly cache refresh.
Choose EventBridge over SNS when filtering is on message body content (not just attributes), when new consumers must attach later without producer changes, or when routing spans accounts or SaaS sources. Choose SNS when the fan-out is simple attribute-filtered notification to a stable set of subscribers.
Step Functions: Orchestration and Distributed Map
When a workflow has more than a couple of steps, branching, retries, human approval, or long waits, embedding that logic across chained Lambdas becomes unmaintainable. Step Functions externalizes the state machine in Amazon States Language.
- Standard workflows: up to one year, exactly-once semantics, full execution history,
.waitForTaskTokenfor human/external gates. Order fulfillment, ETL, approval flows. - Express workflows: up to five minutes, at-least-once, high volume, cheap per execution. Short synchronous orchestrations behind API Gateway; IoT and streaming transforms.
Every task should declare explicit Retry and Catch:
"ValidatePayment": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {"FunctionName": "PaymentValidator", "Payload.$": "$"},
"Retry": [{
"ErrorEquals": ["Lambda.ServiceException", "Lambda.TooManyRequestsException"],
"IntervalSeconds": 2, "MaxAttempts": 4, "BackoffRate": 2.0
}],
"Catch": [{"ErrorEquals": ["PaymentDeclined"], "Next": "RefundStep"}],
"Next": "ShipOrder"
}
Parallel and Map states run branches concurrently and aggregate results — a clean fit for order systems with independent validators (address, inventory, payment). State between steps flows through the execution’s JSON document, eliminating any need for a shared database used solely for workflow coordination.
The .waitForTaskToken pattern pauses execution until an external actor calls SendTaskSuccess with the token — the canonical answer when a workflow spans Lambdas, EC2, containers, on-prem systems, and requires manual approval with minimal ops overhead:
"ManagerApproval": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish.waitForTaskToken",
"Parameters": {
"TopicArn": "arn:aws:sns:us-east-1:111:approvals",
"Message": { "TaskToken.$": "$$.Task.Token", "OrderId.$": "$.orderId" }
},
"Next": "Fulfill"
}
Distributed Map extends the standard Map state to process up to 10,000 parallel child executions and can iterate directly over objects in an S3 bucket or rows in a CSV/JSONL file, with automatic batching, checkpointing, and failure tolerance. For thousands of semistructured S3 objects, it is the most operationally efficient option — point it at a prefix, define the per-item task, and Step Functions handles fan-out, MaxConcurrency, retries, and result aggregation:
{
"Type": "Map",
"ItemReader": {
"Resource": "arn:aws:states:::s3:listObjectsV2",
"Parameters": { "Bucket": "raw-events", "Prefix": "2024/" }
},
"MaxConcurrency": 1000,
"ItemProcessor": {
"ProcessorConfig": { "Mode": "DISTRIBUTED", "ExecutionType": "STANDARD" },
"StartAt": "ProcessObject",
"States": { "ProcessObject": { "Type": "Task", "Resource": "arn:aws:lambda:...:function:ProcessOne", "End": true } }
}
}
Rebuilding the same on SQS or EventBridge requires custom bookkeeping for completion, retries, and result assembly.
Step Functions vs. EventBridge: Step Functions is right when you own the sequence and outcome — state, branches, retries, approvals. EventBridge is right when producers don’t know or care who consumes, and consumers attach independently.
S3 Event Notifications
For near-real-time processing of uploads, configure S3 event notifications on s3:ObjectCreated:* (or a specific variant such as Put, Post, CompleteMultipartUpload) with a Lambda target — subject to the burst caveats above. Guardrails:
- Set reserved concurrency to protect a downstream database.
- Enable a DLQ or on-failure destination for poison messages.
- Route S3 events through EventBridge when multiple consumers need the same events. Direct S3 notification configurations are limited in count and expressiveness; EventBridge fan-out with per-target input transformers scales far better.
Streaming: Kinesis Data Streams vs. Firehose
Kinesis Data Streams (KDS) is a sharded, ordered, replayable log with 24-hour to 365-day retention. Order is preserved per shard, keyed by partition key — critical for per-device or per-tenant aggregation. Multiple consumers read independently (enhanced fan-out for isolated throughput per consumer). Choose KDS when you need ordered replay, multiple independent consumers, or high throughput per shard.
Kinesis Data Firehose is a fully managed delivery stream to S3, Redshift, OpenSearch, or Splunk with built-in buffering (60 s or 1–128 MB), optional Lambda transformation, compression (GZIP, Snappy), and Parquet/ORC conversion. No shards to manage. Firehose is the low-ops choice when custom consumers and replay aren’t needed — just landing near-real-time data.
The canonical near-real-time analytics pipeline is producers → KDS → Firehose → S3 (Parquet) → Athena/QuickSight, with optional Lambda enrichment in Firehose.
AWS Transfer Family for Managed SFTP
When partners require SFTP, FTPS, or FTP into or out of S3 or EFS, AWS Transfer Family provides a managed, multi-AZ endpoint speaking the protocol clients already use. Authentication supports service-managed users, SSH keys, or custom IdPs via API Gateway/Lambda. Files land directly in S3 with SSE and lifecycle policies applied; IAM scope-down policies restrict each user to a specific prefix.
Building this yourself on EC2 requires OpenSSH hardening, patching, HA across AZs, key rotation, and log shipping — all of which Transfer Family absorbs. Choose it whenever the requirement is “partners send us files over SFTP” and you want the files in S3 with minimal operational overhead.
Composing a Canonical Serverless Pattern
A low-overhead ingestion design for per-tenant hourly metrics: sensors POST to API Gateway (HTTP API, regional) → Lambda validates and publishes to EventBridge → rules route to a DynamoDB writer Lambda (tenant ID as partition key, hour-bucket as sort key) and, in parallel, to Firehose → S3 (Parquet) for analytics. New consumers attach as additional EventBridge rules without touching producers — the extensibility requirement SNS-only would not satisfy as cleanly. Where sustained throughput and ordering matter (billing reconciliation, financial event streams), replace EventBridge with Kinesis Data Streams and use enhanced fan-out for independent consumers.
Trap Catalogue: Why the Common Wrong Patterns Fail
NAT instance/gateway for AWS-service traffic from private-subnet Lambdas. NAT instances tie throughput to one EC2 NIC and are a SPOF; NAT gateways bill per GB. Both are unnecessary for AWS-service destinations. Use gateway endpoints for S3/DynamoDB, interface endpoints for everything else.
Ignoring cold starts on latency-critical APIs. On-demand only provisions containers when a request arrives, so bursts miss SLAs. Size provisioned concurrency to p95 burst; use SnapStart for Java workloads without hard SLAs.
Monolithic Lambda receiving a full event and internally branching. Violates least privilege (one role holds every downstream permission), couples deployment cadence, prevents per-responsibility tuning, and scales the whole function to the noisiest branch’s rate. Split by responsibility; glue with EventBridge or Step Functions.
Direct DB connections from many Lambdas. Total connections equal concurrent invocations because containers don’t share pools. Fix with RDS Proxy (pooling), reserved concurrency (rate cap), or SQS (buffering).
Synchronous Lambda for bursty ingestion. Exceeding concurrency returns 429 to API Gateway and 5xx to clients; S3 direct notifications during bursts silently drop events. Insert SQS, or use Gateway → SQS direct integration.
Public Lambda function URLs with AuthType: NONE and no in-function signature validation. Anonymous compute on the public internet. Use AWS_IAM for AWS callers or validate signatures for third-party webhooks.
Custom Lambda authorizer where a built-in works. Adds latency, another patchable function, and a code path where signature-check bugs silently permit access. Prefer IAM authorization for AWS principals; Cognito authorizer for user pools.
Wrong ACM certificate Region for custom domains. Edge-optimized endpoints require the cert in us-east-1; regional endpoints in the API’s own Region.
Missing AWS::Lambda::Permission for push sources (EventBridge, S3, SNS). Events match rules but invocations are silently denied. Distinct from the execution role — this governs who may call the function, not what the function may do.
Missing idempotency. Every layer retries — asynchronous invocations, SQS redelivery on visibility-timeout expiry, Kinesis batch retries. Without a deterministic dedup key and conditional write, duplicates are inevitable.
← Previous: Containers · All domains · Next: Storage →
Practice Serverless questions → · Timed practice on ExamRoll.io →
Pass the whole exam — not just this question
You found this answer. Get every verified question and explanation in one place, and save hours of prep. Free to start.
Pass your exam →