The Ops Atlas xOps School

Cloud & Infrastructure Niche usage Sources verified

ServerlessOps

ServerlessOps is the practice of running serverless compute (functions and request-billed containers) in production: owning event retries and duplicates, concurrency, safe deployments and per-invocation cost. The label itself is a niche practitioner term, used mostly between 2016 and 2020.

Key takeaways

  • The label is niche and mostly historical: an O'Reilly report (2016), DevOpsDays talks (2017–2019) and a consultancy that stopped consulting in 2019. The practice it names, operating serverless compute in production, is real and documented by every major provider.
  • Assume every event can arrive twice, arrive late, or be dropped after its last retry. AWS, Microsoft and Google all document at-least-once delivery for their event triggers.
  • An idempotency record in your own table catches redeliveries, but a timeout after a side effect still repeats that side effect unless the downstream system also accepts an idempotency key, and only while it still remembers that key.
  • For every event path, know who retries, how often, for how long, and where the event goes after the last attempt. Most of those retries are platform settings, not your code.
  • Concurrency is roughly requests per second × duration, so a slow dependency multiplies both your concurrency and your bill.
  • You pay per attempt: retries, duplicates and waiting on slow calls are all billed. Track cost per business event across the whole path (function, idempotency table, queue), not cost per function.
  • Serverless fits spiky, event-driven, short units of work. Steady high-volume or long-running work often fits containers, jobs or a workflow engine better.

ServerlessOps is the operations side of serverless computing: running functions-as-a-service and request-billed containers (AWS Lambda, Azure Functions, Google Cloud Run) in production so that events are processed correctly despite retries and duplicates, scaling stays within platform and downstream limits, deployments are safe, and the bill follows business value. The provider runs servers, patching and capacity; you own failure semantics, configuration, concurrency, telemetry and cost. As the CNCF Serverless Working Group put it, serverless does not mean “that operations engineers are no longer required”.[1]

In one line: the platform decides when your code runs and how often it retries; ServerlessOps makes that safe.

For developers owning functions in production, platform engineers setting guardrails, SREs on call and architects deciding whether serverless fits. Beginners can stop after the glossary; engineers should do the labs.

Item Summary
Term status Niche, mostly historical (2016–2020), no formal definition; the underlying practice is mainstream.
Goal Event-driven systems that neither lose nor double-apply work, stay within limits, and cost a predictable amount per business event.
In scope Invocation models; retries, DLQs and failure destinations; idempotency; batch and stream failures; concurrency, quotas, cold starts; timeouts; safe deployment; telemetry; per-invocation cost.
Out of scope The NoOps question, cloud governance, workflow engines, broker design, telemetry pipelines and spend governance: see how it relates to other Ops.
Prerequisites Basic cloud vocabulary (CloudOps); HTTP and JSON; one programming language.
Key outputs Event-path design records, idempotent handlers, alarms on what actually fails, a DLQ runbook, a concurrency budget, a cost per event.

Serverless removes host work and adds failure modes that stay invisible until production:

Without deliberate serverless operations What the platforms document
Customers are charged twice Lambda event source mappings “process each event at least once, and duplicate processing of records can occur”;[5] the async queue can deliver an event twice.[6]
Events disappear quietly Lambda discards an async event once it expires or fails every attempt;[6] EventBridge drops one after 24 hours and up to 185 attempts unless a DLQ is set.[7]
One bad record stops a whole stream Kinesis mappings default to retrying a failing batch “until the record expires”, which pauses the shard.[8][9]
A traffic spike throttles unrelated functions Functions share a Regional concurrency pool, 1,000 by default.[10][11]
The bill grows with no new customers Every attempt is billed, initialization included; a function writing to its own trigger can loop.[12][13]

The usual cost is not a visible outage but wrong data found later: double payments, missing orders, a backlog nobody owns.

  • Not NoOps. Whether a team can run with little operations staff is evaluated on NoOps; this page teaches the mechanisms that remain.
  • Not a product, method or certification. ServerlessOps was also Tom McLaughlin’s consultancy, which stopped consulting in July 2019.[14]
  • Not only Lambda. The questions apply to Azure Functions, Cloud Run and Knative too.
  • Not workflow orchestration or messaging design. Those belong to WorkflowOps and IntegrationOps; this page covers the function’s side of those contracts.
  • Editorial distinction: the Atlas uses “ServerlessOps” for operating request-billed compute; no standard defines it.

Where the label comes from and where it sits

Link to section “Where the label comes from and where it sits”
When Event Why it matters
Nov 2014 AWS Lambda launches[1] Function-as-a-service becomes mainstream
Nov 2016 Michael Hausenblas, Serverless Ops, a 62-page O’Reilly report[2][15] Earliest book-form use found in this research
2017 DevOpsDays Nashville talk “Serverless Ops: What to do when the server goes away” (Tom McLaughlin)[3] The label questions the operations role
Nov 2017 AWS Well-Architected Serverless Applications Lens first published (major update July 2022), written partly for “operations team members”[16] Provider guidance grows alongside the label
2018 CNCF Serverless Whitepaper v1.0;[1] McLaughlin’s essay (“Serverless operations is not NoOps”);[17] his consultancy and a USENIX LISA closing keynote (per its page)[4] The label’s busiest year (this page’s reading)
2019 Berkeley’s Cloud Programming Simplified;[18] the DevOpsDays talk is retitled “Serverless DevOps”;[19] the consultancy stops consulting[14] The label fades into “DevOps” and plain “operations”
2024–2026 CloudEvents (January 2024)[20] and Knative (September 2025)[21] graduate in CNCF; AWS renames classic functions “Event Functions” and adds Web Functions and MicroVMs (as of October 2026)[22] Platforms move; failure semantics stay
Term Meaning Why it matters
Invocation model How an event reaches code: synchronous (caller waits), asynchronous (platform queues it), poll-based (platform reads a queue or stream) It decides who retries
Event source mapping (ESM) Lambda resource that polls a queue or stream and invokes the function with batches[5] Owns batching, retries and scaling
Execution environment, cold start The sandbox that runs a function, reused (“warm”) or created (“cold”); creation is billed and typically affects under 1% of Lambda invocations[23] State and /tmp may survive; latency
Concurrency Executions in flight: average requests per second × average duration in seconds[11] The real capacity unit
Reserved / provisioned concurrency Reserved: a floor and ceiling for one function; provisioned: environments initialized in advance[11] Isolation versus latency
Throttle (HTTP 429) A request refused because a concurrency limit is reached[24] Queues hide throttles as delay
GB-second Memory (GB) × run time (s), Lambda’s duration billing unit[12] Slow code costs more
Version / alias Version: an immutable snapshot of a function; alias (such as live): a pointer to one Rollback moves a pointer
Canary (traffic shifting) An alias sends a share of traffic to a new version, then the rest if alarms stay quiet[25] Bad releases reach few events
Visibility timeout Time an SQS message stays hidden after being received[26] Too short: redelivery mid-processing
maxReceiveCount / redrive Receives allowed before SQS moves a message to the dead-letter queue[27] Too low: transient errors fill the DLQ
Dead-letter queue (DLQ) / failure destination Where an event goes after the last attempt: an async DLQ gets the event, an async destination an invocation record,[28] a stream mapping’s SQS or SNS destination only metadata[29] Without one, failed events are dropped
Shard, sequence number, iterator age A stream’s ordered partition; a record’s position in it; the age of the last record handed to the function[30] Rising age means a stuck shard
Partial batch response The function reports which records failed; SQS retries only those, a stream everything from the first failure on[31][32] Fewer repeats, not none
Poison message A record that fails every attempt Stalls a stream or fills a DLQ
Idempotency key A stable identifier that makes a repeated request have no further effect[33] The core defence against duplicates

Mental model: three questions for every event path. (1) Who retries, how often and for how long? (2) Where does the event go after the last attempt, and who looks there? (3) What if the handler runs twice, or stops half-way? A trigger without three answers is not ready for production.

Page Shared ground This page’s focus Go there for
NoOps Managed services shift work The mechanisms that remain Whether “less ops” fits your organisation
CloudOps Quotas, Regions, provider events Function-level limits and settings Account structure, guardrails, provider health
WorkflowOps Retries, idempotency Single invocations and batches State machines, durable execution, sagas
IntegrationOps Queues, DLQs, replay Lambda-side mechanics: partial batch responses, timeout ratios, maximum concurrency, pausing a mapping Brokers, schema evolution, broker-agnostic replay and DLQ governance
ObservabilityOps Metrics, logs, traces Which serverless signals to alarm on Telemetry pipelines and tooling
FinOps Cost The per-invocation cost model Budgets, allocation, commitments
ContainerOps Request-billed containers Platform-managed scaling Image build and runtime limits
Figure 1. Where ServerlessOps sits among neighbouring pages
NoOpsis less operations realistic?CloudOpsaccounts, quotas, RegionsServerlessOpsfailure semantics,concurrency,safe deploys, cost perinvocationWorkflowOpsstate machinesIntegrationOpsbrokers and replayObservabilityOpstelemetry pipelinesFinOpsspend governance
Diagram source (Mermaid)
flowchart LR
  N["`NoOps
  is less operations realistic?`"]:::neutral
  C["`CloudOps
  accounts, quotas, Regions`"]:::neutral
  S["`ServerlessOps
  failure semantics, concurrency,
  safe deploys, cost per invocation`"]:::core
  W["`WorkflowOps
  state machines`"]:::neutral
  I["`IntegrationOps
  brokers and replay`"]:::neutral
  O["`ObservabilityOps
  telemetry pipelines`"]:::neutral
  F["`FinOps
  spend governance`"]:::neutral
  N --> S
  C --> S
  S --> W
  S --> I
  S --> O
  S --> F
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029

Text equivalent: NoOps (is less operations realistic?) and CloudOps (accounts, quotas, Regions) feed ServerlessOps (failure semantics, concurrency, safe deploys, cost per invocation), which hands work to WorkflowOps, IntegrationOps, ObservabilityOps and FinOps.

This charter is this Atlas’s proposal; no official manifesto exists.

Mission: every event is processed exactly once in effect, within a known time, at a known cost, by a function someone owns.

  1. Every handler is idempotent on a business key. AWS, Google and Microsoft document at-least-once delivery.[5][34][35]
  2. Every asynchronous path ends somewhere visible: a DLQ or destination with an owner, an alarm and a maximum age.
  3. Retry at one layer. Marc Brooker calls retrying “at a single point in the stack” Amazon’s best practice for “low-cost control-plane and data-plane operations”; five layers of three retries multiply load 243 times.[36]
  4. Concurrency is a budget. Cap what reaches fragile dependencies; reserve what critical functions need.[37][11]
  5. Timeouts are nested. Downstream call < function timeout; SQS visibility timeout ≥ 6 × function timeout.[38]
  6. Cost is measured per business event, including retries and duplicates.
  7. Releases go gradually to a version behind an alias, with alarms that see partial failures.[25]

Non-goals: zero cold starts without a latency target; trusting platform exactly-once claims.

Fundamentals: invocation models and platforms

Link to section “Fundamentals: invocation models and platforms”

Who retries depends on how the event arrives. Lambda does not retry a synchronous invocation, whose code “might have run completely, partially, or not at all”.[9] Asynchronous events get two more attempts on function errors and up to six hours of retries for throttles, then are discarded unless a DLQ or failure destination exists.[6][39] Event source mappings retry the whole batch until it succeeds or expires; for queues, visibility timeout and redrive policy decide the rest.[5][9]

Figure 2. Three invocation models and where each one retries
Poll-basedQueue orstreamPoller readsa batchFunctionQueue: DLQ.Stream: shard waitsAsynchronousS3, SNS,EventBridgeInternal queue,up to 6 hFunction, up to2 retriesDLQ or failuredestinationSynchronousClient or APIcallsFunctionError returned:caller decides
Diagram source (Mermaid)
flowchart LR
  subgraph SY["Synchronous"]
    direction TB
    A1["`Client or API
    calls`"] --> A2["Function"] --> A3["`Error returned:
    caller decides`"]
  end
  subgraph AS["Asynchronous"]
    direction TB
    B1["`S3, SNS,
    EventBridge`"] --> B2["`Internal queue,
    up to 6 h`"] --> B3["`Function, up to
    2 retries`"] --> B4["`DLQ or failure
    destination`"]
  end
  subgraph PO["Poll-based"]
    direction TB
    C1["`Queue or
    stream`"] --> C2["`Poller reads
    a batch`"] --> C3["Function"] --> C4["`Queue: DLQ.
    Stream: shard waits`"]
  end
  SY ~~~ AS ~~~ PO
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  class A3,B4,C4 risk

Text equivalent: three columns. Synchronous: on error, the caller decides whether to retry. Asynchronous: S3, SNS or EventBridge events wait in an internal queue for up to six hours; the function gets up to two retries; failures go to a DLQ or failure destination, or are dropped. Poll-based: a poller reads batches; failed queue messages end in a DLQ, while a failing stream batch makes its shard wait.

The platforms differ in limits but share the semantics (as of October 2026):

Aspect AWS Lambda (Event Functions) Azure Functions (Flex Consumption) Google Cloud Run
Maximum run time 900 s; 5,400 s for async and most ESM invocations on Managed Instances[10] 30 min default, unbounded maximum; HTTP responses within 230 s[40] Request timeout 5 min default, 60 min maximum[41]
Requests per instance One at a time by default[22] Depends on trigger and instance size[42] Default 80 × vCPUs for new services via gcloud or Terraform (80 in the console); maximum 1,000[43]
Event delivery At least once[5][6] At least once (Event Hubs trigger)[35] At least once (Eventarc Standard)[34]
Retries Async: 0–2 retries, up to 6 h; ESM: until success or expiry[39][5] Per trigger: binding or runtime retry policies[44] Backoff 10–600 s, 24 h retention (Eventarc)[34]
Billing unit Requests plus GB-seconds, 1 ms rounding[12] Executions plus GB-seconds; minimum 1,000 ms, then 100 ms[42] Request-based (default) or instance-based[45]
Warm capacity Provisioned concurrency; SnapStart[11][46] Always-ready instances, no free grants[42] Minimum instances, billed, “best-effort”[47]

The team that writes a function owns its event contract. A common split (R = responsible, A = accountable, C = consulted, I = informed):

Activity Product team Platform or cloud team SRE / on-call FinOps Security
Event contract, idempotency key, failure destination A/R C C – I
DLQ triage and redrive A/R I R (out of hours) – I
Concurrency budgets and quota increases R A C I –
Deployment guardrails (aliases, canary, alarms) R A C – I
Runtime upgrades and dependency patching A/R C I – C
Cost per business event review R C – A –
Execution roles and resource policies R C – – A

With a few functions, developers on call often works; with hundreds, Hausenblas suggested “you probably need some on-call strategy”.[48] Platform teams own shared defaults (templates, lint rules, alarm baselines), not functions.

  • Day 0 (design): fill in the event-path design record and a cost estimate (Lab 2).
  • Day 1 (release): deploy through an alias with a canary; load-test to peak; inject a duplicate and a poison message before real traffic does.
  • Day 2 (operate): triage the DLQ, redrive after fixes, review cost per event, keep runtimes current. AWS gives at least 180 days’ notice of a runtime deprecation, but not for container images, and afterwards “may no longer apply security patches”.[49]

Governance is mostly defaults as code plus reviews (this Atlas’s proposal):

  • Policy checks in CI: every asynchronous function has a failure destination; every SQS mapping sets ReportBatchItemFailures and a maximum concurrency (or, in provisioned mode, a maximum poller count[37]); visibility timeout ≥ 6 × function timeout; every DLQ has an alarm and an owner tag.
  • Weekly DLQ review by the owning team; monthly cost and quota reviews with FinOps and CloudOps; a quarterly game day (duplicate storm, poison message, throttled dependency).
  • Escalation: provider incidents follow IncidentOps.

The reference design is an order-payment path, where a duplicate is expensive and visible.

Figure 3. Reference architecture for an idempotent order consumer
Idempotency-Key: order_idafter 5 receivesafter a fix: rate-limited redriveClientHTTPS API: authenticated,throttledIntake function: validate,enqueue, return 202Orders queue: visibility 180 s,maxReceiveCount 5Event source mapping: batch10, max concurrency 20,partial batch responsesConsumer function, alias live,timeout 30 sIdempotency table: keyorder_id, expiry 15 daysPayment provider APIDead-letter queue, 14-dayretentionAlarms: failed records, errors,DLQ depth
Diagram source (Mermaid)
flowchart TB
  U["Client"] --> G["HTTPS API: authenticated, throttled"]
  G --> F1["Intake function: validate, enqueue, return 202"]
  F1 --> Q["Orders queue: visibility 180 s, maxReceiveCount 5"]
  Q --> E["Event source mapping: batch 10, max concurrency 20, partial batch responses"]
  E --> F2["Consumer function, alias live, timeout 30 s"]
  F2 <--> T["Idempotency table: key order_id, expiry 15 days"]
  F2 -->|"Idempotency-Key: order_id"| P["Payment provider API"]
  Q -->|"after 5 receives"| D["Dead-letter queue, 14-day retention"]
  D -->|"after a fix: rate-limited redrive"| Q
  F2 -.-> M["Alarms: failed records, errors, DLQ depth"]
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  class F2,T,E core
  class D,P risk
  class U,G,F1,Q,M neutral

Text equivalent: a client calls an authenticated, throttled HTTPS API; an intake function validates the order, queues it and returns 202. An event source mapping (batches of ten, maximum concurrency 20, partial batch responses) invokes the consumer through its “live” alias; the consumer claims each order in an idempotency table and calls the payment provider with the order ID as key. Messages failing five receives move to a 14-day DLQ, redriven slowly after a fix. Alarms watch failed records, errors and DLQ depth.

Why each element is there:

  • Queue between API and payment: a fast answer for the caller, a controlled rate for the provider; the order is “accepted”, not “paid”, so the client needs a status endpoint. The API authenticates and throttles callers: every request is billed.
  • Maximum concurrency 20, reserved 25: reserved concurrency should be at least the sum of maximum concurrency settings, or Lambda “might throttle your messages”;[37] it comes from the shared pool, at most unreserved concurrency minus 100.[50]
  • Visibility timeout 180 s = 6 × 30 s, maxReceiveCount 5: the minimums AWS recommends, giving Lambda “a few chances to retry”.[38]
  • DLQ retention 14 days, longer than the source’s 4: standard-queue expiry counts from the original enqueue time, so a shorter DLQ deletes evidence.[27][51] A redrive restarts retention and assigns new message IDs,[52] so redriving into the source queue is safe.
  • Idempotency expiry 15 days, so a message redriven on day 10 is still recognised locally (this page’s rule; Powertools defaults to one hour[53]). The provider may forget sooner: see keys expire.

Alternatives. A FIFO queue prevents duplicate sends only within a five-minute deduplication interval;[54] Pub/Sub exactly-once delivery works only for pull subscriptions within one Region;[55] neither makes side effects idempotent. A multi-step payment with compensation belongs in a workflow engine (WorkflowOps).

End-to-end workflow: happy path and exception path

Link to section “End-to-end workflow: happy path and exception path”

Happy path. The consumer claims each order_id with a conditional write (IN_PROGRESS, lease = the invocation’s remaining time), calls the provider with Idempotency-Key: order_id, records COMPLETE, and returns an empty batchItemFailures list, so Lambda deletes the batch.[31][56]

Exception path. The dangerous case is a failure after the side effect, before the acknowledgement.

Figure 4. A timeout after the charge, and why the downstream key matters
Payment APIConsumer and its tableOrders queueclaim order-42: IN_PROGRESS, leasetimes out before success is recordedlease expired: claim againmark COMPLETErecord already COMPLETEorder-42, attempt 1charge, key order-42order-42 again after 180 scharge, same keyoriginal result, no new chargereturns success, mapping deletes messagebroker duplicate of order-42returns success, no side effect
Diagram source (Mermaid)
sequenceDiagram
  participant Q as Orders queue
  participant F as Consumer and its table
  participant P as Payment API
  Q->>F: order-42, attempt 1
  Note over F: claim order-42: IN_PROGRESS, lease
  F->>P: charge, key order-42
  Note over F: times out before success is recorded
  Q->>F: order-42 again after 180 s
  Note over F: lease expired: claim again
  F->>P: charge, same key
  P-->>F: original result, no new charge
  Note over F: mark COMPLETE
  F-->>Q: returns success, mapping deletes message
  Q->>F: broker duplicate of order-42
  Note over F: record already COMPLETE
  F-->>Q: returns success, no side effect

Text equivalent: the consumer claims order 42 in its table, charges with key order-42, and times out before recording success. After 180 seconds the queue redelivers; the lease has expired, so the consumer claims again and repeats the charge with the same key. The payment API returns the original result without charging; the consumer marks the record complete, returns success, and the mapping deletes the message. A later broker duplicate finds the record complete and does nothing.

Without the downstream key the second charge would be real: the local record cannot know whether the first call reached the provider. Hence Amazon’s preferred “unique caller-provided client request identifier”[33] and Google’s advice to use the event ID as the idempotency key for external APIs.[34] Platform IDs catch only redeliveries; prefer a business key like order_id. The Idempotency-Key header is a convention whose IETF draft expired (last revised October 2025).[57]

Keys expire. Stripe’s API v1 may prune a key once it is 24 hours old, then treats a reuse as a new request; it also rejects a reused key whose parameters differ.[58] Replay is safe only within the shorter of the two retentions: before a late redrive, or one after a fix that changes what is sent, look each order up at the provider and skip those already charged.

Practice Goal Owner Verification
Idempotency on a business key, sent downstream No double effects Product team Same event twice, one effect (Lab 1)
Partial batch responses Retry less than the whole batch Product team NumberOfMessagesDeleted never drops to 0 under partial failure[31]
Failure destination on every async path Nothing dropped silently Product team AsyncEventsDropped and DLQ alarms[30]
Timeout budget Retries do not overlap live work Product team Downstream timeout < function timeout ≤ visibility timeout ÷ 6
Concurrency budget Protect dependencies; isolate critical functions Platform + product Peak ≤ 70% of the cap (Lab 2’s starting threshold)
Stream poison handling One record cannot stall a shard Product team IteratorAge alarm; bisect and maximum record age set[8][32]
Gradual deployment Bad versions roll back automatically Platform team Canary alarms include failed records[25]
Recursion and cost guards Loops and runaway retries caught Platform + FinOps Concurrency and billing alarms; recursion detection on[13]

Timeouts in practice. Lambda’s default is 3 seconds; AWS warns against timeouts close to the average duration and advises testing “at the upper bounds” of expected data.[59]

Streams are different from queues. A Kinesis or DynamoDB stream mapping keeps order per shard, so by default a failing batch is retried until it succeeds or expires (MaximumRetryAttempts and MaximumRecordAgeInSeconds default to −1, infinite).[8] Set a finite retry count and record age, turn on BisectBatchOnFunctionError, report item failures, and add an on-failure destination; Lambda then splits the batch at the failing sequence number and retries only the rest.[32] An SQS or SNS destination does not receive the failed records, only their shard and sequence numbers; they must be read back from the stream “before they expire and are lost”, while an S3 destination gets the full record.[29] DynamoDB Streams keeps records 24 hours,[60] so alarm on destination inflow and repair within that window.

Source Kind Use it for
CloudEvents specification (CNCF) Normative specification Event envelopes; source + id “is unique for each distinct event”, and consumers “MAY assume” identical pairs are duplicates[20]
OpenTelemetry FaaS span conventions Specification, status “Development” Span attributes such as faas.trigger and faas.coldstart[61]
Idempotency-Key HTTP header Expired IETF draft A convention, not a standard[57]
AWS Well-Architected Serverless Applications Lens (2017; major update 2022) Vendor framework AWS design reviews[16]
Amazon Builders’ Library Vendor engineering practice Idempotent APIs and retry discipline[33][36]

None is mandatory; CloudEvents, the only normative one, defines the event format, not delivery guarantees.

Categories first, brands second (as of October 2026):

Category Representative options Strengths Watch-outs
Packaging and deployment AWS SAM; Serverless Framework; AWS CDK or Terraform (InfraOps) SAM adds aliases, canaries and alarms in a few lines[25] Serverless Framework v4 is paid above $2 million annual revenue; v3 stays open source but has had no security updates since early 2025[62]
Runtime utilities Powertools for AWS Lambda (Python, TypeScript, Java, .NET) Idempotency, batch and metrics utilities[53][63] Default idempotency expiry is one hour; choose the key yourself[53]
Memory and cost tuning AWS Lambda Power Tuning (open source) Runs the function at several memory sizes; charts cost and speed[64] It makes “real HTTP requests, SDK calls”: never point it at side effects[64]
Tracing and telemetry AWS X-Ray; OpenTelemetry SDKs and collectors End-to-end traces across queues Lambda’s X-Ray sampling is fixed at “1 request per second and 5 percent of additional requests”;[65] request-billed Cloud Run may starve background agents of CPU[45]
Serverless on your own clusters Knative (CNCF graduated) Scale-to-zero on your Kubernetes[21] You operate the cluster too (KubernetesOps)

Select on: event-path answers visible in review; aliases, canaries and alarms expressible; licence and exit path.

Automate safe actions (pausing a mapping, lowering maximum concurrency, ticketing DLQ samples); keep human approval for money-moving redrives. Language models can cluster DLQ errors, but DLQ messages carry full payloads: treat them as production data (PrivacyOps). The core loop needs no AI.

Assumptions: AWS, one Region, Python 3.13 (deprecation scheduled for 30 June 2029 as of October 2026[49]), Powertools for AWS Lambda, and a payment provider that honours an idempotency key.

Example status: YAML syntax-checked by the Atlas validator; not linted with cfn-lint; not deployed. Long-form intrinsics (Ref:, Fn::GetAtt:) keep it parseable as plain YAML.

AWSTemplateFormatVersion: "2010-09-09"
Transform:
  - AWS::LanguageExtensions          # a changed PaymentApiUrl then publishes a version
  - AWS::Serverless-2016-10-31
Description: Idempotent order consumer, partial batch responses, DLQ (illustrative)

Parameters:
  PaymentApiUrl:
    Type: String
    Default: https://payments.example.com/v1/charges   # a test endpoint you control

Resources:
  OrdersDLQ:
    Type: AWS::SQS::Queue
    Properties:
      MessageRetentionPeriod: 1209600       # 14 days, longer than the source queue
  OrdersQueue:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 180                # 6 x the 30 s function timeout
      MessageRetentionPeriod: 345600        # 4 days
      RedrivePolicy:
        deadLetterTargetArn:
          Fn::GetAtt: [OrdersDLQ, Arn]
        maxReceiveCount: 5
  IdempotencyTable:
    Type: AWS::DynamoDB::Table
    Properties:
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      TimeToLiveSpecification:
        AttributeName: expiration
        Enabled: true
  OrderConsumer:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: python3.13
      Handler: app.handler
      CodeUri: src/
      MemorySize: 512
      Timeout: 30
      ReservedConcurrentExecutions: 25      # >= MaximumConcurrency; needs 125 unreserved
      AutoPublishAlias: live
      DeploymentPreference:
        Type: Canary10Percent10Minutes
        Alarms:                             # function-wide, all versions: see the FAQ
          - Ref: FailedRecordsAlarm
          - Ref: ErrorsAlarm
      Environment:
        Variables:
          IDEMPOTENCY_TABLE:
            Ref: IdempotencyTable
          PAYMENT_API_URL:
            Ref: PaymentApiUrl
          POWERTOOLS_SERVICE_NAME: order-consumer
          POWERTOOLS_METRICS_NAMESPACE: Orders
      Policies:                             # the four table actions Powertools documents
        - Statement:
            - Effect: Allow
              Action:
                - dynamodb:GetItem
                - dynamodb:PutItem
                - dynamodb:UpdateItem
                - dynamodb:DeleteItem
              Resource:
                Fn::GetAtt: [IdempotencyTable, Arn]
      Events:
        Orders:
          Type: SQS
          Properties:
            Queue:
              Fn::GetAtt: [OrdersQueue, Arn]
            BatchSize: 10
            FunctionResponseTypes: [ReportBatchItemFailures]
            ScalingConfig:
              MaximumConcurrency: 20        # protects the payment provider
  OrderConsumerLogs:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName:
        Fn::Sub: "/aws/lambda/${OrderConsumer}"
      RetentionInDays: 14                   # owned by the stack, so sam delete removes it
  FailedRecordsAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      Namespace: Orders
      MetricName: FailedRecords
      Dimensions:
        - Name: service
          Value: order-consumer
      Statistic: Sum
      Period: 60
      EvaluationPeriods: 1
      Threshold: 0
      ComparisonOperator: GreaterThanThreshold
      TreatMissingData: notBreaching          # add AlarmActions to page the owners
  ErrorsAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      Namespace: AWS/Lambda
      MetricName: Errors
      Dimensions:
        - Name: FunctionName
          Value:
            Ref: OrderConsumer
      Statistic: Sum
      Period: 60
      EvaluationPeriods: 1
      Threshold: 0
      ComparisonOperator: GreaterThanThreshold
      TreatMissingData: notBreaching
  DlqDepthAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      Namespace: AWS/SQS
      # Counts automatic redrive; NumberOfMessagesSent does not.
      MetricName: ApproximateNumberOfMessagesVisible
      Dimensions:
        - Name: QueueName
          Value:
            Fn::GetAtt: [OrdersDLQ, QueueName]
      Statistic: Maximum
      Period: 300
      EvaluationPeriods: 1
      Threshold: 0
      ComparisonOperator: GreaterThanThreshold
      TreatMissingData: notBreaching

Details that are easy to miss. The canary watches a failed-records metric: a batch that returns item failures is a successful invocation, so Errors stays at zero (this page’s inference from the metric definition;[30] Powertools raises only when every record fails[63]). SAM decides whether to publish a version from the unresolved template, so without AWS::LanguageExtensions a new PaymentApiUrl alone would reach only $LATEST, and the live alias would keep calling the old endpoint.[66] Invocations “should use the alias qualifier”,[25] so check which one the mapping invokes (Lab 3, step 3).

Example status: Python syntax-checked; not executed (needs AWS, DynamoDB and the aws-lambda-powertools package). APIs follow the Powertools documentation for idempotency, batch processing and metrics as of October 2026.[53][63]

"""Order consumer: one payment per order even when SQS delivers a message twice (illustrative)."""
import json
import os
import urllib.request

from aws_lambda_powertools import Metrics
from aws_lambda_powertools.metrics import MetricUnit
from aws_lambda_powertools.utilities.batch import BatchProcessor, EventType, process_partial_response
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSRecord
from aws_lambda_powertools.utilities.idempotency import (
    DynamoDBPersistenceLayer,
    IdempotencyConfig,
    idempotent_function,
)

metrics = Metrics()
processor = BatchProcessor(event_type=EventType.SQS)
persistence = DynamoDBPersistenceLayer(table_name=os.environ["IDEMPOTENCY_TABLE"])
config = IdempotencyConfig(
    event_key_jmespath="powertools_json(body).order_id",  # business key, not the SQS messageId
    raise_on_no_idempotency_key=True,                     # a message without a key fails loudly
    expires_after_seconds=15 * 24 * 3600,                 # longer than the 14-day DLQ retention
)


@idempotent_function(data_keyword_argument="record", config=config, persistence_store=persistence)
def record_handler(record: SQSRecord) -> dict:
    order = json.loads(record.body)
    # Illustrative: a real provider call adds an API key fetched from Secrets Manager, and
    # classifies permanent 4xx errors so they do not cycle through five receives.
    request = urllib.request.Request(
        os.environ["PAYMENT_API_URL"],
        data=json.dumps({"order_id": order["order_id"], "amount": order["amount"]}).encode(),
        headers={"Content-Type": "application/json", "Idempotency-Key": order["order_id"]},
        method="POST",
    )
    with urllib.request.urlopen(request, timeout=5) as response:  # far below the 30 s function timeout
        return {"order_id": order["order_id"], "status": response.status}


@metrics.log_metrics
def handler(event, context):
    config.register_lambda_context(context)  # the in-progress lease ends with this invocation's time limit
    response = process_partial_response(
        event=event, record_handler=record_handler, processor=processor, context=context
    )
    metrics.add_metric(name="FailedRecords", unit=MetricUnit.Count,
                       value=len(response.get("batchItemFailures", [])))
    return response

Why order_id, not messageId: Powertools’ batch example uses messageId but notes that a field “your producer intentionally set” may be more accurate.[53] An order sent twice becomes two messages with different IDs, and a DLQ redrive assigns new IDs,[52] so a messageId key recognises no redriven message.

Lab 1 (beginner): see at-least-once delivery double-charge customers

Link to section “Lab 1 (beginner): see at-least-once delivery double-charge customers”

Objective: measure double charges for three designs: no idempotency, a local record only, and a local record plus a downstream key. Prerequisites: Python 3.8 or later; no cloud account, cost or risk; 20 minutes.

Example status: executed on 2026-10-16 with Python 3.8.10; output below is from that run (seeded, deterministic).

"""Simulate at-least-once delivery and count double charges for three consumer designs."""
import random
import sys

VISIBILITY_TIMEOUT = 180  # seconds before an unacknowledged message is delivered again
LEASE = 30                # in-progress claim expires with the function timeout


class Payments:
    """Downstream API. With keys=True it ignores a repeated idempotency key."""

    def __init__(self, keys):
        self.keys, self.seen, self.charges = keys, set(), {}

    def charge(self, order_id):
        if self.keys and order_id in self.seen:
            return
        self.seen.add(order_id)
        self.charges[order_id] = self.charges.get(order_id, 0) + 1


class Store:
    """Idempotency records, as a table with conditional writes would hold them."""

    def __init__(self):
        self.items = {}

    def claim(self, key, now):
        item = self.items.get(key)
        if item is None or (item["status"] == "IN_PROGRESS" and item["expires"] <= now):
            self.items[key] = {"status": "IN_PROGRESS", "expires": now + LEASE}
            return "CLAIMED"
        return item["status"]

    def complete(self, key):
        self.items[key] = {"status": "COMPLETE", "expires": float("inf")}


def run(design, orders, seed, timeout_rate):
    rng = random.Random(seed)
    payments, store = Payments(keys=(design == "record+key")), Store()
    queue = [(0.0, o) for o in orders]
    queue += [(rng.uniform(0, 60), o) for o in orders if rng.random() < 0.02]  # broker duplicates
    deliveries = 0
    while queue:
        queue.sort()
        now, order = queue.pop(0)
        deliveries += 1
        if design != "naive":
            state = store.claim(order, now)
            if state == "COMPLETE":
                continue                                  # duplicate: acknowledge, do nothing
            if state == "IN_PROGRESS":
                queue.append((now + VISIBILITY_TIMEOUT, order))  # report failure; retry later
                continue
        payments.charge(order)
        if rng.random() < timeout_rate:                    # timeout after the charge, before the ack
            queue.append((now + VISIBILITY_TIMEOUT, order))
            continue
        if design != "naive":
            store.complete(order)
    doubles = sum(1 for n in payments.charges.values() if n > 1)
    return deliveries, sum(payments.charges.values()), doubles


def main():
    n = int(sys.argv[1]) if len(sys.argv) > 1 else 10000
    timeout_rate = float(sys.argv[2]) if len(sys.argv) > 2 else 0.03
    orders = ["order-%05d" % i for i in range(n)]
    print("%-14s %10s %8s %14s" % ("design", "deliveries", "charges", "double-charged"))
    for design in ("naive", "record-only", "record+key"):
        deliveries, charges, doubles = run(design, orders, seed=7, timeout_rate=timeout_rate)
        print("%-14s %10d %8d %14d" % (design, deliveries, charges, doubles))


if __name__ == "__main__":
    main()
  1. Save as at_least_once.py; run python3 at_least_once.py (10,000 orders, 2% broker duplicates, 3% timeouts after the charge).
  2. Run python3 at_least_once.py 10000 0 (no timeouts) and python3 at_least_once.py 10000 0.10 (10% timeouts).

Expected output (step 1):

design         deliveries  charges double-charged
naive               10529    10529            504
record-only         10525    10303            295
record+key          10525    10000              0

Step 2: with no timeouts record-only has 0 double charges (naive 217); with 10% timeouts, 986 (naive 1,179); record+key stays at 0. Validation: the local record stops duplicates arriving after completion; only the downstream key stops those caused by a timeout after the side effect. Limits: the IN_PROGRESS branch fires only 5 times per design (a duplicate inside a timed-out attempt’s lease), and the simulated provider never forgets a key; real ones do (exception path). Troubleshooting: ValueError means a non-numeric argument. Teardown: delete the file.

Lab 2 (intermediate): estimate cost, concurrency and headroom

Link to section “Lab 2 (intermediate): estimate cost, concurrency and headroom”

Objective: compute the monthly cost of the consumer path, cost per million events and peak concurrency, and see what a slow dependency does to both. Prerequisites: Python 3.8 or later; no cost or risk; 30 minutes.

Example status: executed on 2026-10-17 with Python 3.8.10. Defaults: US East (N. Virginia) list prices checked 2026-10-16 for Lambda x86,[12] DynamoDB on-demand writes[67] and SQS requests;[68] two writes per attempt (Powertools claims, then completes or deletes[53]); 1.2 queue requests per event is assumed.

"""Estimate monthly cost and peak concurrency of a queue-driven function and its idempotency table.

Prices are inputs. Defaults: US East (N. Virginia) list prices checked 2026-10-16 for AWS Lambda
(x86, on-demand), DynamoDB on-demand writes and SQS standard requests.
"""
import argparse

SECONDS_PER_MONTH = 30 * 24 * 3600


def estimate(a):
    attempts = a.events * a.attempts_per_event
    gb_seconds = attempts * (a.duration_ms / 1000) * (a.memory_mb / 1024)
    free_gbs, free_req = (400000, 1000000) if a.free_tier else (0, 0)
    compute = max(gb_seconds - free_gbs, 0) * a.price_gb_s
    requests = max(attempts - free_req, 0) / 1e6 * a.price_per_million
    table = attempts * a.writes_per_attempt / 1e6 * a.price_per_million_writes
    queue = a.events * a.queue_requests_per_event / 1e6 * a.price_per_million_queue
    total = compute + requests + table + queue + a.other_usd
    return {
        "invocations": attempts,
        "gb_seconds": gb_seconds,
        "lambda_usd": compute + requests,
        "table_usd": table,
        "queue_usd": queue,
        "total_usd": total,
        "usd_per_million_events": total / (a.events / 1e6),
        "lambda_share_pct": (compute + requests) / total * 100,
        "avg_busy_environments": attempts * (a.duration_ms / 1000) / SECONDS_PER_MONTH,
        "peak_concurrency": a.peak_rps * a.attempts_per_event * (a.duration_ms / 1000),
    }


def main():
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument("--events", type=float, default=30e6, help="business events per month")
    p.add_argument("--duration-ms", type=float, default=200)
    p.add_argument("--memory-mb", type=float, default=512)
    p.add_argument("--attempts-per-event", type=float, default=1.05, help="1 + retries + duplicates")
    p.add_argument("--peak-rps", type=float, default=400, help="peak business events per second")
    p.add_argument("--quota", type=float, default=1000, help="concurrency available to this function")
    p.add_argument("--price-gb-s", type=float, default=0.0000166667)
    p.add_argument("--price-per-million", type=float, default=0.20, help="Lambda requests")
    p.add_argument("--writes-per-attempt", type=float, default=2, help="idempotency table writes")
    p.add_argument("--price-per-million-writes", type=float, default=0.625)
    p.add_argument("--queue-requests-per-event", type=float, default=1.2, help="assumption")
    p.add_argument("--price-per-million-queue", type=float, default=0.40)
    p.add_argument("--other-usd", type=float, default=0, help="logs, API layer, data transfer")
    p.add_argument("--free-tier", action="store_true", help="Lambda free tier only")
    a = p.parse_args()
    r = estimate(a)
    for k, v in r.items():
        print("%-24s %16s" % (k, "{:,.2f}".format(v)))
    headroom = 1 - r["peak_concurrency"] / a.quota
    print("%-24s %15.1f%%" % ("peak_headroom", headroom * 100))
    if headroom < 0.3:
        print("WARNING: less than 30% concurrency headroom at peak")
        raise SystemExit(1)


if __name__ == "__main__":
    main()
  1. Save as fn_cost.py; run python3 fn_cost.py (30 million events a month, 200 ms, 512 MB, 1.05 attempts, peak 400 per second).
  2. Simulate a slow dependency: python3 fn_cost.py --duration-ms 2000.
  3. Try --memory-mb 1024 --duration-ms 100 (more memory brings more CPU) and --free-tier.

Expected output: step 1: lambda_usd 58.80, table_usd 39.38, queue_usd 14.40, total_usd 112.58, usd_per_million_events 3.75, peak_concurrency 84.00, peak_headroom 91.6%, exit code 0. Step 2: lambda_usd 531.30, peak_concurrency 840.00, headroom 16.0%, the warning, exit code 1. Step 3: same cost, half the busy environments (1.22, not 2.43); free tier: Lambda $51.93. Validation: 3.15 million GB-s × $0.0000166667 = $52.50, plus 31.5 × $0.20 = $6.30; 63 × $0.625 = $39.38; 36 × $0.40 = $14.40. Troubleshooting: unrecognized arguments means a mistyped flag. Teardown: none. Lessons: Lambda is half of this path’s bill; in step 2 the dependency, not your code, set the bill and the capacity risk.

Lab 3 (advanced): deploy, break and recover a queue consumer

Link to section “Lab 3 (advanced): deploy, break and recover a queue consumer”

Objective: watch duplicates being absorbed and a poison message reach the DLQ, then redrive it. Prerequisites: a sandbox AWS account (never production), AWS CLI v2, AWS SAM CLI, the reference implementation as template.yaml and src/app.py, src/requirements.txt with aws-lambda-powertools, and a test endpoint you control that logs the Idempotency-Key header. New accounts have reduced concurrency quotas;[10] the reservation needs 125 unreserved (25 + 100[50]), so if step 0 shows less, remove ReservedConcurrentExecutions. Cost and risk: small charges; never use a real payment provider. Status: not executed (no AWS account was used).

Example status: bash syntax-checked; not executed.

# Placeholders: replace with your own values.
STACK=order-consumer-lab
QUEUE_URL="https://sqs.eu-west-1.amazonaws.com/ACCOUNT_ID/orders-queue"
QUEUE_ARN="arn:aws:sqs:eu-west-1:ACCOUNT_ID:orders-queue"
DLQ_URL="https://sqs.eu-west-1.amazonaws.com/ACCOUNT_ID/orders-dlq"
DLQ_ARN="arn:aws:sqs:eu-west-1:ACCOUNT_ID:orders-dlq"

# 0. Reserved concurrency 25 needs unreserved account concurrency of at least 125.
aws lambda get-account-settings --query 'AccountLimit.UnreservedConcurrentExecutions'

sam build && sam deploy --guided --stack-name "$STACK" \
  --parameter-overrides PaymentApiUrl=https://test-endpoint.example.com/charges

# 1. A good order, sent twice by the producer: expect ONE request at the test endpoint.
for i in 1 2; do
  aws sqs send-message --queue-url "$QUEUE_URL" \
    --message-body '{"order_id":"order-1","amount":100}'
done

# 2. A poison message (no order_id): expect it in the DLQ after about 5 x 180 s.
aws sqs send-message --queue-url "$QUEUE_URL" --message-body '{"amount":100}'
aws sqs get-queue-attributes --queue-url "$DLQ_URL" \
  --attribute-names ApproximateNumberOfMessages

# 3. Which function version or alias does the mapping invoke?
aws lambda list-event-source-mappings --event-source-arn "$QUEUE_ARN" \
  --query 'EventSourceMappings[].{uuid:UUID,target:FunctionArn,state:State}'

# 4. Redrive from the DLQ at a fixed, low rate (after fixing the cause).
aws sqs start-message-move-task --source-arn "$DLQ_ARN" \
  --max-number-of-messages-per-second 5

Expected output: step 0, 125 or more; step 1, one request at the endpoint; step 2, ApproximateNumberOfMessages of "1" after roughly 15 minutes; step 3, a target ARN ending in :live; step 4, a TaskHandle. Validation: alone in its batch, the poison message makes Powertools raise, so Errors counts it;[63] mixed with good messages, only FailedRecords shows it. Troubleshooting: a deploy failing on concurrency means step 0 was below 125; on the mapping, visibility timeout < function timeout;[38] no DLQ arrival points to the redrive policy. The move task accepts only DLQs whose sources are SQS queues, at most 500 messages per second.[69] Teardown: sam delete --stack-name "$STACK" (deletes queues, messages, table and log group).

Scenario Context Method Trade-off Success metric
1. Payment consumer (illustrative) Bursty orders; a fixed provider rate The reference implementation Accepted before paid Zero duplicate charges; DLQ empty daily
2. Change-data stream (illustrative) Table updates feed a search index; order per item matters Finite retries, maximum record age, bisect, partial responses, on-failure destination[8][32] Skipped records need a timely repair job IteratorAge normal within minutes; every skipped record repaired
3. Latency-sensitive public API (illustrative) p99 target on checkout; quiet nights Measure cold-start share; then provisioned concurrency, SnapStart, or minimum instances[11][46][47] Warm capacity is billed, used or not p99 on target at the quietest hour
4. Provider disruption (sourced) AWS us-east-1, 19–20 October 2025 See below[70] Asynchronous paths may be deprioritised Backlog drained without duplicate effects
5. Fit at scale (sourced) Prime Video stream monitoring, 2023 See below[71] Per-step charges and limits Cost per stream analysed

Scenario 4: a provider outage (sourced). In AWS’s account of the 19–20 October 2025 us-east-1 disruption, a subsystem polling SQS queues “failed and did not recover automatically” until 4:40 AM PDT; later AWS “throttled Lambda Event Source Mappings and asynchronous invocations to prioritize latency-sensitive synchronous invocations”, and backlogs were processed by 2:15 PM PDT.[70] This page’s lessons: asynchronous work can wait hours, so retention, event age and idempotency expiry must cover a long outage; the backlog returns as a surge for maximum concurrency to absorb; and replays invite duplicates.

Scenario 5: per-step charges (sourced, secondary). InfoQ reports that Prime Video’s stream monitor ran several Step Functions state transitions per second of stream and kept intermediate frames in S3; those charges and an account limit on transitions ended with one process on ECS.[71] For tiny, continuous units of work, per-step pricing and limits set the cost; NoOps discusses the case.

KPI Definition or formula Data source Pitfall
Business success rate Events fully processed ÷ events accepted Producer and consumer counts Function success ≠ business success
Duplicate side-effect rate Side effects repeated for the same key ÷ side effects Downstream logs; idempotency conflicts Unmeasured unless the key is logged
Dead-letter inflow and oldest age Messages entering the DLQ per day; age of the oldest ApproximateNumberOfMessagesVisible; ApproximateAgeOfOldestMessage[72] NumberOfMessagesSent misses automatic redrive[72]
Backlog age Oldest-message age (queues), IteratorAge (streams), AsyncEventAge (async) Lambda and SQS metrics[30] Use the maximum. Messages received three or more times drop out of a standard queue’s oldest age,[72] so catch poison with DLQ depth and FailedRecords
Throttle ratio Throttles ÷ (Invocations + Throttles) Lambda metrics; throttles are not invocations[30] A queue hides throttles as delay
Concurrency headroom 1 − peak ÷ available concurrency ConcurrentExecutions, quota The pool is shared
Timeout headroom 1 − p99 duration ÷ timeout Duration percentiles Averages look safe until a dependency slows
Cold-start share Invocations with an init phase ÷ invocations Init Duration in REPORT lines;[23] faas.coldstart[61] Matters only with latency targets
Cost per million business events Monthly path cost ÷ (business events ÷ 1,000,000) Bill and event counts Function-only cost hides tables, queues and retries

Worked calculation (illustrative). 30 million orders; 31.5 million consumer runs; 63,000 throttles; 120 repeated keys that the provider de-duplicated.

  • Throttle ratio = 63,000 ÷ (31,500,000 + 63,000) = 0.20%.
  • Attempts per order = 31.5 ÷ 30 = 1.05: 5% of compute pays for retries and duplicates.
  • Duplicates prevented by keys = 120 ÷ 30,000,000 = 4 per million, each a double charge without keys.
  • Cost per million orders = $112.58 ÷ 30 = $3.75 for the consumer path (Lab 2); Lambda alone suggests $1.96.

Targets come from business outcomes (no duplicate charges; DLQ empty by end of day); review DLQ and backlog daily, cost monthly. Vanity metrics: number of functions, “100% serverless”, Errors = 0 on a partial-batch consumer.

  • Least privilege. One execution role per function, scoped to exact actions and resources.
  • Who may invoke. An S3 bucket ARN carries no account ID: once you delete a bucket, another account could create one with the same name and invoke the function, so add aws:SourceAccount.[73]
  • Public endpoints. With auth type NONE and a public policy, “any unauthenticated user with your function URL can invoke your function”.[74] Require authentication or throttling on public paths and alarm on invocation rate: every call is billed.
  • Secrets. AWS recommends Secrets Manager, not environment variables, for credentials.[75]
  • DLQs hold full payloads. Apply the source data’s retention, encryption and access rules (PrivacyOps).
  • Snapshots and uniqueness. With SnapStart, unique values generated during initialization can be reused across environments; generate them later.[46]
  • Runtime currency. Major runtime upgrades are “a customer-driven operation”.[49]
  • Account guardrails belong to CloudSecOps; permission design to IAMOps.

Cost model. Lambda bills per request and per GB-second, and “Duration charges apply to code that runs in the handler of a function as well as initialization code”. As of October 2026 the US East (N. Virginia) x86 rates are $0.20 per million requests and $0.0000166667 per GB-second, with a monthly free tier of one million requests and 400,000 GB-seconds; provisioned concurrency is billed while configured, without free tier.[12]

The function is not the whole bill. In Lab 2 the idempotency table (two writes per attempt at $0.625 per million on demand[67]) and queue requests ($0.40 per million[68]) add $53.78 to $58.80 of Lambda. Logs, the API layer, data transfer and VPC networking come on top; take them from your own bill.

Levers, in order of usual impact:

  1. Fix slow dependencies and timeouts. Duration is the multiplier (Lab 2, step 2).
  2. Cut wasted attempts: one retry layer, partial batch responses, cheap duplicate short-circuits.
  3. Tune memory. More memory brings more CPU (one vCPU at 1,769 MB[10]); measure with Power Tuning on a side-effect-free copy.[64]
  4. Batch: fewer invocations, bigger blast radius per failure.
  5. Change the model for steady load. AWS positions Lambda Managed Instances as “ideal for steady-state, high-volume workloads” (EC2 prices plus a 15% management fee, as of October 2026);[12] Google recommends instance-based billing for “steady, slowly varying” traffic.[45]

Capacity is concurrency: the Regional quota (1,000 by default, lower for new accounts), 1,000 new environments per function every 10 seconds, and for SQS mappings five concurrent invocations at first, up to 300 more per minute, to 1,250; provisioned mode, which excludes maximum concurrency, scales to 100,000 (as of October 2026).[10][24][37] Sustainability: nothing idles when nothing runs, but retries and oversized memory burn energy (GreenOps).

Monthly evidence that the model works: every DLQ has an owner and an alarm tested by firing; the last duplicate-storm game day produced zero double effects; redrives record a cause, an approver and the provider’s key retention; cost per event follows forecast; every runtime is at least 180 days from deprecation.[49]

Symptom Likely cause Diagnosis Remediation Prevention
Customers charged twice No idempotency; a timeout after the side effect; a key expired before a redrive Repeated keys downstream Refund; key locally and downstream; look up before late redrives Lab 1 as a test
IteratorAge climbs on one shard Poison record, infinite retries (defaults −1) Errors repeat at one sequence number Finite retries, record age, bisect[8] Stream checklist
Healthy messages land in the DLQ Low maxReceiveCount; visibility under 6 × function timeout; reserved below maximum concurrency Throttles and durations at the time Fix the settings, redrive[38][37] Template defaults
Unrelated functions throttled (429) One function took the unreserved pool Per-function ConcurrentExecutions Reserve for critical functions; cap the noisy one[11] Concurrency budget
Bill rises, traffic flat Retry storm, or a loop detection misses (such as via DynamoDB) Invocations per business event Break the loop; reserved concurrency 0 stops new invocations[13][28] Billing alarms
Canary passed, failures afterwards Item failures invisible to Errors; or a parameter-only change reached only $LATEST Failed-record metric; alias environment Roll back the alias; fix alarms; add AWS::LanguageExtensions[66] Failed-record canary alarms
Async events never processed Past maximum event age, no failure destination AsyncEventsDropped, AsyncEventAge[30] Add a destination; replay from the source Destination required by policy
Event Hubs events skipped Unhandled exception; the checkpoint advanced anyway[35] Exceptions without retries Retry policy or catch-and-park Treat exceptions as data loss

Worked runbook: a poison message floods the dead-letter queue

Link to section “Worked runbook: a poison message floods the dead-letter queue”
Figure 5. Triage and recovery when the dead-letter queue fills
yesnodownstreamthrottlesnoyesAlarm: DLQ depth or failedrecords above 0Most messages fail with thesame error?Payload or code defect:pause the mapping if goodtraffic is also failingDownstream errors orLambda throttles?Lower maximum concurrencyand let messages wait in thequeueCheck reserved and accountconcurrency andmaxReceiveCountFix, test with a DLQ sample,deploy through the canaryRedrive at a fixed low rateand watch failed records andduplicatesDLQ empty and backlog agenormal?Close: record the cause, adda test, adjust alarms
Diagram source (Mermaid)
flowchart TB
  A["Alarm: DLQ depth or failed records above 0"]:::risk --> B{"Most messages fail with the same error?"}:::decision
  B -->|"yes"| C["Payload or code defect: pause the mapping if good traffic is also failing"]
  B -->|"no"| D{"Downstream errors or Lambda throttles?"}:::decision
  D -->|"downstream"| E["Lower maximum concurrency and let messages wait in the queue"]
  D -->|"throttles"| F["Check reserved and account concurrency and maxReceiveCount"]
  C --> G["Fix, test with a DLQ sample, deploy through the canary"]
  E --> G
  F --> G
  G --> H["Redrive at a fixed low rate and watch failed records and duplicates"]:::core
  H --> I{"DLQ empty and backlog age normal?"}:::decision
  I -->|"no"| B
  I -->|"yes"| J["Close: record the cause, add a test, adjust alarms"]:::core
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029

Text equivalent: a DLQ or failed-records alarm starts triage. One dominant error means a payload or code defect (pause the mapping only if good traffic also fails); downstream errors mean lowering maximum concurrency; throttles mean checking concurrency and the receive count. Every branch ends with a fix tested on a DLQ sample, a canary and a slow redrive; triage repeats until the DLQ is empty, then closes with a cause, a test and adjusted alarms.

  1. Classify (5 min). Ten DLQ samples all fail on the amount field and good orders still succeed, so do not pause the mapping (--no-enabled stops polling; messages then wait only until their retention ends[76]).
  2. Contain. Ask the partner to fix the field; classify the incident as data, not availability.
  3. Fix. Accept and validate both forms of the amount; add the DLQ sample as a test; deploy through the canary.
  4. Redrive. aws sqs start-message-move-task --source-arn <dlq-arn> --max-number-of-messages-per-second 20, a rate the provider can absorb;[69] keys only hours old make any repeat harmless.
  5. Verify. DLQ empty; FailedRecords at 0; 4,000 new charges and no repeated keys at the provider.
  6. Close. Postmortem via IncidentOps; validate the contract at intake so bad payloads fail at the API.
  • “The platform gives us exactly once.” Platform de-duplication is limited in time and scope (alternatives).
  • Retrying everywhere. SDK, function, queue and caller retries multiply load during an incident;[36] pick one layer.
  • The DLQ as a bin. Without an owner, an alarm and retention longer than the source,[27] it is delayed data loss.
  • messageId as the idempotency key. It misses producer resends and redriven messages, which get new IDs.[52]
  • Synchronous function chains. A waiting caller is billed while it waits;[12] use a queue or a workflow engine.
  • Unlimited concurrency by default. A spike becomes a database outage; cap consumers of fragile dependencies.
  • “Serverless is always cheaper.” Cost scales with attempts × duration × memory; steady, heavy work can be cheaper elsewhere.[71]

Good fit: bursty traffic with idle periods (most functions in Azure’s production data were “invoked very infrequently”[77]); event-driven glue between managed services; short units of work; teams that prefer owning code to hosts.

Poor fit, or not yet: steady high-volume work; work beyond platform limits (15 minutes on Lambda Event Functions, 90 for async and most event-source invocations on Managed Instances;[10] 60 minutes per Cloud Run request[41]); strict latency with no budget for warm capacity; side effects that cannot be idempotent; nobody on call. Hidden responsibilities: DLQ triage, runtime upgrades, quotas, cost per event.

Figure 6. Should this workload run on serverless compute?
no: steady and heavyyesnoyesnoyesyesnoNew workloadSpiky or event-driven, withidle periods?Containers, instance-basedbilling or Lambda ManagedInstancesEach unit of work fits platformtime limits?Jobs or a workflow engine(WorkflowOps)Every side effect can bemade idempotent?Not yet: fix the contract firstLatency target on everyrequest?Functions with warm capacity,or containers with minimuminstancesFunctions or serverlesscontainers on demand
Diagram source (Mermaid)
flowchart TB
  A["New workload"] --> B{"Spiky or event-driven, with idle periods?"}:::decision
  B -->|"no: steady and heavy"| X1["Containers, instance-based billing or Lambda Managed Instances"]:::neutral
  B -->|"yes"| C{"Each unit of work fits platform time limits?"}:::decision
  C -->|"no"| X2["Jobs or a workflow engine (WorkflowOps)"]:::neutral
  C -->|"yes"| D{"Every side effect can be made idempotent?"}:::decision
  D -->|"no"| X3["Not yet: fix the contract first"]:::risk
  D -->|"yes"| E{"Latency target on every request?"}:::decision
  E -->|"yes"| F["Functions with warm capacity, or containers with minimum instances"]:::core
  E -->|"no"| G["Functions or serverless containers on demand"]:::core
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029

Text equivalent: steady, heavy traffic points to containers, instance-based billing or Lambda Managed Instances. For spiky work, units beyond platform time limits go to jobs or a workflow engine; side effects that cannot be idempotent mean “not yet”. Otherwise a latency target on every request points to warm capacity or minimum instances, and everything else runs on demand.

The tree is this page’s judgement; adjust it with your own cost (Lab 2) and latency data.

This is this Atlas’s model, not an industry standard.

Level Observable behaviour Exit criterion
1. Ad hoc Console-built functions; default retries; no DLQs Every function in code with an owner
2. Defined Templates set timeouts, DLQs, partial batch responses Every event path has a design record
3. Safe Idempotency on business keys; canaries with failed-record alarms; concurrency budgets A duplicate-storm game day passes with zero double effects
4. Measured Cost per event, backlog and DLQ age reviewed; redrive runbook rehearsed Monthly review on time for a quarter
5. Optimised Compute model chosen per workload from data Decisions recorded with cost and latency evidence
Window Actions Milestone
Days 1–30 Inventory triggers; answer the three questions for each; add DLQs, destinations and alarms; fix timeout ratios No event path without a failure destination
Days 31–60 Idempotency on the top five side-effecting functions; partial batch responses; concurrency caps for fragile dependencies; canaries First duplicate-storm test passed
Days 61–90 Cost per event in the monthly review; runtime currency report; game day; CI policy checks Maturity level 3 for critical paths

Preflight for a new event-driven function

  • Invocation model identified; retry owner and limits written down
  • Business idempotency key passed to every downstream side effect; the provider’s key retention known
  • Failure destination or DLQ with an owner, an alarm and retention longer than the source
  • Timeouts nested: downstream call < function timeout; SQS visibility ≥ 6 × function timeout
  • Maximum concurrency on consumers of fragile dependencies; reserved concurrency ≥ that maximum
  • Stream mappings: finite retries, maximum record age, bisect, partial batch responses

Production readiness

  • Duplicate, poison-message and slow-dependency tests pass
  • Canary alarms include failed records, not only Errors
  • Peak concurrency ≤ 70% of what is available, or a quota increase requested
  • Execution role scoped to exact actions; invoke permissions limited by source account or ARN; public paths authenticated

Monthly review

  • DLQ counts, oldest ages and redrive records reviewed
  • Cost per business event against forecast
  • Runtimes over 180 days from deprecation, including versions behind aliases (notices list only $LATEST; use Trusted Advisor or the API[49]); container-image functions checked by hand

Event-path design record (one per trigger, kept next to the code):

Function / trigger:          order-consumer  <-  SQS orders-queue (poll-based)
Business key:                order_id (sent downstream as Idempotency-Key)
Who retries, how long:       SQS after 180 s visibility, maxReceiveCount 5 (~15 min)
After the last attempt:      orders-dlq, 14 days, owner: payments team, alarm: depth > 0
Timeouts:                    provider call 5 s < function 30 s <= visibility 180 s / 6
Concurrency:                 mapping max 20, reserved 25; provider limit: ___ requests/s
Partial failures reported:   yes (ReportBatchItemFailures), FailedRecords metric
Expected volume / cost:      30 M events/month, 1.05 attempts/event, $3.75 per million
                             (Lab 2: Lambda + table + queue; logs and API layer extra)
Local record expiry:         15 days > DLQ retention (14 days)
Provider key retention:      ___ (Stripe API v1: may be pruned after 24 h)
Replay safety:               the shorter of the two; older redrives: look up the order
                             at the provider before charging

DLQ runbook card:

Alarm:           orders-dlq ApproximateNumberOfMessagesVisible > 0
First 5 minutes: sample 10 messages; group by error; is good traffic still succeeding?
Pause?           only if good traffic fails: update-event-source-mapping --no-enabled
Fix:             add the sample as a test; deploy through the canary
Redrive:         start-message-move-task at <= consumer capacity; approver: ______
Close:           cause, count, duplicates seen (expect 0), alarm or test added
Option Fits Operations you still own Cost shape
Functions (Lambda, Azure Functions) Short, bursty, event-driven work Event semantics, concurrency, runtime upgrades Per request and GB-second
Serverless containers (Cloud Run) HTTP services; up to 1,000 concurrent requests per instance[43] Image builds (ContainerOps), concurrency settings Per request, or per instance lifetime
Lambda Managed Instances Steady, high-volume work in the Lambda model Instance choice and capacity commitments EC2 prices plus a management fee[12]
Containers on Kubernetes Steady load, custom runtimes, long processes The cluster as well (KubernetesOps) Provisioned capacity, used or not

Is ServerlessOps a recognised discipline? No. It is a niche label from 2016–2020; the practice is simply operating serverless applications. Use the label only if your organisation already does.

Does a FIFO queue make my consumer exactly-once? No. It prevents duplicate sends within a five-minute window;[54] a consumer that times out after a side effect can still repeat it.

Should I keep functions warm with scheduled pings? Measure first: on Lambda, cold starts typically affect under 1% of invocations.[23] Where a latency target requires it, use the platform’s warm capacity; Cloud Run treats minimum instances as “best-effort”.[47]

Do the canary alarms judge only the new version? Not in the reference template: both are function-wide, so during the canary an old-version failure (a flaky provider, say) also rolls the release back, a conservative choice. To judge the new version alone, alarm on Errors per version (the ExecutedVersion dimension, which AWS suggests for two versions behind a weighted alias[78]) and give FailedRecords a version dimension.

Can I safely redrive a ten-day-old DLQ message? Only if both your idempotency record and the provider still hold its key; otherwise look the order up at the provider first (keys expire).

  • New compute types (as of October 2026). AWS now distinguishes Lambda Event Functions (“the name for the original Lambda function type”), Web Functions (several requests per environment) and MicroVMs (environments living up to 8 hours).[22] The boundaries of “serverless” shift; the three event-path questions do not.
  • Durable execution. Lambda durable functions run workflows “lasting up to a year”;[22] treat them as a workflow engine (WorkflowOps).
  • Telemetry standards. OpenTelemetry’s FaaS conventions are at “Development” status;[61] attribute names may change.
  • Day 1: read this page to the decision framework; run Labs 1 and 2.
  • Week 1: deploy Lab 3 in a sandbox; break it with a poison message; redrive it.
  • Month 1: design records for your real functions; failed-record alarms; a duplicate-storm game day.
  • Advanced: stream semantics; durable execution (WorkflowOps); SLO design (SRE).
  • Portfolio project: an idempotent consumer whose tests replay every message twice, plus a cost-per-event dashboard.
  • Label: niche, mostly historical; the practice is operating serverless compute in production.
  • Assume: at-least-once delivery, retries you did not write, events dropped after the last attempt, keys that expire.
  • Do: business-key idempotency sent downstream; partial batch responses; owned failure destinations; nested timeouts; concurrency caps with matching reservations; canaries with failed-record alarms.
  • Measure: backlog and DLQ age, throttle ratio, duplicate effects, whole-path cost per million events.
  • Watch out for: infinite stream retries, metadata-only stream destinations, short DLQ retention, alarms on the wrong metric.
  • Decide: spiky, short, idempotent work fits; steady, heavy or long-running work usually fits elsewhere.

A judgement call: drain the backlog fast, or protect the payment provider?

Link to section “A judgement call: drain the backlog fast, or protect the payment provider?”

Situation (illustrative). After a four-hour incident, 2 million orders are queued and the business wants them processed within the hour. The payment provider’s contract allows 300 requests per second; each charge takes about 0.25 s.

Option A: remove the caps. Drop maximum concurrency and the reservation so the consumer draws on the Regional pool (1,000 by default). 1,000 consumers at 0.25 s offer about 4,000 requests per second, over 13 times the contract, while the account’s other unreserved functions in that Region are throttled.[11] Expect provider throttling, timeouts, retries multiplying load[36] and a filling DLQ.

Option B: drain at the contract rate. Concurrency = rate × duration = 300 × 0.25 = 75 consumers.[11] Two million orders at 300 per second take about 1.9 hours.

Decision. Choose B: maximum concurrency about 70 (below 75) and reserved concurrency raised to at least 70 in the same change, or Lambda throttles messages[37] and healthy orders reach the DLQ after five receives. Tell the business it takes about two hours, or negotiate a temporary higher rate. A looks faster but probably finishes later, as failed charges cycle through retries and the DLQ. Uncertainty: the provider may tolerate more, and urgent orders may justify a priority queue with its own cap.

You should now be able to explain at-least-once delivery; design an idempotent consumer with a failure destination; configure timeouts and concurrency consistently; diagnose a stalled stream or a filling DLQ; and decide whether a workload fits serverless compute.

  1. A consumer records each order_id in a table before charging. Why can customers still be charged twice?
Show answer for question 1

A timeout after the charge, before the record is completed, lets the lease expire and the message is processed again. Only a key the provider honours, while it remembers it, stops the second charge (Lab 1: 295 double charges with the record only, 0 with the key).

  1. A Kinesis consumer’s IteratorAge keeps rising on one shard while the others are fine. What do you check first?
Show answer for question 2

A poison record with default retries: MaximumRetryAttempts and MaximumRecordAgeInSeconds default to −1, so the batch is retried until the record expires.[8] Set finite limits, bisect on error, add an on-failure destination.

  1. Your function timeout is 60 s. What is the minimum SQS visibility timeout AWS recommends, and why?
Show answer for question 3

360 s, six times the function timeout, so Lambda can retry if the function is throttled while processing a previous batch.[38]

  1. A canary deployment passed with Errors at zero, yet the DLQ filled an hour later. Name two possible causes.
Show answer for question 4

Item failures in partial batch responses are not function errors, so the canary was blind to them; or the DLQ signal came after the canary ended (five receives × 180 s is about 15 minutes). A parameter-only change that reached only $LATEST is a third.[66]

  1. A workload processes 400 events per second at 200 ms each. Its dependency slows to 2 s. What happens to concurrency and cost?
Show answer for question 5

Concurrency rises from 80 to 800 (84 to 840 with 5% retries), close to the default 1,000 quota; the Lambda bill rises about ten times (Lab 2: $58.80 to $531.30).[11][10]

  1. A stream mapping’s on-failure destination is an SQS queue. What does the repair job find there?
Show answer for question 6

Only shard and sequence numbers. The job must read the records from the stream before they expire (24 hours on DynamoDB Streams), or the destination should be S3, which gets the full record.[29][60]

ServerlessOps never became a discipline with a body or certification, and the label has faded. The work it named did not. Serverless platforms take away servers and hand you a contract written in retries, delivery guarantees, concurrency limits and per-attempt prices. Teams that read it carefully, making every effect idempotent, giving every failed event an owned destination, and budgeting concurrency against their slowest dependency, get less undifferentiated work and costs that follow demand. Teams that skip it get duplicate charges, silent losses and bills that grow without new customers.

Sources were opened on 2026-10-16, or on 2026-10-17 where the footnote says so. Limits, prices and product names marked “as of October 2026” go stale: re-verify them quarterly and on each Lambda, SAM or Powertools release; Lab 2 takes prices as inputs.

Changelog.

  • 2026-10-16, r0: first draft (term status confirmed as niche; 13 parts; 6 diagrams; Labs 1–2 executed, Lab 3 not executed).
  • 2026-10-17, r1: revised after the independent review: provider key expiry, metadata-only stream destinations, whole-path cost in Lab 2 (re-executed), concurrency in the judgement call, Lens dated 2017, SAM version publishing, 8 new sources, repetition cut.
  • 2026-10-17, post-score polish: stream partial-batch retries, function-wide canary alarms (FAQ), S3 bucket-deletion precondition, re-check cadence; repeats trimmed.

  1. CNCF Serverless Whitepaper v1.0 — CNCF Serverless Working Group, 2018-02-14. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  2. Serverless Ops (Michael Hausenblas) — Goodreads listing of the O’Reilly report, published 2016-11-11. Checked 2026-10-16. ↩︎ ↩︎

  3. Serverless Ops: What to do when the server goes away — Tom McLaughlin, devopsdays Nashville 2017. Checked 2026-10-16. ↩︎ ↩︎

  4. About ServerlessOps — ServerlessOps, undated. Checked 2026-10-16. ↩︎ ↩︎

  5. Lambda event source mappings — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  6. How Lambda handles errors and retries with asynchronous invocation — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎

  7. Retry policy for EventBridge targets — AWS documentation. Checked 2026-10-16. ↩︎

  8. Lambda parameters for Kinesis event source mappings — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  9. Understanding retry behavior in Lambda — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  10. Lambda quotas — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  11. Understanding Lambda Event Function scaling — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  12. AWS Lambda Pricing — Amazon Web Services. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  13. Lambda recursive loop detection — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  14. Ending ServerlessOps AWS Cloud And DevOps Consulting Services — Tom McLaughlin, 2019-07-07. Checked 2026-10-16. ↩︎ ↩︎

  15. Learn about serverless with these books, videos, and tutorials — O’Reilly, 2018-10-12. Checked 2026-10-16. ↩︎

  16. Serverless Applications Lens — AWS Well-Architected; first published 2017-11-01, major update 2022-07-14 per its document revisions. Checked 2026-10-16. ↩︎ ↩︎

  17. Serverless Ops: What do we do when the server goes away? — Tom McLaughlin, DEV, 2018-03-19 (first published on serverlessops.io, 2018-02-20). Checked 2026-10-16. ↩︎

  18. Cloud Programming Simplified: A Berkeley View on Serverless Computing — Jonas et al., arXiv, 2019-02-09. Checked 2026-10-16. ↩︎

  19. Serverless DevOps: What do we do when the server goes away? — Tom McLaughlin, devopsdays Atlanta 2019. Checked 2026-10-16. ↩︎

  20. CloudEvents specification — CNCF CloudEvents project (graduated 2024-01-25 per cloudevents.io). Checked 2026-10-16. ↩︎ ↩︎

  21. Knative — CNCF project page. Checked 2026-10-16. ↩︎ ↩︎

  22. What is AWS Lambda? — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎

  23. Understanding the Lambda execution environment lifecycle — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  24. Event Function scaling behavior — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎

  25. Deploying serverless applications gradually with AWS SAM — AWS documentation. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  26. Using Lambda with Amazon SQS — AWS documentation. Checked 2026-10-16. ↩︎

  27. Using dead-letter queues in Amazon SQS — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  28. Capturing records of Lambda asynchronous invocations — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎

  29. Retain discarded records for a DynamoDB event source in Lambda — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  30. Types of metrics for Lambda Event Functions — AWS documentation. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  31. Handling errors for an SQS event source in Lambda — AWS documentation. Checked 2026-10-17. ↩︎ ↩︎ ↩︎

  32. Partial batch response with Kinesis and Lambda — AWS documentation. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎

  33. Making retries safe with idempotent APIs — Malcolm Featonby, Amazon Builders’ Library. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  34. Retry events — Google Cloud Eventarc, updated 2026-10-13. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎

  35. Reliable event processing with Azure Functions and Event Hubs — Microsoft Learn, 2026-08-17. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  36. Timeouts, retries, and backoff with jitter — Marc Brooker, Amazon Builders’ Library. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎

  37. Configuring scaling behavior for SQS event source mappings — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  38. Configuring an Amazon SQS queue for Lambda — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  39. Configuring error handling for asynchronous invocation — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎

  40. Azure Functions hosting options — Microsoft Learn, 2026-09-16. Checked 2026-10-16. ↩︎

  41. Configure request timeout — Google Cloud Run, updated 2026-10-13. Checked 2026-10-16. ↩︎ ↩︎

  42. Flex Consumption plan hosting — Microsoft Learn, 2026-09-15. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  43. About concurrency — Google Cloud Run, updated 2026-10-13. Checked 2026-10-16. ↩︎ ↩︎

  44. Azure Functions error handling and retries — Microsoft Learn, 2026-09-15. Checked 2026-10-16. ↩︎

  45. Billing settings for services — Google Cloud Run, updated 2026-10-13. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  46. Improving startup performance with Lambda SnapStart — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  47. Set minimum instances — Google Cloud Run, updated 2026-10-13. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  48. Interview: Michael Hausenblas on learning to walk before running into a serverless mess — DevClass, 2019-01-14. Checked 2026-10-16. ↩︎

  49. Lambda Event Function runtimes — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  50. Configuring reserved concurrency for an Event Function — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎

  51. CreateQueue — Amazon SQS API Reference. Checked 2026-10-16. ↩︎

  52. Configure a dead-letter queue redrive in Amazon SQS — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  53. Idempotency — Powertools for AWS Lambda (Python). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  54. Exactly-once processing in Amazon SQS — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎

  55. Exactly-once delivery — Google Cloud Pub/Sub, updated 2026-10-13. Checked 2026-10-16. ↩︎

  56. How do I make my Lambda function idempotent? — AWS re:Post Knowledge Center, updated 2026-05-07. Checked 2026-10-16. ↩︎

  57. The Idempotency-Key HTTP Header Field — IETF httpapi WG, expired draft -07 (revised 2025-10-15). Checked 2026-10-16. ↩︎ ↩︎

  58. Idempotent requests — Stripe API Reference. Checked 2026-10-16. ↩︎

  59. Configure Lambda Event Function timeout — AWS documentation. Checked 2026-10-16. ↩︎

  60. Change data capture for DynamoDB Streams — Amazon DynamoDB Developer Guide. Checked 2026-10-16. ↩︎ ↩︎

  61. Semantic conventions for FaaS spans — OpenTelemetry. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  62. Upgrading to Serverless Framework V4 — Serverless Inc. Checked 2026-10-16. ↩︎

  63. Batch Processing — Powertools for AWS Lambda (Python). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎

  64. AWS Lambda Power Tuning — Alex Casalboni, GitHub. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  65. Visualize Lambda invocations using AWS X-Ray — AWS documentation. Checked 2026-10-16. ↩︎

  66. AWS::Serverless::Function — AWS SAM documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  67. Amazon DynamoDB pricing for on-demand capacity — Amazon Web Services. Checked 2026-10-16. ↩︎ ↩︎

  68. Amazon SQS Pricing — Amazon Web Services. Checked 2026-10-16. ↩︎ ↩︎

  69. start-message-move-task — AWS CLI 2.38.1 Command Reference. Checked 2026-10-16. ↩︎ ↩︎

  70. Summary of the Amazon DynamoDB Service Disruption in the Northern Virginia (US-EAST-1) Region — Amazon Web Services, October 2025. Checked 2026-10-16. ↩︎ ↩︎

  71. Prime Video Switched from Serverless to EC2 and ECS to Save Costs — Rafał Gancarz, InfoQ, 2023-05-03. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  72. Available CloudWatch metrics for Amazon SQS — AWS documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎

  73. Granting function access to AWS services — AWS documentation. Checked 2026-10-17. ↩︎

  74. Control access to Lambda function URLs — AWS documentation. Checked 2026-10-16. ↩︎

  75. Working with Lambda environment variables — AWS documentation. Checked 2026-10-16. ↩︎

  76. update-event-source-mapping — AWS CLI 2.38.1 Command Reference. Checked 2026-10-16. ↩︎

  77. Serverless in the Wild — Shahrad et al., USENIX ATC 2020 (arXiv v3, 2020-06-06). Checked 2026-10-16. ↩︎

  78. Viewing metrics for Lambda Event Functions — AWS documentation. Checked 2026-10-17. ↩︎

Related Ops

NoOps

NoOps is the aspiration that automation and fully managed platforms can remove the need for a dedicated operations team. In practice it is a trade-off: operational work shrinks and moves to providers, platforms and developers, but it does not disappear.

CloudOps

CloudOps (cloud operations) is running workloads on public cloud through the provider's control plane: account hierarchy, guardrails, quotas, Regions, provider signals and managed-service change. The label is common, but no neutral body defines it.

WorkflowOps

Workflow orchestration engines and operations: DAGs, state machines, retries and idempotency, long-running and durable workflows, sagas/compensation, Temporal, Apache …

In preparation

IntegrationOps

Operating system-to-system integrations: integration patterns (point-to-point, hub-and-spoke, ESB, event-driven), message brokers (Kafka, RabbitMQ), iPaaS, idempotent …

In preparation

ObservabilityOps

ObservabilityOps is this Atlas's name for running observability as an internal product: the telemetry pipeline, standards, budgets and alert hygiene that let engineers answer new questions about production at a predictable cost. The label itself is not standardised.

FinOps

FinOps is the operating model for variable technology spend: engineering, finance, product, procurement and leadership share timely cost data and clear decision rights, so that cloud, SaaS, data-platform and AI spend buys measurable business value.

SRE (Site Reliability Engineering)

Site Reliability Engineering (SRE) runs production services as a software-engineering problem: it sets explicit reliability targets (SLOs) from users' experience and uses the remaining error budget to decide how fast to ship and when to stop and fix.

ContainerOps

ContainerOps is this Atlas's name for operating single containers below the orchestrator: building small, traceable OCI images, managing tags, digests and registries, and running containers predictably on a host's runtime, namespaces and cgroups.

Practise and get certified

From xOps School

Practise ServerlessOps and get certified

The free ServerlessOps track on xOps School has study notes, a 25-question practice quiz with explanations, and a verifiable certificate when you score 70% or more. Sign in with Google to start.

For teams

Instructor-led training

Live batches for your team, with completion certificates and reports for L&D/HR.

Source: master.md (the editorial source of this page) · built 2026-10-17 · diagrams rendered with @mermaid-js/mermaid-cli 11.17.0 (mermaid 11.17.2)