Cold Starts bei Serverless: Ursachen, Kosten und Gegenmaßnahmen
Cold Starts sind die versteckte Steuer des Serverless-Modells — Ursachen, Kosten in Latenz und praktische Strategien, um Funktionen warmzuhalten.

Der erste Aufruf einer Serverless-Funktion nach einer Phase der Inaktivität dauert deutlich länger als die nachfolgenden Aufrufe. Dieser „Cold Start" umfasst das Bereitstellen eines Containers, das Laden der Runtime, das Initialisieren der Abhängigkeiten und das Ausführen des Initialisierungscodes. Bei AWS Lambda kann ein Cold Start je nach gewählter Runtime, Paketgröße und VPC-Konfiguration zwischen 100 ms und mehreren Sekunden hinzufügen.
Was bei einem Cold Start passiert
Ein Serverless-Aufruf folgt diesem Ablauf: Die Plattform empfängt die Anfrage, prüft, ob ein warmer Container verfügbar ist, und stellt andernfalls einen neuen bereit. Der kalte Pfad umfasst das Herunterladen des Deployment-Pakets, das Starten der Runtime und das Ausführen von Code auf Modulebene, bevor der Handler die Anfrage verarbeiten kann.
// Everything outside the handler runs during cold start
import { DynamoDB } from "@aws-sdk/client-dynamodb"; // ~200ms import
import { S3Client } from "@aws-sdk/client-s3"; // ~150ms import
const dynamodb = new DynamoDB({}); // Connection initialization
const s3 = new S3Client({});
// This handler runs on EVERY invocation (warm or cold)
export const handler = async (event: APIGatewayEvent) => {
// Handler code here
return { statusCode: 200, body: "OK" };
};Cold-Start-Auswirkungen messen
Der Unterschied zwischen kalten und warmen Antworten lässt sich mit strukturiertem Logging messen.
// ❌ No visibility into cold starts
export const handler = async (event: unknown) => {
const result = await processRequest(event);
return result;
};
// ✅ Track cold starts explicitly
let isFirstInvocation = true;
export const handler = async (event: unknown) => {
const start = Date.now();
const isCold = isFirstInvocation;
isFirstInvocation = false;
const result = await processRequest(event);
console.log(JSON.stringify({
coldStart: isCold,
duration: Date.now() - start,
functionName: process.env.AWS_LAMBDA_FUNCTION_NAME,
memoryAllocated: process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE,
}));
return result;
};Strategie 1 zur Minderung: Paketgröße reduzieren
Das Deployment-Paket muss heruntergeladen und entpackt werden, bevor die Runtime startet. Kleinere Pakete bedeuten schnellere Cold Starts.
// ❌ Importing the entire AWS SDK v2 (~70MB)
import AWS from "aws-sdk";
const dynamodb = new AWS.DynamoDB();
// ✅ Import only what you need (SDK v3 modular imports ~3MB)
import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
const client = new DynamoDBClient({});Tree-Shaking und das Bundling des Funktionscodes mit esbuild oder ähnlichen Tools können die Paketgröße drastisch reduzieren:
# Bundle with esbuild — output is a single file with only used code
npx esbuild src/handler.ts \
--bundle \
--platform=node \
--target=node18 \
--outfile=dist/handler.js \
--minify \
--external:@aws-sdk/* # SDK v3 is included in Lambda runtimeStrategie 2 zur Minderung: Lazy Initialization (verzögerte Initialisierung)
Nicht jeder Aufruf benötigt jede Abhängigkeit. Aufwendige Ressourcen sollten nur dann initialisiert werden, wenn der jeweilige Codepfad sie tatsächlich benötigt.
// ❌ Always initializing S3 even if most invocations don't use it
const s3 = new S3Client({});
const dynamodb = new DynamoDBClient({});
export const handler = async (event: APIGatewayEvent) => {
if (event.path === "/upload") {
await s3.send(new PutObjectCommand(uploadParams));
}
// 90% of invocations only use DynamoDB
return dynamodb.send(new GetItemCommand(params));
};
// ✅ Lazy initialization — S3 client created only when needed
const dynamodb = new DynamoDBClient({});
let _s3: S3Client | null = null;
function getS3Client(): S3Client {
if (!_s3) _s3 = new S3Client({});
return _s3;
}
export const handler = async (event: APIGatewayEvent) => {
if (event.path === "/upload") {
await getS3Client().send(new PutObjectCommand(uploadParams));
}
return dynamodb.send(new GetItemCommand(params));
};Strategie 3 zur Minderung: Provisioned Concurrency
Für latenzkritische Pfade hält Provisioned Concurrency eine festgelegte Anzahl von Funktionsinstanzen dauerhaft warm — dadurch werden Cold Starts vollständig eliminiert, allerdings auf Kosten der Bezahlung für ungenutzte Rechenleistung.
# serverless.yml — provisioned concurrency configuration
functions:
api:
handler: dist/handler.handler
memorySize: 512
provisionedConcurrency: 5 # 5 instances always warm
events:
- http:
path: /api/{proxy+}
method: ANYStrategie 4 zur Minderung: Auswahl von Arbeitsspeicher und Runtime
Eine höhere Speicherzuweisung erhöht proportional die CPU-Leistung, wodurch sich die Initialisierung beschleunigt.
// CloudFormation / SAM template
// Doubling memory from 128MB to 256MB can cut cold start time in half
// The extra cost per invocation is often offset by faster execution
// Runtime comparison (approximate cold start overhead):
// Python 3.x: ~200ms
// Node.js 18.x: ~250ms
// Go 1.x: ~100ms (compiled binary)
// Java 17: ~800ms (JVM startup)
// .NET 6: ~400ms
// Choose your runtime based on cold start toleranceCold Starts in einer VPC
Das Anbinden einer Lambda-Funktion an eine VPC verlängerte Cold Starts historisch um 5 bis 10 Sekunden, bedingt durch die Erstellung der ENI (Elastic Network Interface). AWS hat dies mit Hyperplane ENI verbessert, doch Funktionen innerhalb einer VPC weisen weiterhin eine höhere Cold-Start-Latenz auf. Eine Funktion sollte nur dann an eine VPC angebunden werden, wenn sie tatsächlich auf VPC-Ressourcen zugreifen muss.
Die wichtigsten Erkenntnisse
- Cold Starts sind Initialisierungs-Overhead – die Bereitstellung des Containers, der Start der Runtime und das Laden der Module tragen allesamt dazu bei
- Paketgröße reduzieren – Abhängigkeiten bündeln und per Tree-Shaking bereinigen, modulare SDK-Imports verwenden
- Ressourcenintensive Abhängigkeiten verzögert initialisieren – Clients nur für die Codepfade erstellen, die sie tatsächlich benötigen
- Provisioned Concurrency eliminiert Cold Starts – auf Kosten der Bezahlung für dauerhaft aktive Rechenleistung
- Mehr Speicher bedeutet schnellere Initialisierung – die CPU-Leistung skaliert linear mit dem zugewiesenen Speicher
- VPCs vermeiden, sofern nicht notwendig – die Anbindung an eine VPC verursacht weiterhin einen messbaren Cold-Start-Overhead


