Node.js Serverless on AWS Lambda · Capstone Case Studies
Capstone: A Black-Friday Order Pipeline
Design an order pipeline on Lambda that survives 20x traffic without losing, duplicating or overselling orders.
Scenario
Example company: an online retailer runs checkout on Node.js Lambda functions behind API Gateway, with DynamoDB for orders and stock, a third-party payment provider, and an email service. Normal peak is 50 orders per second; Black Friday is forecast at 1,000 orders per second for the first hour.
Last year's problems: duplicate charges after client retries, overselling of popular items, the payment provider rate-limiting the platform (it allows 300 requests per second), and confirmation emails arriving hours late.
Requirements
- Accept orders quickly (p99 under 1 second for the API response) even when downstreams are slow.
- Never oversell stock and never charge twice for one order.
- Respect the payment provider's 300 requests per second limit.
- Every accepted order is eventually charged and confirmed, or explicitly cancelled with a notification.
- Operators can see the state of any order and the backlog at any time.
Constraints
- Node.js on Lambda, DynamoDB, SQS, EventBridge and Step Functions are available; no new databases.
- Account concurrency quota can be raised to 3,000 if requested two weeks in advance.
- The team has three engineers and four weeks.
Deliverables
- The architecture: request path, queues, workflows and data model, with a diagram or step list.
- How you guarantee no overselling and no double charges, including under retries and crashes.
- How you protect the payment provider and other downstreams while the backlog drains.
- Concurrency, quotas and cost: the settings you would change and why.
- Observability and the load test plan before the event, and what you watch during it.
Hints
- The API does not need to finish the whole order; it needs to accept it durably.
- DynamoDB transactions and condition expressions can reserve stock atomically.
- An SQS event source's maximum concurrency is a throttle you control.
Draft each deliverable below before revealing the model answer and rubric.
Further reading: Using Lambda with Amazon SQS · Amazon DynamoDB transactions
Deliverables
Describe the architecture.
Model answer
POST /orders(HTTP API + Lambda) validates input, requires an Idempotency-Key, and in one DynamoDB transaction writes the order (status = PENDING_PAYMENT) and conditionally decrements stock (stock >= qty). Returns202 Acceptedwith the order ID in well under a second.- A DynamoDB Stream or an explicit
OrderPlacedevent (outbox) puts the order on an SQS queue for payments. - A payment worker (SQS event source, partial batch responses, DLQ) charges the card with the provider's idempotency key = order ID, then updates the order to
PAIDwith a condition onPENDING_PAYMENT. OrderPaidevents on EventBridge fan out to email (its own queue and worker) and fulfilment.- A Step Functions standard workflow (or a scheduled sweeper) handles timeouts: orders stuck in
PENDING_PAYMENTtoo long are cancelled, stock is released and the customer is notified.
Rubric
Look for: asynchronous acceptance (202) with durable storage first, queues between the API and slow downstreams, events for fan-out, and an explicit path for stuck orders. Misses: charging the card inside the API request, synchronous chains, no handling of orders that never complete.
Guarantee no overselling and no double charges.
Model answer
- Overselling: the stock decrement is a conditional update (
stock >= :qty) inside the same transaction as the order insert; either both happen or neither. Cancellations increment stock with a condition on the order's status to avoid double releases. - Double charges: Powertools Idempotency on the API (Idempotency-Key header) so client retries return the original order; the payment call uses the order ID as the provider's idempotency key, so SQS redeliveries and worker retries cannot charge twice; the status update to
PAIDis conditional. - Crashes between charge and status update are safe: the redelivered message calls the provider again with the same key, which returns the original charge.
Rubric
Strong answers use database-enforced atomicity and idempotency keys at each boundary (client->API, worker->provider). Weak answers rely on 'checking first' (read-then-write) or on SQS FIFO alone for exactly-once.
Protect the payment provider and other downstreams.
Model answer
- Set the payment queue's event source maximum concurrency so that concurrency x per-invocation rate stays below 300 requests per second (for example 60 concurrent workers each making about 4 calls per second with batch size 1-5), and handle 429s with backoff by returning failed items.
- The queue absorbs the burst; the backlog drains at the provider's pace. Communicate expected confirmation delays.
- Separate queues and concurrency caps for email so a slow email service never delays payments.
Rubric
Look for concrete numbers derived from the 300 rps limit and the use of queue-level concurrency caps rather than hoping retries will smooth things out.
Concurrency, quotas and cost.
Model answer
- Request the account concurrency increase in advance; give the API function reserved concurrency (for example 1,000) and enough provisioned concurrency for the first minutes to avoid cold-start spikes.
- Cap the workers (payments 60, email 50) with event source maximum concurrency.
- DynamoDB on-demand (or provisioned with pre-warming) with keys that avoid hot partitions; stock items for popular products may need write sharding or a reservation approach.
- Cost: dominated by Lambda duration and DynamoDB writes; acceptable for one day, and provisioned concurrency is scheduled only for the event window.
Rubric
Good answers allocate concurrency per function deliberately, think about DynamoDB hot keys for popular items, and time-box expensive settings.
Observability and load testing.
Model answer
- Load test at 1.5x forecast in a production-like account with the provider's sandbox, including failure injection (provider 429s and timeouts).
- Dashboards: accepted orders per second, queue depth and age per queue, payment success and failure rates, orders by status, DLQ depth, throttles, API p99.
- Alarms: DLQ not empty, oldest message age above SLO, payment failure rate, orders stuck in PENDING_PAYMENT.
- Structured logs with order ID and correlation ID across all functions so any order can be traced end to end.
Rubric
Look for queue-age and stuck-state signals (not only error counts), and a load test that includes downstream failures.
Open in the app to mark lessons complete, take notes and track your progress.