5 min read
On this page

Serverless Architecture

FaaS Deep Dive

Function as a Service is the core compute primitive of serverless architecture. The provider manages all infrastructure; developers deploy only code.

Execution Model

Request ──► API Gateway ──► Cold/Warm Start ──► Function Execution ──► Response
                                  │
                          ┌───────┴────────┐
                          │ Execution Env  │
                          │ ┌────────────┐ │
                          │ │  Runtime   │ │
                          │ │  (Node.js) │ │
                          │ ├────────────┤ │
                          │ │  Your Code │ │
                          │ │  + Deps    │ │
                          │ └────────────┘ │
                          │  /tmp storage  │
                          └────────────────┘
                          Frozen after use,
                          reused if available

Provider Comparison

| Feature | AWS Lambda | Cloud Functions 2nd Gen | Azure Functions | |---------|-----------|------------------------|-----------------| | Max timeout | 15 min | 60 min | 10 min (Consumption) | | Max memory | 10 GB | 32 GB | 1.5 GB (Consumption) | | Concurrency | 1 per instance | Configurable (up to 1000) | Configurable | | Min billing | 1 ms | 100 ms | 1 ms | | Deployment size | 250 MB (zip), 10 GB (container) | 100 MB (source) | N/A | | VPC support | Yes (improved cold start) | Yes (Serverless VPC) | Yes (VNet) |

Execution Lifecycle

INIT Phase                    INVOKE Phase           SHUTDOWN
├── Extension init            ├── Handler called     ├── Graceful
├── Runtime init              ├── Business logic     │   shutdown
├── Function init             ├── Return response    │   hooks
│   (outside handler)         └── Freeze env         └── Cleanup
└── ~100-500ms first time         ~ms-seconds

Code outside the handler runs once per execution environment and persists across invocations. Use this for database connections and SDK clients.

# Lambda: connection reuse across invocations
import boto3
dynamodb = boto3.resource('dynamodb')  # Initialized once
table = dynamodb.Table('orders')       # Reused across invocations

def handler(event, context):
    # This runs every invocation
    return table.get_item(Key={'id': event['order_id']})

Event Sources and Triggers

AWS Lambda Event Sources

| Category | Sources | Invocation | |----------|---------|------------| | API | API Gateway, ALB, Function URL | Synchronous | | Storage | S3, DynamoDB Streams, Kinesis | Async / Poll-based | | Messaging | SQS, SNS, EventBridge | Async / Poll-based | | Orchestration | Step Functions | Synchronous | | Schedule | EventBridge Scheduler | Asynchronous | | Streaming | Kafka (MSK), Kinesis | Poll-based (batched) |

Invocation Patterns

Synchronous:   Client ──► Lambda ──► Response to client
               (API Gateway, ALB)

Asynchronous:  Event ──► Lambda Service Queue ──► Lambda
               (S3, SNS, EventBridge)
               Built-in retry: 2 attempts, then DLQ

Poll-based:    Lambda polls ──► SQS/Kinesis/DynamoDB Streams
               (Batch processing with configurable batch size)

Error Handling

  • DLQ (Dead Letter Queue): Capture failed async invocations in SQS/SNS
  • Destinations: Route success/failure to Lambda, SQS, SNS, or EventBridge
  • Partial batch failure: Report individual failures in SQS/Kinesis batches
  • Idempotency: Use Powertools for deduplication via idempotency keys

Serverless Frameworks

AWS SAM (Serverless Application Model)

# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Runtime: python3.12
    Timeout: 30
    MemorySize: 256
    Tracing: Active

Resources:
  OrderFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: app.handler
      CodeUri: src/
      Events:
        ApiEvent:
          Type: Api
          Properties:
            Path: /orders
            Method: post
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref OrdersTable

  OrdersTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: id
        Type: String

AWS CDK (Cloud Development Kit)

// Define serverless API with CDK
const ordersTable = new dynamodb.Table(this, 'Orders', {
  partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
});

const orderFn = new lambda.Function(this, 'OrderFunction', {
  runtime: lambda.Runtime.NODEJS_20_X,
  handler: 'index.handler',
  code: lambda.Code.fromAsset('lambda/'),
  environment: { TABLE_NAME: ordersTable.tableName },
});

ordersTable.grantReadWriteData(orderFn);

const api = new apigateway.RestApi(this, 'OrdersApi');
api.root.addResource('orders').addMethod('POST',
  new apigateway.LambdaIntegration(orderFn));

Other Frameworks

  • Serverless Framework: Multi-cloud, plugin ecosystem, large community
  • SST (Serverless Stack): CDK-based with live Lambda debugging
  • Pulumi: General-purpose IaC with serverless support in multiple languages

Step Functions (Workflow Orchestration)

State Machine Types

Standard (default)              Express
├── Max duration: 1 year        ├── Max duration: 5 min
├── Exactly-once execution      ├── At-least-once execution
├── $0.025 per 1K transitions   ├── Based on executions + duration
└── Audit/compliance workflows  └── High-volume data processing

Common Patterns

{
  "StartAt": "ValidateOrder",
  "States": {
    "ValidateOrder": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:...:validate",
      "Next": "CheckInventory",
      "Retry": [{ "ErrorEquals": ["TransientError"], "MaxAttempts": 3 }],
      "Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "HandleError" }]
    },
    "CheckInventory": {
      "Type": "Choice",
      "Choices": [{
        "Variable": "$.inStock",
        "BooleanEquals": true,
        "Next": "ProcessPayment"
      }],
      "Default": "BackorderItem"
    },
    "ProcessPayment": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:...:payment",
      "End": true
    }
  }
}

Step Functions Patterns

| Pattern | Use Case | Implementation | |---------|----------|----------------| | Sequential | Pipeline processing | Chain Task states | | Parallel | Fan-out/fan-in | Parallel state with branches | | Map | Process collection items | Map state (inline or distributed) | | Choice | Conditional branching | Choice state with rules | | Wait | Delay or schedule | Wait state (seconds or timestamp) | | Saga | Distributed transactions | Catch + compensating actions |

Serverless Databases

| Service | Type | Scaling | Billing | |---------|------|---------|---------| | DynamoDB On-Demand | Key-value/document | Instant, automatic | Per request | | Aurora Serverless v2 | Relational | 0.5 ACU increments | Per ACU-second | | Firestore | Document | Automatic | Per operation | | Neon | PostgreSQL | Scale to zero | Per compute-second | | PlanetScale | MySQL | Automatic branching | Per row read/write |

Limitations and Mitigations

Cold Starts

| Factor | Impact | Mitigation | |--------|--------|-----------| | Language runtime | Java/C# slower than Python/Node | Choose lightweight runtimes | | Package size | Larger = slower init | Tree-shake, minimize deps | | VPC attachment | Additional ENI setup time | Use VPC-only when needed | | Concurrent burst | Many cold starts at once | Provisioned concurrency |

Timeouts

  • Lambda: 15 min max; break long tasks into Step Functions workflows
  • API Gateway: 29 second integration timeout for synchronous calls
  • Solution: Return async job ID, poll or use WebSocket for result

Other Constraints

  • Payload size: 6 MB sync, 256 KB async for Lambda invocation
  • Concurrency: Account-level limits (default 1000 concurrent)
  • Ephemeral storage: /tmp limited to 10 GB
  • Stateless: No shared memory between invocations

Cost Optimization

Pricing Levers

Lambda Cost = Invocations × (Duration × Memory)
              $0.20/1M       $0.0000166667 per GB-second

Optimization Strategies

  1. Right-size memory: Use AWS Lambda Power Tuning to find optimal memory
  2. Reduce duration: Optimize code, use connection pooling, avoid cold paths
  3. Minimize invocations: Batch SQS messages, filter events at source
  4. Use ARM (Graviton2): 20% cheaper with comparable or better performance
  5. Provisioned concurrency: Only for latency-sensitive paths (costs when idle)
  6. Tiered architecture: Not everything needs to be serverless

When Serverless Is Not Cost-Effective

  • Sustained high throughput (> millions of invocations/hour consistently)
  • Long-running processes (> 15 minutes)
  • GPU workloads or specialized hardware needs
  • When consistent sub-10ms latency is required

Serverless Patterns

| Pattern | Description | Implementation | |---------|-------------|----------------| | API backend | REST/GraphQL API | API GW + Lambda + DynamoDB | | Event processing | React to cloud events | S3 trigger + Lambda + SQS | | Scheduled tasks | Cron-like execution | EventBridge Schedule + Lambda | | Stream processing | Real-time data pipeline | Kinesis + Lambda + S3 | | Web application | Full-stack serverless | CloudFront + S3 + API GW + Lambda | | ETL pipeline | Data transformation | Step Functions + Lambda + Glue | | ChatBot/webhook | Receive and process webhooks | Function URL + Lambda |

Key Takeaways

  • FaaS provides event-driven, auto-scaling compute with per-millisecond billing
  • Initialize shared resources (DB connections, SDK clients) outside the handler
  • Step Functions orchestrate complex workflows with built-in error handling and retries
  • Cold starts are the primary latency concern; mitigate with provisioned concurrency and small packages
  • Serverless cost scales linearly with usage, making it ideal for variable/low traffic
  • Not all workloads fit serverless; evaluate duration, throughput, and latency requirements