AWS Lambda Bellek ve Zaman Aşımı Optimizasyonu
Serverless dünyasına adım attığınızda Lambda fonksiyonlarınızın “sadece çalışıyor” olması sizi tatmin etmemeye başlar bir süre sonra. Çünkü Lambda’da her milisaniye ve her megabyte para demek. Fonksiyonunuz 3 saniyede tamamlanıyor ama 128 MB bellek yeterli olduğu halde siz 1 GB ayarladıysanız, ya da tam tersi bellek yetersizliğinden sürekli timeout alıyorsanız, hem uygulamanız sağlıklı çalışmıyor hem de faturanız gereksiz şişiyor. Bu yazıda Lambda optimizasyonunu gerçek dünya senaryolarıyla, ölçülebilir sonuçlarla ele alacağız.
Lambda Fiyatlandırma Mantığını Anlamak
Optimizasyon yapmadan önce neyi optimize ettiğinizi bilmeniz lazım. Lambda sizi iki şeyden faturalandırıyor: istek sayısı ve GB-saniye cinsinden hesaplama süresi. GB-saniye hesaplaması şu formülle yapılıyor:
Kullanılan Bellek (GB) x Çalışma Süresi (saniye) = GB-saniye
Yani 512 MB bellek ayarlayıp 2 saniyede tamamlanan bir fonksiyon size 1 GB-saniye maliyeti çıkarır. 256 MB ayarlayıp 2 saniyede tamamlansaydı 0.5 GB-saniye olacaktı. Ama işin ilginç tarafı şu: daha fazla bellek verdiğinizde fonksiyon daha hızlı çalışabilir. Bu da bazen daha yüksek bellek ayarlamanın aslında daha az maliyete yol açtığı paradoksal bir durumu ortaya çıkarıyor.
Lambda’nın CPU tahsisi doğrudan bellek miktarıyla orantılı. 128 MB verdiğinizde çok kısıtlı CPU alıyorsunuz, 1792 MB verdiğinizde tam bir vCPU’ya ulaşıyorsunuz. Bu eşiğin ötesinde ise çok çekirdekli hesaplama başlıyor.
Temel Bellek Optimizasyon Stratejileri
Doğru Başlangıç Noktasını Bulmak
Çoğu geliştirici Lambda fonksiyonuna 128 MB verip geçer. Bu yaklaşım genellikle ya yetersiz kalır ya da fonksiyon için fazla yeterli. Doğru başlangıç noktasını bulmak için CloudWatch Logs Insights kullanabilirsiniz:
# CloudWatch Logs Insights sorgusu - Lambda bellek kullanımını analiz et
aws logs start-query
--log-group-name "/aws/lambda/my-function"
--start-time $(date -d '7 days ago' +%s)
--end-time $(date +%s)
--query-string 'filter @type = "REPORT" | stats avg(@maxMemoryUsed), max(@maxMemoryUsed), min(@maxMemoryUsed) by bin(1h)'
Bu sorgu size son 7 günün bellek kullanım istatistiklerini verir. Eğer maksimum bellek kullanımınız sürekli olarak ayarladığınız limitin %90’ının üzerindeyse, bellek artırmanız gerekiyor. Eğer %40’ın altındaysa azaltmayı düşünebilirsiniz.
AWS Lambda Power Tuning ile Sistematik Optimizasyon
Manuel denemeler yerine AWS Lambda Power Tuning aracını kullanmak hayat kurtarıcı. Bu araç Step Functions kullanarak fonksiyonunuzu farklı bellek konfigürasyonlarında çalıştırıp hangisinin en iyi maliyet/performans dengesini sunduğunu analiz ediyor.
# Lambda Power Tuning kurulumu için SAR (Serverless Application Repository) kullanımı
aws serverlessrepo create-cloud-formation-change-set
--application-id arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning
--stack-name lambda-power-tuning
--capabilities CAPABILITY_IAM
--parameter-overrides '[{"name":"lambdaResource","value":"*"}]'
# Stack'i deploy et
aws cloudformation execute-change-set
--change-set-name <changeset-arn>
--stack-name lambda-power-tuning
Power Tuning kurulduktan sonra Step Functions konsolundan ya da CLI ile çalıştırabilirsiniz:
# Power Tuning state machine'ini başlat
aws stepfunctions start-execution
--state-machine-arn "arn:aws:states:eu-west-1:123456789:stateMachine:powerTuningStateMachine"
--input '{
"lambdaARN": "arn:aws:lambda:eu-west-1:123456789:function:my-api-handler",
"powerValues": [128, 256, 512, 1024, 2048, 3008],
"num": 10,
"payload": {"test": "data"},
"parallelInvocation": true,
"strategy": "balanced"
}'
strategy parametresi için üç seçenek mevcut:
- cost: En ucuz konfigürasyonu seçer, yavaş olsa bile
- speed: En hızlı konfigürasyonu seçer, pahalı olsa bile
- balanced: Maliyet ve hız arasında en iyi dengeyi bulur
Gerçek Dünya Senaryosu: E-ticaret Sipariş İşleme
Bir e-ticaret projesinde sipariş işleme Lambda’mız vardı. Fonksiyon şunu yapıyordu: siparişi DynamoDB’ye yaz, SMS gönder, e-posta gönder ve stok güncelle. Başlangıçta 512 MB ve 30 saniyelik timeout ile çalışıyordu.
CloudWatch Logs Insights analizi şunu gösterdi: Gerçek bellek kullanımı ortalama 187 MB, maksimum 203 MB. Ortalama süre ise 4.2 saniye. Power Tuning’i çalıştırdığımızda 256 MB konfigürasyonunun hem maliyeti düşürdüğünü hem de süreyi 4.2 saniyeden 3.8 saniyeye indirdiğini gördük. Aylık 40.000 sipariş hacminde bu optimizasyon %23 maliyet düşüşü sağladı.
Zaman Aşımı Optimizasyonu
Doğru Timeout Değerini Belirlemek
Timeout ayarı “yüksek koy, güvende ol” mantığıyla yapılmamalı. Yüksek timeout değerleri şu sorunlara yol açar:
- Hatalı veya takılı çağrılar için gereksiz bekleme ve maliyet
- Downstream servislerle entegrasyonda zincirleme gecikme
- API Gateway’in 29 saniyelik hard limitini aşma riski
Timeout değerini belirlemek için şu yaklaşımı benimsiyorum: p99 yanıt süresini ölçün ve ona %50-100 buffer ekleyin.
# P99 yanıt süresini CloudWatch Logs Insights ile bul
aws logs start-query
--log-group-name "/aws/lambda/order-processor"
--start-time $(date -d '30 days ago' +%s)
--end-time $(date +%s)
--query-string 'filter @type = "REPORT" | stats pct(@duration, 99) as p99, pct(@duration, 95) as p95, avg(@duration) as avg by bin(1d)'
# Timeout değerini güncelle (p99 = 8 saniye ise 15 saniye makul)
aws lambda update-function-configuration
--function-name order-processor
--timeout 15
--memory-size 256
Cascade Timeout Problemi
Microservice mimarisinde Lambda fonksiyonları birbirini çağırdığında cascade timeout ciddi bir sorun. A fonksiyonu B’yi, B de C’yi çağırıyorsa ve A’nın timeout’u 30 saniyeyse, bu zincirin tamamı için yeterli buffer olmayabilir.
# Lambda'dan başka bir Lambda'yı asenkron çağırma - cascade önlemek için
aws lambda update-function-configuration
--function-name function-a
--timeout 30
# Function B'nin timeout'unu A'dan düşük tutun
aws lambda update-function-configuration
--function-name function-b
--timeout 20
# Function C en düşük olmalı
aws lambda update-function-configuration
--function-name function-c
--timeout 10
Timeout Handling’i Kod İçinde Yönetmek
Lambda’da timeout yaklaşırken bunu kodunuzda da yönetmeniz gerekiyor. Python için şu pattern’ı kullanıyorum:
import json
import boto3
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
# Kalan süreyi millisaniye cinsinden al
time_remaining = context.get_remaining_time_in_millis()
# 2000ms kalmadan önce işlemi gracefully tamamla
threshold_ms = 2000
results = []
items_to_process = event.get('items', [])
for i, item in enumerate(items_to_process):
# Her iterasyonda kalan süreyi kontrol et
if context.get_remaining_time_in_millis() < threshold_ms:
logger.warning(f"Zaman azalıyor. {i}/{len(items_to_process)} işlendi.")
# Kalan itemleri SQS'e at - yarıda bırakma
if i < len(items_to_process):
sqs = boto3.client('sqs')
sqs.send_message(
QueueUrl='https://sqs.eu-west-1.amazonaws.com/123/retry-queue',
MessageBody=json.dumps({
'remaining_items': items_to_process[i:],
'context': 'timeout_fallback'
})
)
break
# Normal işlem
result = process_item(item)
results.append(result)
return {
'statusCode': 200,
'processed': len(results),
'total': len(items_to_process)
}
def process_item(item):
# İş mantığı burada
return {'id': item['id'], 'status': 'processed'}
İleri Seviye Optimizasyon Teknikleri
Cold Start’ı Azaltmak
Bellek ve timeout optimizasyonuna ek olarak cold start süresi de performansı doğrudan etkiliyor. Cold start süresini azaltmak için şu teknikleri uygulayabilirsiniz:
# Provisioned Concurrency - önceden ısıtılmış instance'lar
aws lambda put-provisioned-concurrency-config
--function-name my-api-handler
--qualifier production
--provisioned-concurrent-executions 5
# Application Auto Scaling ile provisioned concurrency yönetimi
aws application-autoscaling register-scalable-target
--service-namespace lambda
--resource-id function:my-api-handler:production
--scalable-dimension lambda:function:ProvisionedConcurrency
--min-capacity 2
--max-capacity 10
Provisioned Concurrency maliyetli olduğu için sadece gerçekten düşük latency gerektiren fonksiyonlar için kullanın. Bir yük testi API endpoint’i için değil, kullanıcıya direkt yanıt dönen kritik servisler için tercih edin.
Lambda SnapStart ile Java Optimizasyonu
Java Lambda fonksiyonları için cold start süresi bazen 10-15 saniyeye çıkabiliyor. SnapStart bu sorunu dramatik biçimde çözüyor:
# SnapStart'ı Java 11/17/21 runtime için etkinleştir
aws lambda update-function-configuration
--function-name java-api-function
--snap-start ApplyOn=PublishedVersions
--runtime java21
--memory-size 512
# Yeni versiyon yayınla - SnapStart snapshot'ı bu sırada oluşturulur
aws lambda publish-version
--function-name java-api-function
--description "SnapStart optimized v2"
SnapStart ile Java Lambda cold start süreleri genellikle %90’ın üzerinde düşüyor. 12 saniyelik cold start 200 milisaniyenin altına inebiliyor.
Ortam Değişkenleri ve Initialization Kodu Optimizasyonu
Lambda’da handler fonksiyonu dışındaki kod yalnızca cold start sırasında çalışır. Bu nedenle database bağlantıları, SDK client’ları ve konfigürasyonları handler dışında initialize etmek kritik:
import boto3
import os
import json
from functools import lru_cache
# Bu kod SADECE cold start sırasında çalışır - cache'lenir
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])
s3_client = boto3.client('s3')
# Konfigürasyonu cache'le - her çağrıda SSM'e gitme
@lru_cache(maxsize=1)
def get_config():
ssm = boto3.client('ssm')
response = ssm.get_parameter(
Name='/myapp/config',
WithDecryption=True
)
return json.loads(response['Parameter']['Value'])
def lambda_handler(event, context):
# Config zaten cache'lendi, SSM çağrısı yapılmaz
config = get_config()
# DynamoDB client zaten hazır
response = table.get_item(Key={'id': event['id']})
return {
'statusCode': 200,
'body': json.dumps(response.get('Item', {}))
}
Bu pattern ile DynamoDB bağlantısı warm instance’larda yeniden kurulmaz, bu da her çağrıda 50-100ms tasarruf sağlar.
Bellek Sızıntılarını Tespit Etmek
Lambda’da bellek sızıntısı özellikle Node.js fonksiyonlarında yaygın. Fonksiyon warm kaldığında bellek kullanımının her çağrıda artması bellek sızıntısının işareti:
# Bellek kullanımını invocation bazında izle
aws logs start-query
--log-group-name "/aws/lambda/node-api-function"
--start-time $(date -d '1 day ago' +%s)
--end-time $(date +%s)
--query-string 'filter @type = "REPORT" | fields @timestamp, @maxMemoryUsed, @memorySize | sort @timestamp asc | limit 100'
Bellek kullanımı invocation’dan invocation’a sürekli artıyorsa, global scope’ta biriken nesneleri temizlemeniz gerekiyor.
Monitoring ve Alarm Kurulumu
Optimizasyon tek seferlik değil, sürekli bir süreç. CloudWatch alarmları kurarak proaktif yaklaşım sağlayabilirsiniz:
# Bellek kullanımı %85'i geçince alarm
aws cloudwatch put-metric-alarm
--alarm-name "Lambda-HighMemoryUsage-order-processor"
--alarm-description "Lambda bellek kullanımı kritik seviyede"
--metric-name "max_memory_used_MB"
--namespace "LambdaInsights"
--dimensions Name=function_name,Value=order-processor
--statistic Maximum
--period 300
--evaluation-periods 3
--threshold 400
--comparison-operator GreaterThanThreshold
--alarm-actions "arn:aws:sns:eu-west-1:123456789:ops-alerts"
# Timeout oranı için alarm
aws cloudwatch put-metric-alarm
--alarm-name "Lambda-TimeoutRate-order-processor"
--alarm-description "Lambda timeout oranı yüksek"
--metric-name "Errors"
--namespace "AWS/Lambda"
--dimensions Name=FunctionName,Value=order-processor
--statistic Sum
--period 300
--evaluation-periods 2
--threshold 5
--comparison-operator GreaterThanThreshold
--alarm-actions "arn:aws:sns:eu-west-1:123456789:ops-alerts"
Lambda Insights’ı etkinleştirerek daha granüler metrikler elde edebilirsiniz:
# Lambda Insights katmanını fonksiyona ekle
aws lambda update-function-configuration
--function-name order-processor
--layers "arn:aws:lambda:eu-west-1:580247275435:layer:LambdaInsightsExtension:38"
--environment Variables='{
"TABLE_NAME": "orders",
"POWERTOOLS_SERVICE_NAME": "order-processor",
"LOG_LEVEL": "INFO"
}'
Optimizasyon Kararları için Pratik Rehber
Doğru konfigürasyonu seçmek için şu kriterleri kullanıyorum:
Bellek için karar noktaları:
- 128-256 MB: Basit veri dönüşümleri, kısa hesaplamalar, az sayıda API çağrısı
- 512 MB-1 GB: Orta ölçekli veri işleme, birden fazla servis entegrasyonu, Node.js/Python uygulamaları
- 1-2 GB: Büyük dosya işleme, görüntü/ses manipülasyonu, karmaşık hesaplamalar
- 2-10 GB: Makine öğrenmesi çıkarımı, büyük veri setleri, paralel işlemler
Timeout için karar noktaları:
- 3-10 saniye: API Gateway arkasındaki senkron endpoint’ler
- 15-60 saniye: Arka planda çalışan orta ölçekli işlemler
- 1-5 dakika: Veri pipeline’ları, toplu işlemler
- 5-15 dakika: Büyük dosya işleme, karmaşık raporlama
Dikkat edilmesi gereken durumlar:
- API Gateway ile kullanılan Lambda’lar için timeout 29 saniyenin üzerine çıkamazsınız
- SQS trigger’lı Lambda’lar için visibility timeout, Lambda timeout’unun en az 6 katı olmalı
- Step Functions’ta her adım için ayrı timeout stratejisi belirleyin
Maliyet Optimizasyonu Hesaplama Aracı
Kendi hesabınızdaki optimizasyon potansiyelini ölçmek için şu script’i kullanabilirsiniz:
#!/bin/bash
# Lambda maliyet analizi scripti
FUNCTION_NAME=$1
DAYS=${2:-30}
echo "=== $FUNCTION_NAME - Son $DAYS Gün Analizi ==="
# Toplam çağrı sayısı
INVOCATIONS=$(aws cloudwatch get-metric-statistics
--namespace AWS/Lambda
--metric-name Invocations
--dimensions Name=FunctionName,Value=$FUNCTION_NAME
--start-time $(date -d "$DAYS days ago" -u +%Y-%m-%dT%H:%M:%SZ)
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ)
--period $((DAYS * 86400))
--statistics Sum
--query 'Datapoints[0].Sum'
--output text)
# Ortalama süre
AVG_DURATION=$(aws cloudwatch get-metric-statistics
--namespace AWS/Lambda
--metric-name Duration
--dimensions Name=FunctionName,Value=$FUNCTION_NAME
--start-time $(date -d "$DAYS days ago" -u +%Y-%m-%dT%H:%M:%SZ)
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ)
--period $((DAYS * 86400))
--statistics Average
--query 'Datapoints[0].Average'
--output text)
# Mevcut bellek ayarı
MEMORY=$(aws lambda get-function-configuration
--function-name $FUNCTION_NAME
--query 'MemorySize'
--output text)
echo "Toplam Cagri: $INVOCATIONS"
echo "Ortalama Sure: ${AVG_DURATION}ms"
echo "Bellek Ayari: ${MEMORY}MB"
# GB-saniye hesapla
GB_SECONDS=$(echo "scale=4; $INVOCATIONS * ($AVG_DURATION/1000) * ($MEMORY/1024)" | bc)
echo "Tahmini GB-Saniye: $GB_SECONDS"
# Maliyet tahmini (us-east-1 fiyatları)
COST=$(echo "scale=4; $GB_SECONDS * 0.0000166667" | bc)
echo "Tahmini Maliyet (USD): $$COST"
Sonuç
Lambda optimizasyonu, “çalışıyor mu? Tamam o zaman” zihniyetinin çok ötesinde bir konu. Doğru bellek ayarı hem performansı artırıyor hem de maliyeti düşürüyor. Bu ikisi çoğu zaman çelişkili görünse de Lambda’nın CPU/bellek ilişkisi sayesinde doğru noktayı bulmak mümkün.
Önerim şu: Önce mevcut fonksiyonlarınızı CloudWatch Logs Insights ile analiz edin, gerçek bellek kullanımı ile ayarladığınız değer arasındaki farkı görün. Sonra Lambda Power Tuning ile sistematik bir test yapın. Elde ettiğiniz verilere göre konfigürasyonu güncelleyin ve CloudWatch alarmları kurarak süreci izlemeye devam edin.
Unutmayın, bu tek seferlik bir optimizasyon değil. Uygulamanız değiştikçe, trafik profili dönüştükçe, yeni özellikler ekledikçe bu optimizasyon döngüsünü tekrar çalıştırmanız gerekiyor. Aylık düzenli Lambda maliyet analizi yapmayı alışkanlık haline getirin. Orta ölçekli bir projede bile bu yaklaşım yıllık yüzlerce dolar tasarruf sağlayabiliyor. Büyük ölçekte ise bu rakamlar binlerce dolara ulaşıyor.
