Lesson 86 of 129
Serverless and FaaS
Serverless is a model in which a cloud provider runs application code and handles provisioning, scaling, patching, and capacity, and the customer pays per use. Function as a service (FaaS) is its compute form: the developer deploys individual functions, and the platform runs an instance of a function when an event arrives. Examples: AWS Lambda, Google Cloud Functions and Cloud Run functions, Azure Functions. Servers still exist, but the customer does not manage them. Managed services with the same properties (object storage, queues, managed databases such as DynamoDB) are also called serverless.
Execution model
A function is code with a handler that receives an event and returns a result. The platform:
- Receives an event (an HTTP request, a queue message, a file upload).
- Finds an idle execution environment (a sandbox, such as a container or microVM, holding the runtime and code) for that function, or creates one.
- Invokes the handler in it.
- Keeps the environment idle for a period for reuse, then destroys it.
graph LR
E1["HTTP request"] --> P["Platform: router and scheduler"]
E2["Queue message"] --> P
E3["Object created"] --> P
P --> W1["Environment 1 (warm)"]
P --> W2["Environment 2 (warm)"]
P --> W3["Environment 3 (new: cold start)"]
W1 --> D[("Database / other services")]
W2 --> D
W3 --> DCharacteristics:
- Scaling per request. The platform adds environments as concurrent events increase, and removes them when idle, down to zero.
- Stateless. An environment may be reused, but can be destroyed at any time. Durable state must live in an external store. Local memory and disk may act as a cache only.
- Short-lived. Invocations have a maximum duration.
- One request per environment at a time in the classic FaaS model (some platforms, such as Cloud Run, can serve multiple concurrent requests per instance).
Cold starts
A cold start occurs when no warm environment is available, and the platform must create one: allocate a sandbox, download the code, start the runtime, and run initialization code. The first request pays this latency in addition to execution time. Later requests reusing the environment are warm.
Factors that influence cold start time:
- Runtime: interpreted or JIT runtimes with heavy startup (such as the JVM) usually start slower than compiled native binaries or lightweight interpreters.
- Package size, and number of dependencies loaded at start.
- Initialization work: opening connections, loading configuration and models.
- Network configuration, such as attaching the function to a private network, which can add setup time on some platforms.
- Allocated memory. On many platforms more memory comes with proportionally more CPU, which shortens initialization.
Mitigations: keep packages small, do initialization lazily or once outside the handler so warm invocations reuse it, choose fast-starting runtimes, use provisioned or minimum-instance settings that keep environments warm (at a cost), and use snapshot-based startup features where the platform offers them. Latency-sensitive paths with strict tail-latency requirements need such measures, or a different model.
Limits
Platforms impose limits. Exact values differ by provider and change over time, so check current documentation. The categories are:
- Maximum execution duration per invocation.
- Memory and CPU allocation, and ephemeral disk size.
- Maximum request and response payload size.
- Deployment package or image size.
- Concurrency limits per function and per account. Beyond these, requests are throttled.
- No inbound connections to the function except through the platform, and no long-lived listening state.
Consequences: long-running jobs must be split into steps or run elsewhere, and concurrency spikes can overload downstream systems. A relational database with a small connection limit can be exhausted when hundreds of environments each open their own connection. Use a connection proxy or pooler, or a database designed for this access pattern.
Event triggers
A function is invoked by a trigger. Categories:
| Trigger | Example | Notes |
|---|---|---|
| Synchronous HTTP | API gateway or function URL | Caller waits for the response. Cold start adds to latency. |
| Asynchronous event | Object storage notification, event bus | The platform queues the event and retries on failure. |
| Stream or queue poller | Kafka topic, SQS queue, Kinesis stream | The platform polls and invokes with batches. Partitions or queue settings limit parallelism. |
| Schedule | Cron-style rule | Periodic jobs. |
Because retries are common for asynchronous and polled triggers, delivery is at least once, and functions must be idempotent (see idempotent consumers and deduplication). Failed events are commonly sent to a dead-letter queue after retries are exhausted.
Cost model
Billing is per invocation plus per unit of resource-time: typically the configured memory multiplied by the duration, rounded to a fine granularity. There is no charge while a function is idle (unless provisioned capacity is purchased).
Example with assumed unit counts: a function configured with 512 MB (0.5 GB) that runs for 200 ms uses 0.5 x 0.2 = 0.1 GB-seconds per invocation. 10 million invocations per month use 1,000,000 GB-seconds, and the bill is that quantity times the provider's per-GB-second price, plus a per-invocation charge. If the same load ran at a steady 4 invocations per second, one small container instance could serve it continuously, and the comparison depends on the two price lists.
A simple comparison: a server costs a fixed amount per hour regardless of load. A function costs proportionally to work done.
| Load profile | Cheaper option |
|---|---|
| Sporadic or spiky traffic, long idle periods | Serverless: pay only for executions |
| Low, unpredictable volume | Serverless: no idle servers |
| Steady high utilization | Provisioned servers or containers: a fully utilized instance costs less per unit of work than per-invocation pricing |
Other cost components: API gateway or trigger charges, data transfer, provisioned concurrency, and the operational cost of many small functions. Compare using actual request rate, duration, and memory.
When to use serverless
Good fit:
- Event-driven processing: image thumbnails after upload, file parsing, webhook handlers.
- Glue between managed services, and scheduled jobs.
- Workloads with highly variable or unpredictable traffic, including scale to zero.
- Small teams that want minimal infrastructure management.
- Bursty parallel batch work.
Poor fit:
- Steady, high-throughput services where per-invocation cost exceeds the cost of running containers.
- Strict low tail latency where cold starts are unacceptable and warm capacity would remove the cost benefit.
- Long-running or stateful processes, and workloads needing persistent connections (such as WebSocket servers) or specialized hardware.
- Applications with heavy in-memory state or large startup cost.
- Cases needing full control of the runtime, networking, or portability. Function handler interfaces, triggers, and surrounding services are provider-specific, which creates lock-in.
Design considerations: functions that call other functions synchronously add latency and multiply cost, since each waits and is billed. Prefer events or queues between functions. Observability needs distributed traces across functions (see distributed tracing), because a request may cross many short-lived environments. Serverless functions and containers can coexist in one system, each used where its profile matches.
Interview questions
- What is a cold start and how is it reduced? It is the delay to create an execution environment and initialize the runtime and code before the first request runs. Reduce it with smaller packages, faster runtimes, lazy or one-time initialization, and provisioned warm capacity.
- Why must functions be stateless? The platform may create or destroy environments at any moment and runs many in parallel, so local state is neither durable nor shared. State goes to external stores.
- When is serverless more expensive than containers? For sustained high utilization, where paying per invocation and per millisecond exceeds the cost of a continuously running, well-utilized instance.
- What problem arises when functions scale out against a relational database? Each environment can open its own connection, and high concurrency exhausts the database connection limit. A pooler or proxy, and concurrency limits, address it.
- Why should function handlers be idempotent? Asynchronous and polled triggers retry on failure, so the same event can be delivered more than once.
