Search…

Serverless architecture patterns

In this series (10 parts)
  1. Cloud fundamentals and the shared responsibility model
  2. Compute: VMs, containers, serverless
  3. Networking in the cloud
  4. Cloud storage services
  5. Managed databases in the cloud
  6. Cloud IAM and access control
  7. Serverless architecture patterns
  8. Cloud cost management
  9. Multi-cloud and cloud-agnostic design
  10. Cloud Well-Architected Framework

You deploy a function. You do not choose an instance type, configure an operating system, or patch anything. A request arrives and the platform spins up your code, runs it, then tears it down. You pay only for the milliseconds your code executes. That is the serverless promise. It delivers on that promise more often than critics admit, but it also fails in ways that catch teams off guard.

What serverless actually means

Serverless does not mean “no servers.” It means you do not manage them. The cloud provider handles provisioning, scaling, patching, and availability. You write functions, define triggers, and the platform does the rest.

The model shifts operational complexity from the consumer to the provider. You stop worrying about capacity planning and start worrying about function design, cold starts, and vendor-specific event schemas.

Event-driven triggers

Every serverless function needs an event source. The trigger determines when and why your code runs.

Common triggers include:

  • HTTP requests via API gateways
  • Message queue events from SQS, Pub/Sub, or Event Hubs
  • Object storage events when files are uploaded or modified
  • Database change streams from DynamoDB Streams or Firestore triggers
  • Scheduled timers running functions on a cron schedule
  • IoT device messages from connected hardware
graph LR
  A["User uploads
image to bucket"] --> B["Storage event
trigger fires"]
  B --> C["Resize function
executes"]
  C --> D["Save thumbnail
to output bucket"]
  D --> E["Database trigger
fires"]
  E --> F["Metadata function
updates catalog"]
  F --> G["Queue message
sent"]
  G --> H["Notification function
alerts user"]

  style A fill:#4a9,color:#fff
  style C fill:#49a,color:#fff
  style F fill:#49a,color:#fff
  style H fill:#49a,color:#fff

An event-driven serverless pipeline. Each step triggers the next through cloud events, not direct invocation.

The pipeline above processes an image upload through three functions. No function knows about the others. They communicate through events. This loose coupling means you can replace any function without touching the rest.

Cold starts and mitigations

When no warm instance exists, the platform must create one. It downloads your deployment package, initializes the runtime, loads your dependencies, and runs your handler. This delay is the cold start.

Cold start duration depends on several factors:

Factor                   Impact
--------------------------------------------
Runtime language         JVM-based: 1-5s, Python/Node: 100-500ms
Package size             Larger packages take longer to load
VPC attachment           Adds 1-10s in some providers
Memory allocation        More memory means faster CPU, faster init
Provisioned concurrency  Eliminates cold starts entirely

Mitigations fall into three categories.

Keep functions small. Strip unused dependencies. Use tree-shaking. A 5 MB deployment package initializes faster than a 50 MB one.

Provisioned concurrency. Cloud providers let you pre-warm a set number of instances. These stay initialized and ready. You pay for idle time but eliminate cold start latency. Use this for latency-sensitive endpoints.

Runtime choice. Python, Node.js, and Go initialize fast. Java and .NET have heavier runtimes. If cold start latency matters, pick a lightweight runtime or use GraalVM native images to reduce JVM startup.

Function composition

Real workloads rarely fit in a single function. You need to chain functions together, handle errors, add retries, and manage state across steps. Direct function-to-function calls create tight coupling and make error handling painful.

Orchestration services solve this. AWS Step Functions, GCP Workflows, and Azure Durable Functions let you define state machines that coordinate multiple functions.

A Step Functions workflow looks like this conceptually:

{
  "StartAt": "ValidateOrder",
  "States": {
    "ValidateOrder": {
      "Type": "Task",
      "Next": "ProcessPayment"
    },
    "ProcessPayment": {
      "Type": "Task",
      "Next": "ShipOrder",
      "Retry": [{"ErrorEquals": ["PaymentTimeout"], "MaxAttempts": 3}],
      "Catch": [{"ErrorEquals": ["States.ALL"], "Next": "HandleFailure"}]
    },
    "ShipOrder": {
      "Type": "Task",
      "End": true
    },
    "HandleFailure": {
      "Type": "Task",
      "End": true
    }
  }
}

The orchestrator manages retries, error routing, and state between steps. Individual functions stay simple. Each one does a single thing.

Parallel execution is also built in. You can fan out to multiple functions, wait for all to complete, then aggregate results. This pattern works well for batch processing where items are independent.

The cost model

Serverless pricing has three components: invocations, compute duration, and data transfer.

Invocations are charged per request. A typical rate is $0.20 per million requests. This component is almost negligible for most workloads.

Compute duration is charged per GB-second. You pick a memory size, the provider allocates proportional CPU, and you pay for each 1 ms increment your function runs. A function using 512 MB for 200 ms costs about $0.0000017 per invocation.

Data transfer follows standard cloud egress pricing. Responses leaving the cloud cost money. Internal transfers between services in the same region are usually free.

Serverless costs less at low volumes. At high volumes, always-on compute wins on price.

The crossover point depends on your function duration and memory. For a 128 MB function that runs 100 ms, serverless stays cheaper up to roughly 30 million requests per month. Beyond that, a dedicated container or VM costs less per request.

Step Functions and workflow patterns

Orchestration services support several workflow patterns.

Sequential: steps run one after another. Order validation, then payment, then fulfillment.

Parallel: multiple branches execute simultaneously. Resize an image into three sizes at once.

Choice: conditional branching based on function output. If the payment amount exceeds a threshold, route to manual review.

Map: iterate over a collection. Process each item in a batch with a separate function invocation.

Wait: pause execution for a duration or until a specific time. Hold an order for customer confirmation.

These patterns compose. A real workflow might start sequential, fan out to parallel branches, each with their own choice states, then converge for a final step. The orchestrator tracks execution state, which means functions remain stateless.

When serverless is a bad fit

Serverless is not universal. Certain workloads suffer under the model.

Long-running processes. Most providers cap function execution at 15 minutes. Batch jobs that run for hours need containers or VMs.

Steady high-throughput traffic. When your baseline load is thousands of requests per second around the clock, you pay a premium for serverless. Containers with autoscaling handle this more cost-effectively.

Stateful workloads. Functions are ephemeral. If your process needs to hold state in memory across requests, serverless forces you to externalize that state to a database or cache, adding latency and complexity.

GPU or specialized hardware. Serverless platforms generally do not offer GPU instances. ML inference at scale needs dedicated compute.

Low-latency requirements under 10 ms. Cold starts make sub-10ms response guarantees impossible without provisioned concurrency, which partially negates the serverless benefits.

Designing for failure

Every external call from a serverless function can fail. The function itself can time out. The trigger can deliver the same event twice. Build defensively.

Idempotency. Design every function so that running it twice with the same input produces the same result. Use idempotency keys in databases or deduplication tokens in queues.

Timeouts. Set aggressive timeouts on external calls. A function that waits 60 seconds for a downstream service wastes money and blocks concurrency.

Dead letter queues. Route failed events to a DLQ instead of dropping them. This gives you a place to inspect failures and replay events after fixing bugs.

Retries with backoff. Orchestration services handle this natively. For direct invocations, implement exponential backoff with jitter to avoid thundering herds.

Observability

Serverless functions produce logs, but tracing a request across ten functions is hard without distributed tracing. Inject correlation IDs into every event. Use cloud-native tracing services or OpenTelemetry to connect the dots.

Monitor these metrics:

  • Invocation count: are functions being triggered as expected?
  • Duration: is latency creeping up?
  • Error rate: are functions failing more than the baseline?
  • Throttles: is the platform rejecting invocations due to concurrency limits?
  • Cold start percentage: how often are users hitting cold instances?

What comes next

Serverless can dramatically reduce costs at low scale, but at high scale the equation flips. The next article on cloud cost management digs into reserved instances, spot pricing, Savings Plans, and FinOps practices that keep cloud spending under control regardless of architecture.

Start typing to search across all content
navigate Enter open Esc close