AWS Lambda Cold Start Optimizasyonu: Production Dersleri
Production ortamlarından öğrenilen AWS Lambda cold start optimizasyon stratejileri. Runtime seçimi, provisioned concurrency ve pratik optimizasyon teknikleri.
Cold start gecikmesinin faturasını kullanıcı öder. İnteraktif bir API’nin arkasındaki bir Lambda function’ında en ucuz kazançlar üç yerden gelir: seçtiğin runtime, deployment package boyutu ve handler çalışmadan önce initialization kodunun yaptığı iş. Önce bu üçüyle uğraş.
Provisioned concurrency, kapsadığı isteklerin üstünden init maliyetini alır; latency SLA’i olan bir checkout endpoint’i için doğru cevap da budur. Provisioned sayısını aşan trafik on-demand environment’lara taşar ve orada yine cold start’a yakalanabilir; yani scaling policy en az rezervasyon kadar önemli. Yalnız faturayı, devreye almadan önce hesap yapmayı gerektirecek kadar değiştiriyor.
Cold Start’ın Bedeli Nerede Ödenir#
Ödeme akışlarında tanıdık bir tablo çıkar ortaya. Trafik yükselir, Lambda bunu karşılamak için yeni execution environment’lar açar ve her yeni environment handler isteği görmeden önce init maliyetini öder. Ortanca değer yerinde dururken kuyruk uzar; yavaş bir checkout çağrısını hiçbir retry politikası bekleyen kişiden gizleyemez.
Asenkron işler aynı gecikmeyi kimse fark etmeden soğurur. Bazı invocation’larda bir saniye fazla harcayan bir SQS consumer’ı kuyruğu yine boşaltır. Bir function’ın ne kadar emeği hak ettiğine işte bu fark karar verir.
Cold Start Temellerini Anlamak#
Cold Start Sırasında Ne Oluyor#
Lambda yeni bir execution environment oluşturduğunda, handler çağrılmadan önce Init aşaması çalışır:
- Deployment package’ını ve bağlı layer’ları indir.
- Runtime’ı başlat (Node.js, Python vb.).
- Initialization kodunu çalıştır: modül seviyesindeki import’lar, client oluşturma, veritabanı bağlantıları.
- Event’i handler’a devret.
Cold start dediğimiz şey 1-3 arası adımlar. Kontrol edebildiklerin ise 1 ve 3.
Init süresi runtime’a ve package boyutuna göre değişir. Yaygın olarak raporlanan aralıklar şöyle kümeleniyor:
- Node.js 22: Tipik 200-800ms
- Python 3.12: Tipik 300-1200ms
- Java 21: 1-4 saniye (evet, gerçekten)
- Go: 100-400ms (hız şampiyonu)
Runtime Seçim Stratejisi#
Runtime’ların performance karakteristiğine bakınca öne çıkanlar şöyle:
Yeni projeler için:
- Node.js 22: Performance ve ekosistem dengesi en iyi
- Go: Başlatma süresi kritikse bunu seç
- Python: Sadece ekip uzmanlığı gerektiriyorsa
Latency-sensitive workload’lar için kaçın:
- Java: SnapStart optimizasyonuna yatırım yapmaya hazır değilsen
- .NET: Cold start’lar öngörülemez olabiliyor
// KÖTÜ: dependency yüklemesi handler'ın içine ertelenmiş
export const handler = async (event) => {
const { DynamoDBClient } = await import('@aws-sdk/client-dynamodb');
const { format } = await import('date-fns');
// Bunun bedelini her environment'taki ilk invocation öder ve bu süre
// Init aşaması yerine bir kullanıcı isteğinin üstüne biner.
};
// İYİ: modül seviyesinde, yani Init sırasında çalışır
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { format } from 'date-fns';
const client = new DynamoDBClient({});
export const handler = async (event) => {
// Sadece istek işi
};
Provisioned Concurrency: Ne Zaman ve Nasıl#
Provisioned Concurrency için Business Case#
Provisioned Concurrency kullan:
- SLA gereklilikleri olan user-facing API’lar
- İnsan etkileşimiyle tetiklenen function’lar
- Peak trafik desenleri öngörülebilir
- Kötü UX maliyeti > provisioned concurrency maliyeti
Provisioned Concurrency atlama:
- Async işlemler (SQS, EventBridge)
- Batch job’lar ve data processing
- Gevşek SLA’li internal API’lar
- Öngörülemeyen trafik desenleri olan function’lar
CloudFormation’da Provisioned Concurrency#
Tek başına bir provisioned concurrency kaynağı yok. Bu bir version ya da alias özelliği; yani yayınlanmış bir version’a ve trafiği yönlendirebileceğin sabit bir hedefe ihtiyacın var:
# CloudFormation template
Resources:
PaymentProcessorFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: nodejs22.x
Handler: index.handler
MemorySize: 1024 # Çoğu workload için sweet spot
Timeout: 30
Role: !GetAtt PaymentProcessorRole.Arn
Code:
S3Bucket: !Ref ArtifactBucket
S3Key: !Ref ArtifactKey # Build hash'ini taşır, yani her build yeni bir obje
# Buradaki her özellik replacement gerektirir ve yeni version'ı yayınlayan şey
# bu replacement; o yüzden CodeSha256 artifact ile birlikte değişmeli
PaymentProcessorVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref PaymentProcessorFunction
CodeSha256: !Ref ArtifactSha256
# Provisioned concurrency alias'in bir özelliği
PaymentProcessorAlias:
Type: AWS::Lambda::Alias
Properties:
Name: provisioned
FunctionName: !Ref PaymentProcessorFunction
FunctionVersion: !GetAtt PaymentProcessorVersion.Version
ProvisionedConcurrencyConfig:
ProvisionedConcurrentExecutions: 50 # Muhafazakar başla
# Trafik spike'ları için auto-scaling
ProvisionedConcurrencyTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
# Alias adı aşağıda düz metin, yani sırayı elle bildirmek gerekiyor
DependsOn: PaymentProcessorAlias
Properties:
MaxCapacity: 200
MinCapacity: 20
ResourceId: !Sub 'function:${PaymentProcessorFunction}:provisioned'
ScalableDimension: lambda:function:ProvisionedConcurrency
ServiceNamespace: lambda
ProvisionedConcurrencyPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: pc-utilization
PolicyType: TargetTrackingScaling
ScalingTargetId: !Ref ProvisionedConcurrencyTarget
TargetTrackingScalingPolicyConfiguration:
TargetValue: 0.7
PredefinedMetricSpecification:
PredefinedMetricType: LambdaProvisionedConcurrencyUtilization
Burada üç ayrıntı insanı yakıyor. Yalnızca kodun değiştiği bir deploy kendi başına yeni bir version yayınlamaz: AWS::Lambda::Version ancak özelliklerinden biri değiştiğinde yeniden oluşturulur, yani CodeSha256 yeni artifact’ın hash’ini taşımazsa alias, üzerindeki provisioned kapasiteyle birlikte eski kodu sunmaya devam eder. Çağıranların alias’i invoke etmesi gerekiyor, çünkü provisioned üzerindeki kapasite $LATEST’e düşen istekler için hiçbir şey yapmaz. Bir de scalable target tek başına yalnızca kaynağı kaydeder; scaling policy olmadan minimum ve maksimum, template’te duran iki sayıdan ibaret kalır.
Provisioned Concurrency Ne Kadara Mal Oluyor#
Provisioned concurrency başlatılmış environment’ları hazırda bekletir; oraya yönlenen istekler Init aşamasını atlar. Karşılığında fatura artık sıfıra inmiyor.
Aşağıdaki liste fiyatları us-east-1 ve x86 için; kendi bölgen için yeniden hesaplamakta fayda var:
- On-demand duration: GB-saniye başına $0,0000166667, artı milyon istek başına $0,20
- Provisioned concurrency rezervasyonu: GB-saniye başına $0,0000041667, yani GB-saat başına $0,015
- Provisioned kapasitede karşılanan invocation’ların duration’ı: GB-saniye başına $0,0000097222
1 GB’lık, ayda bir milyon kez çağrılan ve invocation başına ortalama 500 ms süren bir function’ı ele alalım. Bu, 500.000 GB-saniyelik hesaplama demek.
- On-demand: 500.000 × $0,0000166667 = $8,33, artı $0,20 istek ücreti. Yaklaşık $8,53.
- Günde 12 saat boyunca tutulan 10 birim provisioned concurrency ile: 10 GB × 12 saat × 30 gün = 3.600 GB-saat × $0,015 = rezervasyon için $54,00. Aynı 500.000 GB-saniye düşük provisioned tarifeden $4,86 tutar, üstüne $0,20 istek ücreti. Yaklaşık $59,06.
Kapasiteyi günün yarısında rezerve etmek, bu profildeki bir function için on-demand faturanın kabaca yedi katına çıkıyor. Asıl mesele de bu oran. Function ne kadar ucuzsa provisioned concurrency yüzde olarak o kadar kötü görünür; dolayısıyla karar, çarpanın yerine o gecikmenin sana neye mal olduğu üzerine kurulmak zorunda.
Keep-Warm Stratejileri: İyi ve Kötü Yanları#
EventBridge Keep-Warm (Legacy Yaklaşım)#
// Keep-warm implementasyonu
exports.handler = async (event) => {
// Keep-warm ping'lerini handle et
if (event.source === 'aws.events' && event['detail-type'] === 'Keep Warm') {
return { statusCode: 200, body: 'Staying warm!' };
}
// Normal handler mantığı
return processRequest(event);
};
Keep-warm yaklaşımı neden önerilmez:
- Her function’a karmaşıklık ekliyordu
- EventBridge maliyetleri birikiyor
- Trafik spike’larında güvenilmez
- Provisioned Concurrency daha öngörülebilir
Lambda Extensions Nerede İşe Yarar#
Extension’lar bir environment’ı ayakta tutmaz. Invocation bitince Lambda, extension process’leri dahil tüm environment’ı dondurur. Extension’ın sana verdiği şey lifecycle’da bir koltuk: INVOKE ve SHUTDOWN olaylarına kaydolur ve her environment’ın ne yaptığını raporlayabilir. Init bedelini ne sıklıkla ödediğini de böyle öğrenirsin.
// /opt/extensions altından başlatılan ayrı bir process olarak çalışır
const EXTENSION_NAME = 'init-telemetry';
const API = `http://${process.env.AWS_LAMBDA_RUNTIME_API}/2020-01-01/extension`;
const registration = await fetch(`${API}/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Lambda-Extension-Name': EXTENSION_NAME
},
body: JSON.stringify({ events: ['INVOKE', 'SHUTDOWN'] })
});
// Sonraki her çağrı bu identifier'ı taşır
const extensionId = registration.headers.get('Lambda-Extension-Identifier');
// İş için long-poll; Lambda'da olay oluşana kadar istek bekler
const next = await fetch(`${API}/event/next`, {
headers: { 'Lambda-Extension-Identifier': extensionId }
});
Package Boyutu Optimizasyonu#
Bundle Analizi#
Deployment package boyutu doğrudan Init aşamasının 1. adımına gider; o yüzden işe gerçekte ne gönderdiğine bakarak başla:
npx webpack-bundle-analyzer dist/stats.json
Üç suçlu sürekli karşına çıkar:
aws-sdk(v2): desteği bitti ve her servis client’ını birden içeri alıyor. Her biri tek servis taşıyan@aws-sdk/client-*paketlerine geç.moment: tree shaking yok, üstüne varsayılan olarak locale verisini de sürüklüyor. Çoğu formatlama ihtiyacınıdate-fnsya da gömülüIntlAPI’si karşılar.lodash: kök paketi import etmek tüm kütüphaneyi çeker. Bunun yerine tek tek function’ları import et.
Pratik Bundling Stratejisi#
// KÖTÜ: desteği bitmiş AWS SDK v2'yi içeri alır
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
// İYİ: AWS SDK v3 ile selective import'lar
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
Lambda için Webpack Konfigürasyonu#
// webpack.config.js Lambda için optimize edilmiş
module.exports = {
target: 'node',
mode: 'production',
entry: './src/index.ts',
// Managed Node.js runtime'ı AWS SDK v3 ile geliyor; yine de kullandığın
// client'ları bundle'lamak sürümü sabitler ve modül çözümlemesini kısaltır.
optimization: {
minimize: true,
usedExports: true, // Tree shaking
sideEffects: false
},
resolve: {
extensions: ['.ts', '.js']
}
};
Lambda Layer’lar: Stratejik Kullanım#
Layer’a Ne Koymalı#
Layer’lar için iyi adaylar:
- Function’lar arası paylaşılan business logic
- Ağır dependency’ler (analytics SDK’ları, vb.)
- Custom runtime’lar veya araçlar
Function package’ında tut:
- Function’a özel mantık
- Sık değişen kod
- Küçük utility kütüphaneleri
Layer Performance Etkisi#
Layer’lar Init sırasında indirilip /opt altına açılır; yani cold start’ın yanında değil, tam içinde yer alırlar. Sınırları da kotalar çiziyor: function başına beş layer ve function package’ı ile tüm layer’ları birlikte açılmış halde 250 MB.
Maliyet modeli bu kadar basit. Byte byte’tır; package’la gelmiş ya da layer’la gelmiş fark etmez. Bir layer, aynı ağır dependency’yi birkaç function paylaşıyorsa ve altı yerine tek bir artifact güncellemek istiyorsan hak ediyor yerini.
Pratik kural: tek bir paylaşılan layer, o da içindekine birden fazla function ihtiyaç duyuyorsa.
Connection Pooling ve Initialization#
Database Connection Stratejisi#
// Handler dışında connection pooling
import { Pool } from 'pg';
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 1, // Önemli: Lambda = tek eş zamanlı execution
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
});
export const handler = async (event: any) => {
const client = await pool.connect();
try {
const result = await client.query('SELECT NOW()');
return result.rows;
} finally {
// finally'de bırak; yoksa hata fırlatan bir query bağlantıyı sızdırır
client.release();
}
};
AWS Service Client Yeniden Kullanımı#
// Service client yeniden kullanım pattern'i
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { S3Client } from '@aws-sdk/client-s3';
// Handler dışında initialize et
const dynamoClient = new DynamoDBClient({});
const s3Client = new S3Client({});
export const handler = async (event: any) => {
// Client'ları invocation'lar arası yeniden kullan
// AWS SDK v3 connection pooling'i internal olarak handle eder
};
Production’da Cold Start Monitoring#
Temel CloudWatch Metrikleri#
import { CloudWatchClient, PutMetricDataCommand } from '@aws-sdk/client-cloudwatch';
import type { Context } from 'aws-lambda';
const cloudwatch = new CloudWatchClient({});
// Modül seviyesi: yalnızca her execution environment'taki
// ilk invocation için false kalır
let isWarm = false;
export const handler = async (event: unknown, context: Context) => {
if (!isWarm) {
isWarm = true;
await cloudwatch.send(new PutMetricDataCommand({
Namespace: 'Lambda/Performance',
MetricData: [{
MetricName: 'ColdStart',
Value: 1,
Unit: 'Count',
Dimensions: [{ Name: 'FunctionName', Value: context.functionName }]
}]
}));
}
// İstek işi
};
Bunun en çok önemsediğin invocation’a ne yaptığına dikkat et: function’ın karşılayacağı en yavaş isteğe senkron bir API çağrısı ekliyor. Yerine embedded metric format log satırı yazmak ölçümü kritik yoldan çıkarır, çünkü CloudWatch metriği log’dan asenkron olarak ayıklar.
X-Ray Tracing Kurulumu#
Active tracing’i açtığında Init aşamasını Lambda senin yerine raporluyor: servis segment’i altında Initialization adlı bir subsegment olarak. Cold start peşindeysen izleyeceğin sayı o; onu üreten bir SDK çağrısı yok. SDK’nın işi downstream görünürlük, yani yavaş bir init’in kaynağı import’ların mı yoksa ağa uzanan bir client mı, onu görmek:
import AWSXRay from 'aws-xray-sdk-core';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
// Modül seviyesinde sar ki kurulum işi de trace edilsin
const dynamo = AWSXRay.captureAWSv3Client(new DynamoDBClient({}));
export const handler = async (event: unknown) => {
// `dynamo` üzerinden yapılan çağrılar otomatik olarak subsegment olur
};
Yaygın Cold Start Tuzakları#
Tuzak 1: Warm-Up Mantığını Aşırı Karmaşıklaştırmak#
Provisioned Concurrency’den daha pahalı ve daha az güvenilir olan karmaşık keep-warm sistemleri geliştirmek için haftalarca zaman harcamak yaygın bir tuzaktır.
Tuzak 2: Memory Etkisini Göz Ardı Etmek#
Lambda CPU’yu memory ayarıyla orantılı veriyor ve Init aşaması CPU işi. 50 MB package’ı olan 128 MB’lık bir function, aynı package’ı taşıyan 1 GB’lık bir function’dan daha yavaş cold start yapar.
Tuzak 3: Yanlış Runtime Seçimi#
Cold start sonuçlarını anlamadan user-facing API için Java seçmek. SnapStart kullanmaya ve kapsamlı tuning yapmaya hazır değilsen, Node.js veya Python’da kal.
Tuzak 4: Dependency Şişkinliği#
Bundle etkisini düşünmeden npm package’ları eklemek. Her dependency cold start süresine ekler, özellikle transitive dependency’ler.
Optimizasyonu Nerede Bırakmalı#
Çoğu function için varsayılan yeterli: Node.js ya da Go seç, package’ı yalın tut, client’ları modül seviyesinde kur ve orada dur. Cold start invocation’ların küçük bir kısmına dokunur; asenkron bir consumer’da bu kısmın kimsenin fark edeceği bir bedeli yok. Varsayılanı, cevabı bir insan beklerken ve kuyruk gecikmesi verdiğin bir sözü bozarken aş. Provisioned concurrency ya da JVM function’larda SnapStart, getirdiği faturayı işte o noktada hak ediyor.
Serinin Devamı#
Bu serinin sonraki bölümü memory allocation’ı ve ısınmış bir function’ın nasıl çalıştığını belirleyen tuning işini ele alıyor.
İşlenen konular:
- Memory vs CPU allocation stratejileri
- Benchmarking teknikleri
- Performance profiling araçları
- Maliyet analizi framework’leri
Kaynaklar#
- Understanding the Lambda execution environment lifecycle (yeni sekmede açılır) - Init, Invoke ve Shutdown aşamalarını ve soğuk başlatma sırasında neler yaşandığını açıklayan resmi belge.
- Configuring provisioned concurrency for a function (yeni sekmede açılır) - Etkileşimli iş yüklerinde soğuk başlatma gecikmesini ortadan kaldırmak için önceden başlatılmış yürütme ortamlarının yapılandırılması.
- Improving startup performance with Lambda SnapStart (yeni sekmede açılır) - Sıfırdan başlatmak yerine Firecracker microVM anlık görüntüsünden geri yükleme yapan SnapStart özelliği.
- Understanding and Remediating Cold Starts: An AWS Lambda Perspective (yeni sekmede açılır) - Soğuk başlatma nedenleri, çalışma zamanı karşılaştırmaları ve çözüm stratejileri üzerine AWS Compute Blog derinlemesine incelemesi.
- Operating Lambda: Performance optimization - Part 1 (yeni sekmede açılır) - Başlatma en iyi uygulamalarını ve ısınma desenlerini ele alan Lambda performans blog serisi.
- Best practices for working with AWS Lambda functions (yeni sekmede açılır) - Paket boyutu optimizasyonu, işleyici ayrımı ve bağlantı yeniden kullanımı dahil resmi Lambda en iyi uygulamaları.
- Understanding Lambda function scaling (yeni sekmede açılır) - Lambda’nın eşzamanlılığı nasıl ölçeklendirdiği ve ölçekleme davranışı ile soğuk başlatma sıklığı arasındaki ilişki.
- AWS Lambda pricing (yeni sekmede açılır) - İstek, süre ve provisioned concurrency için bölge bazlı liste fiyatları; yukarıdaki maliyet modelinin kaynağı.
- Lambda Extensions API (yeni sekmede açılır) - Function’ın yanında çalışan extension’lar için kayıt, olay bekleme ve kapanma davranışı.
- Lambda quotas (yeni sekmede açılır) - Paketleme kararlarını sınırlayan deployment package, layer sayısı ve açılmış boyut limitleri.
AWS Lambda Production Rehberi: 5 Yıllık Gerçek Dünya Deneyimi
5+ yıllık production deneyimine dayalı kapsamlı AWS Lambda rehberi. Cold start optimizasyonu, performans ayarlama, monitoring ve maliyet optimizasyonu ile gerçek savaş hikayeleri ve pratik çözümler.
Bu serideki tüm yazılar
İlgili yazılar
Global uygulamalar için AWS edge computing çözümlerini seçme ve uygulama üzerine pratik örnekler ve maliyet optimizasyonu stratejileri içeren kapsamlı teknik rehber.
aws · cloudfront · lambda +6
Dev, staging ve production ortamlarında Lambda Layer versiyonlarını AWS CDK ile yönetmek için pratik yaklaşımlar: otomatik pipeline ve rollback dahil.
aws · lambda · aws-cdk +4
AWS Lambda'da Node.js'den Go'ya geçiş ne zaman kendini amorti eder, ne zaman etmez: karar çerçevesi, serverless Go pattern'ları ve maliyet matematiği.
go · nodejs · serverless +5
AWS Lambda performance tuning: memory-CPU modeli, Power Tuning ile benchmarking, maliyet analizi ve adaptive allocation pattern'ları.
lambda · serverless · performance +2
Advanced AWS Lambda pattern'leri ve maliyet optimizasyonu: Lambda Layers, VPC konfigürasyonu, cross-account execution ve mimari kararlar.
lambda · serverless · cost-optimization +5