Serverless Architecture#

Serverless doesn’t mean “no servers.” It means you don’t manage, provision, or think about servers. The provider handles scaling, patching, and availability. You write code, define triggers, and pay per execution. This section covers when serverless is the right choice, how to design serverless systems, and the anti-patterns that make serverless expensive or fragile.


Event-Driven Serverless#

Serverless functions are inherently event-driven: something happens, a function runs, and it stops. This model maps naturally to many workloads.

Common Trigger Patterns#

TriggerEvent SourceUse Case
HTTP requestAPI GatewayREST APIs, webhooks
MessageSQS, Pub/Sub, Event HubsAsync processing, fan-out
ScheduleCloudWatch Events, Cloud SchedulerCron jobs, periodic tasks
File uploadS3, GCS, Blob StorageImage processing, ETL
Database changeDynamoDB Streams, Firestore triggersMaterialised views, audit
StreamKinesis, Kafka, Event HubsReal-time analytics, enrichment

Event-Driven Architecture#

┌──────────┐     ┌───────────────┐     ┌──────────────┐
│  Client   │────►│  API Gateway  │────►│  Function A  │
└──────────┘     └───────────────┘     │  (validate   │
                                        │   & enqueue)  │
                                        └──────┬───────┘
                                               │
                                               ▼
                                        ┌──────────────┐
                                        │  Message      │
                                        │  Queue (SQS)  │
                                        └──────┬───────┘
                                               │
                              ┌────────────────┼────────────────┐
                              ▼                ▼                ▼
                       ┌────────────┐   ┌────────────┐   ┌────────────┐
                       │ Function B │   │ Function C │   │ Function D │
                       │ (process)  │   │ (notify)   │   │ (audit)    │
                       └─────┬──────┘   └─────┬──────┘   └─────┬──────┘
                             │                │                │
                             ▼                ▼                ▼
                       ┌──────────┐    ┌──────────┐    ┌──────────┐
                       │ DynamoDB │    │ SES/SNS  │    │ S3       │
                       └──────────┘    └──────────┘    └──────────┘

Cold Starts#

A cold start occurs when a function is invoked but no execution environment is available. The provider must allocate resources, load the runtime, and initialise your code.

Cold Start Breakdown#

Cold Start Timeline
├── Container allocation       (~50–200ms)    Provider
├── Runtime initialisation     (~50–500ms)    Provider
├── Dependency loading         (~100ms–5s)    Your code
├── Handler initialisation     (~10–100ms)    Your code
└── Function execution         (varies)       Your code
    Total cold start: 200ms – 10s+

Cold Start Factors#

FactorImpactMitigation
RuntimeJVM/C# cold start > Python/Node/GoUse Go or Node for latency-sensitive functions
Package sizeLarger package = slower loadMinimise dependencies, use layers/extensions
Memory allocationMore memory = faster CPU = faster cold startAllocate 512MB+ even if you don’t need the memory
VPC attachmentENI creation adds 1–10sUse VPC only when you need private resources
Provisioned concurrencyPre-warms N instancesUse for critical paths (adds cost)

Cold Start Decision Framework#

Is cold start latency acceptable for this workload?
│
├── Not latency-sensitive (async, batch, scheduled)
│   └── Accept cold starts — they're free and rare
│
├── Moderately sensitive (API behind 1s SLO)
│   ├── Use a lightweight runtime (Go, Node, Python)
│   ├── Minimise dependencies
│   └── Keep functions warm with scheduled pings
│
└── Highly sensitive (sub-100ms required)
    ├── Use provisioned concurrency
    ├── Or use containers instead (always warm)
    └── Or put the function behind a cache

Function Composition#

Individual functions are small. Real systems chain multiple functions together. The challenge is orchestrating them without creating a distributed spaghetti monster.

Composition Patterns#

PatternHow It WorksUse Case
Sequential pipelineFunction A → Queue → Function B → Queue → Function CETL, multi-step processing
Fan-out/fan-inOne event triggers N parallel functions; results aggregatedBatch processing, map-reduce
OrchestrationA workflow engine coordinates functionsComplex business processes
ChoreographyFunctions react to events independentlyLoosely coupled domains

Orchestration vs Choreography#

Orchestration (Step Functions / Workflows)
┌─────────────────────────────────────┐
│          Workflow Engine             │
│                                     │
│  Start → Validate → Process → Notify│
│            │                        │
│            ├── On error → Retry     │
│            └── On failure → Alert   │
└─────────────────────────────────────┘
Central control, explicit error handling, visible state

Choreography (Event-driven)
┌──────────┐   Event A   ┌──────────┐   Event B   ┌──────────┐
│ Service 1 │ ──────────► │ Service 2 │ ──────────► │ Service 3 │
└──────────┘             └──────────┘             └──────────┘
                                                       │ Event C
                                                       ▼
                                                 ┌──────────┐
                                                 │ Service 4 │
                                                 └──────────┘
Decentralised, loosely coupled, harder to debug

Workflow Engines#

ServiceProviderFeatures
Step FunctionsAWSVisual workflow, Express (high-throughput) and Standard modes
WorkflowsGCPYAML-based, HTTP call support, long-running
Durable FunctionsAzureCode-based orchestration in C#/Python/JS
TemporalSelf-hosted / CloudLanguage-native, replay-based, cross-cloud

Serverless Databases#

Serverless extends beyond compute. Several database services now offer pay-per-query or auto-scaling models.

DatabaseModelBest For
DynamoDB (on-demand)Pay per read/write unitUnpredictable traffic, key-value
Aurora Serverless v2Scales ACUs (Aurora Capacity Units)Variable SQL workloads
FirestorePay per document operationMobile/web backends, real-time sync
BigQueryPay per TB scannedAd-hoc analytics, data warehousing
Cosmos DB (serverless)Pay per RU consumedGlobal distribution, multi-model
Neon / PlanetScaleScale to zero PostgreSQL/MySQLDev environments, low-traffic apps

When to Use Serverless Databases#

Good FitPoor Fit
Traffic is unpredictable or spikySustained high throughput (provisioned is cheaper)
Application has idle periodsAlways-on OLTP with consistent load
Development and staging environmentsSub-millisecond latency requirements
Prototype or MVPComplex joins across large datasets

Serverless Anti-Patterns#

The Distributed Monolith#

Deploying a monolith as dozens of Lambda functions that call each other synchronously.

BAD: Lambda A → calls → Lambda B → calls → Lambda C → calls → Lambda D
     (4x cold starts, 4x latency, impossible to debug)

GOOD: Lambda A handles the full request
      OR: Use a workflow engine to orchestrate steps
      OR: Use a container if the logic is tightly coupled

The Lambda Pinball#

Events bouncing between functions with no clear flow, making the system impossible to trace or debug.

Fix: Use a workflow engine (Step Functions, Temporal) for multi-step processes. Reserve choreography for genuinely independent, loosely coupled domains.

Ignoring Concurrency Limits#

Every function has a concurrency limit (default: 1,000 on AWS). A traffic spike can exhaust the limit and throttle your entire application.

Fix: Set reserved concurrency for critical functions. Use a queue to buffer requests and smooth out spikes.

Over-granular Functions#

One function per API endpoint creates hundreds of deployable units with no shared code.

Fix: Group related endpoints into a single function or use a framework (Powertools, Chalice, SST) that deploys a cohesive service.


When Serverless Doesn’t Fit#

WorkloadWhy Serverless FailsAlternative
Long-running processes (> 15 min)Execution time limitsContainers, Step Functions
Sustained high throughputPer-invocation pricing is expensive at scaleContainers or VMs
Stateful workloadsNo local state between invocationsContainers with persistent storage
Low-latency requirements (< 10ms)Cold starts break the SLOContainers (always warm)
GPU/ML inferenceFaaS doesn’t offer GPUGPU instances or managed ML
WebSocket / long-lived connectionsStateless model doesn’t supportAPI Gateway WebSocket or containers
Monolithic applicationsNot designed for event-driven decompositionContainers or PaaS

The Crossover Point#

Cost ($)
│
│         ╱  Containers (fixed base + per-container)
│        ╱
│       ╱
│      ╱
│     ╱ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
│    ╱           Serverless (per-invocation)
│   ╱        ╱
│  ╱     ╱
│ ╱  ╱
│╱╱
├─────────────────────────────────── Requests/month
│        ↑
│   Crossover point
│   (~1M–10M requests/month, varies)

Below the crossover, serverless is cheaper (often free). Above it, containers win on per-unit economics.


Key Takeaways#

  • Serverless is ideal for event-driven, bursty, and low-traffic workloads where per-invocation pricing and scale-to-zero matter
  • Cold starts are the main latency concern — mitigate with lightweight runtimes, minimal dependencies, or provisioned concurrency
  • Use workflow engines (Step Functions, Temporal) for multi-step processes — don’t chain functions synchronously
  • Serverless databases extend the pay-per-use model to data storage; use them for unpredictable or variable traffic
  • Avoid the distributed monolith: if functions are tightly coupled, they should be a single function or a container
  • Serverless has a cost crossover point — at sustained high volume, containers are more economical