dissected.io
Lesson 12 of 23
Intermediateintermediateserverlesslambdacloud-functionsfaas11 min read

Serverless Functions — Lambda vs Cloud Functions vs Azure Functions

Serverless functions are the highest abstraction that still lets you run your own code. You write a function, attach it to an event source, and the platform handles provisioning, scaling, patching, and billing. No containers to build (though they're used under the hood), no clusters to manage, no capacity planning. The model is simple: event arrives, function instance executes, you pay for the milliseconds of compute used, and at zero traffic you pay zero.

Serverless — Lambda Connection Pool Exhaustion

The Execution Model

Understanding the execution model prevents a class of bugs. When a function is invoked for the first time (or after a period of inactivity), the platform creates a new execution environment — this is a cold start. It downloads your code, initializes the runtime, and runs your module-level / global-scope code. Then it calls your handler function with the event payload.

Here's the key: global-scope code runs once per instance, and handler code runs once per request. The instance sticks around for subsequent invocations (a warm start), so global-scope work — database connections, SDK clients, loaded configuration — is reused. This is by design. Initialize expensive resources outside the handler.

import boto3

# Global scope: runs once per cold start, reused across warm invocations
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('users')

def handler(event, context):
    # Handler scope: runs every invocation
    user_id = event['pathParameters']['id']
    response = table.get_item(Key={'id': user_id})
    return {'statusCode': 200, 'body': json.dumps(response['Item'])}

Cold starts typically add 100ms-1s for interpreted languages (Python, Node.js) and 1-10s for compiled languages with heavy runtimes (Java, .NET). The dominant factors are deployment package size, runtime initialization, and any work your global-scope code does (opening DB connections, loading ML models).

The Concurrency Trap

This is where serverless gets teams into trouble. Most serverless platforms follow a one request per instance model. If 1,000 requests arrive simultaneously, the platform creates 1,000 instances. Each instance opens its own database connection. Your database, which was sized for 50 connections, now has 1,000 connection attempts, and everything falls over.

The fix: connection pooling via a proxy. AWS provides RDS Proxy specifically for this. You can also use PgBouncer, ProxySQL, or simply design your functions to talk to connection-friendly backends (DynamoDB, HTTP APIs, queues) rather than connection-limited relational databases.

Provisioned concurrency (Lambda) pre-warms a set number of instances, eliminating cold starts for that capacity and giving you predictable connection counts. You pay for the provisioned instances whether they're serving traffic or not — it's a tradeoff between latency and cost.

Event Sources

Functions shine when triggered by events rather than serving as HTTP APIs. The common triggers:

  • HTTP (API Gateway, Cloud Endpoints): request-response, the most common but least interesting use
  • Queues (SQS, Pub/Sub, Service Bus): process messages asynchronously, natural backpressure via batch size
  • Storage events (S3, Cloud Storage, Blob Storage): trigger on file upload — resize images, parse CSVs, transcode video
  • Schedules (CloudWatch Events, Cloud Scheduler): cron jobs without a server — nightly reports, cleanup tasks
  • Streams (Kinesis, Kafka, Event Hubs): process ordered event streams with batching and checkpointing
  • Database changes (DynamoDB Streams, Firestore triggers): react to data mutations in real time

Limits That Shape Architecture

Serverless functions have hard limits that constrain what you can build. Timeout ceilings: Lambda allows up to 15 minutes, Cloud Functions up to 60 minutes (2nd gen), Azure Functions has no hard limit on premium plans but 10 minutes on consumption. If your workload takes longer, you need Step Functions, Workflows, or Durable Functions to chain steps.

Memory and CPU are coupled. On Lambda, you set memory from 128MB to 10GB, and CPU scales proportionally. At 1,769MB you get one full vCPU. Counter-intuitively, increasing memory can make functions cheaper — a function that takes 10 seconds at 128MB might take 1 second at 1,024MB, and the 1-second execution at 8x memory costs less than the 10-second execution at 1x memory.

Deployment package size: Lambda allows 50MB zipped (250MB unzipped), or up to 10GB with container images. GCP Cloud Functions has a 500MB source limit. Keep dependencies lean.

AWS Implementation (Lambda)

Lambda supports Node.js, Python, Java, .NET, Go, Ruby, and custom runtimes via container images. It integrates with virtually every AWS service as an event source. Lambda@Edge and CloudFront Functions run at CDN edge locations for low-latency transformations. Lambda Layers let you share common dependencies across functions. Step Functions orchestrate multi-step workflows with error handling, retries, and parallel execution.

Lambda billing: per request ($0.20/million) plus per GB-second of compute. The free tier is generous — 1 million requests and 400,000 GB-seconds per month, permanently.

GCP Implementation (Cloud Functions)

Cloud Functions has been effectively merged with Cloud Run — 2nd gen Cloud Functions are Cloud Run services under the hood, giving you the same concurrency model (one instance can handle multiple requests), VPC connectivity, and longer timeouts. This makes GCP's "serverless function" closer to a serverless container with a function-shaped API. Eventarc provides a unified eventing layer, and Workflows handles orchestration.

Azure Implementation (Azure Functions)

Azure Functions supports the broadest runtime set: C#, JavaScript, Python, Java, PowerShell, and custom handlers. The Consumption plan gives you true scale-to-zero billing. The Premium plan adds pre-warmed instances, VNet integration, and no timeout limits. Durable Functions (an extension) provides stateful orchestration patterns — fan-out/fan-in, human interaction workflows, and function chaining — without a separate orchestration service.

Azure Functions has a unique binding system — declarative input/output bindings let you read from a queue, write to a database, and send a message to another queue without any SDK code, just configuration.

Side-by-Side Comparison

AspectLambda (AWS)Cloud Functions (GCP)Azure Functions
Max timeout15 min60 min (2nd gen)Unlimited (Premium)
Concurrency1 req/instanceMulti-concurrent (2nd gen)1 req/instance (Consumption)
Max memory10 GB32 GB (2nd gen)14 GB (Premium)
Cold start mitigationProvisioned concurrencyMin instances (Cloud Run)Pre-warmed instances (Premium)
OrchestrationStep FunctionsWorkflowsDurable Functions
Edge executionLambda@Edge, CloudFront FunctionsN/AN/A
Free tier1M requests + 400K GB-s/mo2M invocations/mo1M requests/mo
Container image supportYes (up to 10GB)Yes (via Cloud Run)Yes

When Serverless Gets Expensive

Serverless is cheap at low traffic and expensive at sustained high traffic. The crossover point where a dedicated VM becomes cheaper depends on your workload, but a rough guide: if a function runs continuously for more than 30-40% of the month, a reserved VM or container is cheaper. Serverless per-invocation billing adds up when you're processing millions of events per hour at scale.

The cost comparison is not just compute price, though. Factor in the operational cost you're not paying — no patching, no scaling configuration, no on-call for infrastructure issues. For many teams, paying 2x the raw compute cost is worth it for the operational simplicity.

Anti-Patterns

Distributed monoliths: breaking a monolith into 200 Lambda functions that call each other synchronously over HTTP. You've added network latency, failure modes, and debugging complexity while gaining nothing. Use queues for async communication between functions.

Functions calling functions synchronously: Function A calls Function B which calls Function C. Each one is paying for idle time waiting for the downstream call. Use an orchestrator (Step Functions, Workflows, Durable Functions) or an event-driven architecture instead.

Huge deployment packages: a 200MB Lambda with half of npm. Cold starts balloon, iteration speed drops, and you're probably importing things you don't use. Use tree-shaking, split heavy dependencies into layers, or question whether this workload belongs in a function.

Key Takeaways

  • Global-scope code runs once per instance (cold start); handler code runs per invocation — initialize expensive resources outside the handler
  • One request per instance means 1,000 concurrent requests create 1,000 database connections — use connection proxies or connection-friendly backends
  • Memory and CPU are coupled — increasing memory can actually decrease cost by reducing execution time proportionally more
  • Event-driven triggers (queues, storage events, streams) are where serverless functions shine; HTTP APIs are fine but less compelling compared to containers
  • Serverless is cheaper at low/bursty traffic and more expensive at sustained high throughput — the crossover point is roughly 30-40% utilization
  • Avoid distributed monoliths and synchronous function chains — use orchestrators and asynchronous patterns instead

Next, we'll move into cloud networking — VPCs, subnets, and how your workloads communicate securely.

Enjoyed this breakdown?

Get new lessons in your inbox.