Skip to content
~/sph.sh

AWS Lambda'da Bun ve Alternatif JavaScript Runtime'ları Çalıştırma

Bun ve Deno'yu AWS Lambda üzerinde custom runtime kullanarak çalıştırmak için teknik implementasyon rehberi, gerçek performans benchmark'ları, maliyet analizi ve production deployment pattern'leri ile.

Abstract

AWS Lambda resmi olarak Node.js'i destekliyor, ancak platformun custom runtime özelliği Bun ve Deno gibi alternatif JavaScript runtime'larına kapı açıyor. Bu rehber, bu runtime'ları Lambda üzerinde çalıştırmanın teknik implementasyonunu iki yaklaşımla inceliyor: Lambda Layer'ları ve container image'ları. Gerçek benchmark'lardan gelen performans karakteristiklerini, implementasyon tuzaklarını ve AWS'nin optimize edilmiş Node.js runtime'ı ile alternatif runtime'lar arasındaki trade-off'ları inceleyeceğiz.

Custom Runtime Sorusu

Developer'lar Lambda deployment'ları için alternatif JavaScript runtime'larının cazip hale geldiği senaryolarla karşılaşıyor. Yaygın motivasyonlar arasında latency-sensitive uygulamalarda cold start overhead'i, TypeScript transpilation adımlarından kaçınma, runtime verimliliğinin maliyeti etkilediği CPU-bound workload'lar ve Node.js LTS desteğinden önce modern JavaScript özelliklerine erişim var.

Temel teknik zorluk: AWS Lambda yönetilen runtime'ları için yoğun şekilde optimize edilmiş, ancak custom runtime'lar bu optimizasyonları feda ediyor. Alternatif runtime'lardan gelen performans kazancı, cold start cezası ve implementasyon karmaşıklığına değer mi?

Lambda Custom Runtime'larını Anlamak

AWS Lambda'nın custom runtime özelliği, Lambda Runtime API'yi implement ederek herhangi bir runtime çalıştırmanıza olanak tanıyor. Bu API, runtime'ınızın event'leri almak ve response'ları döndürmek için kullandığı basit bir HTTP interface sağlıyor.

Runtime API Flow'u

typescript
// Basitleştirilmiş Lambda Runtime API implementasyonuconst RUNTIME_API = `http://${process.env.AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime`;
while (true) {  // 1. Sonraki invocation'ı al  const eventResponse = await fetch(`${RUNTIME_API}/invocation/next`);  const requestId = eventResponse.headers.get('Lambda-Runtime-Aws-Request-Id');  const event = await eventResponse.json();
  try {    // 2. Handler'ı çağır    const result = await handler(event);
    // 3. Response'u döndür    await fetch(`${RUNTIME_API}/invocation/${requestId}/response`, {      method: 'POST',      body: JSON.stringify(result),    });  } catch (error) {    // 4. Error'u bildir    await fetch(`${RUNTIME_API}/invocation/${requestId}/error`, {      method: 'POST',      body: JSON.stringify({        errorMessage: error.message,        errorType: error.constructor.name,      }),    });  }}

Bootstrap süreci sonsuz döngüde çalışıyor: Lambda'dan event'leri talep ediyor, handler'ınızı çalıştırıyor ve sonuçları döndürüyor. Bu basit protokol, custom runtime'ları mümkün kılan şey.

Implementasyon Yaklaşımı 1: Lambda Layer'ları ile Bun

Lambda Layer'ları, runtime bağımlılıklarını paketlemenin ve birden fazla function arasında paylaşmanın bir yolunu sağlıyor. Bun, Runtime API'yi implement eden resmi bun-lambda package'ı sağlıyor.

Bun Lambda Layer'ını Build Etme

bash
# Bun repository'sini clone'lagit clone https://github.com/oven-sh/bun.gitcd bun/packages/bun-lambda
# Layer'ı build et ve publish et (varsayılan arm64)bun run publish-layer
# x86_64 için build et (uyumluluk için önerilir)ARCH=x64 bun run publish-layer

Publish script'i Bun runtime'ı ve bootstrap script'i ile bir Lambda Layer oluşturuyor, sonra AWS hesabınıza publish ediyor. arn:aws:lambda:us-east-1:123456789012:layer:bun-runtime:1 gibi görünen bir Layer ARN alacaksınız.

Bun Lambda Handler Yazmak

Bun Lambda handler'ları Node.js convention'ları yerine Web API standardını takip ediyor:

typescript
// handler.ts - Bun Lambda handlerexport default {  async fetch(request: Request): Promise<Response> {    const event = await request.json();
    // Lambda event'ini işle    const result = {      message: 'Hello from Bun on Lambda!',      timestamp: Date.now(),      input: event,    };
    return new Response(JSON.stringify(result), {      headers: { 'Content-Type': 'application/json' },    });  },};

Handler'ın handler değil fetch metodu export ettiğine dikkat edin. Bu Bun'ın Web API yaklaşımını takip ediyor. Lambda event'leri standart Request objelerine dönüştürülüyor ve handler'ınız Response objeleri döndürüyor.

AWS CDK ile Deploy Etme

typescript
import { Function, Runtime, Code, LayerVersion, Architecture } from 'aws-cdk-lib/aws-lambda';
// Publish edilmiş Bun layer'ına referans verconst bunRuntimeLayer = LayerVersion.fromLayerVersionArn(  this,  'BunRuntime',  'arn:aws:lambda:us-east-1:123456789012:layer:bun-runtime:1');
const bunFunction = new Function(this, 'BunFunction', {  runtime: Runtime.PROVIDED_AL2023,  handler: 'index.fetch',  code: Code.fromAsset('dist'),  layers: [bunRuntimeLayer],  architecture: Architecture.X86_64, // Layer mimarisi ile eşleşmeli});

Kritik gereklilik: Layer mimarisi function mimarisi ile eşleşmeli. Her ikisine de ihtiyacınız varsa x86_64 ve arm64 için ayrı layer'lar build edin.

Implementasyon Yaklaşımı 2: Container Image'ları

Container image'ları runtime environment üzerinde tam kontrol sağlıyor ve gelişmiş optimizasyonları mümkün kılıyor. Bu yaklaşım, HTTP sunucularını Lambda-uyumlu handler'lara dönüştürmek için AWS Lambda Web Adapter kullanıyor.

Bun Container Deployment

dockerfile
# Bun Lambda deployment için multi-stage buildFROM public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 AS aws-lambda-adapterFROM oven/bun:1-debian AS runtime
# Lambda adapter'ı kopyalaCOPY --from=aws-lambda-adapter /lambda-adapter /opt/extensions/lambda-adapter
WORKDIR /var/task
# Dependency'leri yükleCOPY package.json bun.lock ./RUN bun install --production --frozen-lockfile
# Uygulamayı kopyalaCOPY . .
# Gerekli Lambda adapter konfigürasyonuENV PORT=8080
CMD ["bun", "run", "index.ts"]

Lambda adapter gelen Lambda event'lerini intercept ediyor, bunları 8080 portundaki sunucunuza HTTP request'lere dönüştürüyor, sonra response'ları Lambda formatına geri dönüştürüyor.

Cache Pre-warming ile Deno

Deno'nun mimarisi modül çözümlemesini ve derlemeyi cache'liyor. Docker build sırasında uygulamayı önceden çalıştırmak bu cache'leri doldurur:

dockerfile
FROM public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 AS adapterFROM denoland/deno:bin-2.6.3 AS deno-binFROM debian:bookworm-slim
# Deno'yu yükleCOPY --from=deno-bin /deno /usr/local/bin/denoCOPY --from=adapter /lambda-adapter /opt/extensions/lambda-adapter
WORKDIR /var/taskENV DENO_DIR=/var/deno_dir
# Uygulamayı kopyalaCOPY . .
# Kritik: Deno cache'lerini ön-ısıt# Bu, runtime cache'lerini doldurmak için build sırasında uygulamayı bir kez çalıştırırRUN timeout 10s deno run --allow-net main.ts || [ $? -eq 124 ] || exit 1
ENV PORT=8080CMD ["deno", "run", "--allow-net", "main.ts"]

timeout 10s komutu build sırasında uygulamayı çalıştırıyor, Deno'nun tüm modül çözümlemesini ve derlemeyi cache'lemesine izin veriyor. Exit code 124 (timeout) bekleniyor ve kabul edilebilir - sadece cache'leri dolduruyoruz, sunucuyu gerçekten çalıştırmıyoruz.

Container Image'ları Build Etme ve Deploy Etme

bash
# Doğru mimari için build et (Apple Silicon'da kritik)docker build \  --platform linux/amd64 \  --provenance=false \  -t bun-lambda:latest .
# ECR'ye authenticate olaws ecr get-login-password --region us-east-1 | \  docker login --username AWS --password-stdin ${ECR_URI}
# Tag'le ve push etdocker tag bun-lambda:latest ${ECR_URI}:latestdocker push ${ECR_URI}:latest
# Lambda function oluşturaws lambda create-function \  --function-name bun-container-function \  --package-type Image \  --code ImageUri=${ECR_URI}:latest \  --role arn:aws:iam::123456789012:role/lambda-role

Platform spesifikasyonu kritik: Lambda varsayılan olarak x86_64 kullanıyor, ancak Apple Silicon'daki Docker varsayılan olarak arm64 kullanıyor. arm64 Lambda function'ları kullanmıyorsanız her zaman --platform linux/amd64 belirtin.

Performans Benchmark'ları

CPU-intensive benchmark'tan (SHA3-512 hash üretimi, 50 iterasyon) gerçek dünya performans verisi:

Cold Start Süreleri (Initialization Duration)

RuntimeOrtalamap10p90Aralık
Node.js (managed)152ms146ms160ms~14ms
Deno (container)267ms185ms297ms~30ms
Bun (layer)548ms500ms603ms~56ms

Temel bulgular:

  • Node.js cold start'larda Deno'ya göre %76, Bun'a göre %260 kazanıyor
  • Yönetilen runtime'lar için AWS optimizasyonları önemli fark yaratıyor
  • Bun'ın layer yaklaşımı önemli initialization overhead'i ekliyor

Warm Invocation Duration

RuntimeOrtalamap50p90
Deno13.7ms6.7ms19.8ms
Node.js21.3ms8.1ms56.7ms
Bun50.5ms15.2ms68.2ms

Temel bulgular:

  • Deno warm invocation'larda Node.js'e göre %36 kazanıyor
  • Container-based Deno custom runtime olmasına rağmen daha iyi performans gösteriyor
  • Bun'ın execution performansı bu benchmark'ta geride kalıyor

Maliyet Analizi

Tipik bir workload için gerçek maliyet etkilerini analiz edelim: ayda 10 milyon invocation, 512MB bellek, 100ms ortalama süre, %10 cold start oranı.

Node.js (Managed Runtime)

Compute: 10M × 0.0000000083 × 100ms = $0.83Request'ler: 10M × 0.0000002 = $2.00Toplam: $2.83/ay

Bun (Custom Layer)

50ms daha hızlı execution ancak 500ms daha yavaş cold start varsayarsak:

Cold start overhead: 1M × 500ms × 0.0000000083 = $4.15Compute tasarrufu: 10M × 50ms × 0.0000000083 = $4.15 tasarrufNet etki: Node.js ile yaklaşık eşitTrade-off: Cold start'larda daha kötü kullanıcı deneyimi

Deno (Container)

115ms daha yavaş cold start ancak %35 daha hızlı warm execution varsayarsak:

Cold start overhead: 1M × 115ms × 0.0000000083 = $0.95Compute tasarrufu: 9M × 35ms × 0.0000000083 = $2.62 tasarrufNet tasarruf: ~$1.67/ayPerformans ve maliyetin en iyi dengesi

Karar faktörleri:

  • Yüksek sabit trafik daha hızlı warm invocation'ları tercih eder (Deno)
  • Sık cold start'lar Node.js managed runtime'ı tercih eder
  • CPU-intensive function'lar runtime performansından daha fazla faydalanır
  • I/O-bound workload'lar (%95 Lambda function) minimal runtime etkisi görür

Yaygın Tuzaklar

Platform Mimari Uyumsuzluğu

Yanlış CPU mimarisi için container image'ları build etmek şifreli runtime hataları oluşturur.

Semptom:

Error: Runtime exited with error: exit status 1Runtime.InvalidEntrypoint

Temel sebep: Lambda varsayılan olarak x86_64 kullanıyor, ancak Apple Silicon'daki Docker varsayılan olarak arm64 kullanıyor.

Çözüm:

bash
# Build'de her zaman platform belirtdocker build --platform linux/amd64 -t myfunction .
# Build edilmiş image'ı doğruladocker inspect myimage:latest | grep Architecture# Çıktı şu olmalı: "Architecture": "amd64"

Eksik Lambda Adapter Konfigürasyonu

Container lokal olarak çalışıyor ancak Lambda'da bağlantı hatalarıyla başarısız oluyor.

Semptom: Function timeout oluyor veya 502 Bad Gateway döndürüyor.

Temel sebep: Lambda adapter PORT environment variable'ının 8080'e set edilmesini gerektiriyor.

Doğru implementasyon:

dockerfile
ENV PORT=8080CMD ["bun", "run", "server.ts"]
typescript
// Uygulamada environment variable kullanconst port = process.env.PORT || 3000;
Bun.serve({  port: Number(port),  fetch(request) {    return new Response('Hello World');  }});

AWS SDK Uyumluluk Sorunları

Daha eski Bun versiyonlarında Could not resolve: 'http2' hataları ve S3 ile SignatureDoesNotMatch hataları dahil AWS SDK uyumluluk zorlukları vardı. Son versiyonlar önemli ölçüde gelişti, ancak kendi kullanım senaryonuzda AWS SDK operasyonlarını her zaman açıkça test edin:

typescript
// test/aws-sdk.test.tsimport { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';import { describe, test, expect } from 'bun:test';
describe('AWS SDK Uyumluluğu', () => {  test('S3 PutObject çalışıyor', async () => {    const client = new S3Client({ region: 'us-east-1' });    const result = await client.send(new PutObjectCommand({      Bucket: 'test-bucket',      Key: 'test.txt',      Body: 'test content'    }));    expect(result.$metadata.httpStatusCode).toBe(200);  });});

Dockerfile'da Bun versiyonunu sabitle:

dockerfile
# Kararlılık için specific version tag kullanFROM oven/bun:1-debian

Lambda Layer Mimari Uyumsuzluğu

Problem: Layer başarıyla deploy ediliyor ancak function "Runtime not supported" hatası veriyor.

Çözüm: Her iki mimari için de layer'lar build et ve publish et:

bash
# x86_64 için build etARCH=x64 bun run publish-layer# Çıktı: arn:aws:lambda:us-east-1:123:layer:bun-x64:1
# arm64 için build etARCH=arm64 bun run publish-layer# Çıktı: arn:aws:lambda:us-east-1:123:layer:bun-arm64:1

CDK'da layer ve function arasında mimari eşleştir:

typescript
import { Architecture } from 'aws-cdk-lib/aws-lambda';
const bunLayerX64 = LayerVersion.fromLayerVersionArn(  this, 'BunLayerX64',  'arn:aws:lambda:us-east-1:123:layer:bun-x64:1');
new Function(this, 'MyFunction', {  architecture: Architecture.X86_64,  layers: [bunLayerX64], // Eşleşmeli});

Production-Ready İmplementasyon Pattern'leri

Pattern 1: HTTP Sunucu ile Deno + Lambda Adapter

API workload'ları için iyi çalışan şey:

typescript
// main.ts - oak framework ile Denoimport { Application } from "https://deno.land/x/[email protected]/mod.ts";
const app = new Application();
app.use((ctx) => {  ctx.response.body = { message: "Hello from Deno on Lambda!" };});
const port = parseInt(Deno.env.get("PORT") || "8080");console.log(`Sunucu ${port} portunda çalışıyor`);await app.listen({ port });
dockerfile
# Optimize edilmiş DockerfileFROM public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 AS adapterFROM denoland/deno:bin-2.6.3 AS deno-binFROM debian:bookworm-slim
# Minimal dependency'lerRUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
# Binary'leri kopyalaCOPY --from=deno-bin /deno /usr/local/bin/denoCOPY --from=adapter /lambda-adapter /opt/extensions/lambda-adapter
WORKDIR /var/taskENV DENO_DIR=/var/deno_dir PORT=8080
# UygulamaCOPY . .
# Cache'i ön-ısıt (kritik optimizasyon)RUN timeout 10s deno run -A main.ts || [ $? -eq 124 ] || exit 1
CMD ["deno", "run", "-A", "main.ts"]

Production deployment'tan sonuçlar:

  • Cold start: 185ms (p50) vs Node.js 145ms
  • Warm invocation: 6.7ms (p50) vs Node.js 8.1ms
  • Maliyet tasarrufu: daha hızlı execution sayesinde ~%15
  • Developer experience: build adımı olmadan native TypeScript

Pattern 2: Hybrid Yaklaşım - Workload Başına Runtime

Her function tipi için optimal runtime kullan:

typescript
// runtime-selector.ts - Karar mantığıtype RuntimeSelection = 'nodejs' | 'bun' | 'deno';
function selectRuntime(characteristics: {  cpuIntensive: boolean;  ioIntensive: boolean;  coldStartSensitive: boolean;  requiresAWSSDK: boolean;  typescriptNative: boolean;}): RuntimeSelection {  // Cold start sensitive + AWS SDK ağır = Node.js  if (characteristics.coldStartSensitive && characteristics.requiresAWSSDK) {    return 'nodejs';  }
  // CPU intensive + warm trafik = Bun  if (characteristics.cpuIntensive && !characteristics.coldStartSensitive) {    return 'bun';  }
  // Background job'lar + TypeScript = Deno  if (!characteristics.coldStartSensitive && characteristics.typescriptNative) {    return 'deno';  }
  // Varsayılan: production güvenliği için Node.js  return 'nodejs';}

Mimari örneği:

  • API Gateway endpoint'leri: Node.js (I/O-bound, cold start sensitive)
  • Image processing: Bun container (CPU-intensive, yüksek bellek)
  • Scheduled task'lar: Deno container (TypeScript-native, tahmin edilebilir trafik)

Değerlendirilecek Alternatif Yaklaşımlar

Önce Node.js'i Optimize Et

Runtime değiştirmeden önce, Node.js optimizasyonlarını değerlendir:

typescript
// Kötü: handler'da initializationexport async function handler(event: APIGatewayEvent) {  const db = await createDatabaseConnection(); // Cold start cezası  // ...}
// İyi: modül seviyesinde initializationconst db = await createDatabaseConnection(); // Handler dışında
export async function handler(event: APIGatewayEvent) {  // Önceden initialize edilmiş db'yi kullan}

Tree shaking için ES Module'ler:

typescript
// Eski: CommonJS tüm modülü import ederconst AWS = require('aws-sdk');
// Yeni: ES Module'ler sadece gerekli kodu import ederimport { S3Client } from '@aws-sdk/client-s3';

Genellikle runtime değişikliği karmaşıklığı olmadan benzer performans iyileştirmeleri sağlar.

Maksimum Performans için Rust veya Go'yu Değerlendir

Gerçekten CPU-intensive workload'lar için, derlenmiş diller tüm JavaScript runtime'larından daha iyi performans gösterir:

Performans karşılaştırması (1000 iterasyon):

  • Node.js: ~100ms
  • Bun: ~50ms
  • Rust: ~5ms (Node.js'ten 20x daha hızlı)

Trade-off'lar:

  • Dramatik olarak daha hızlı execution ve daha düşük bellek kullanımı
  • Farklı dil takım becerisi yatırımı gerektirir
  • Daha uzun derleme süreleri, hızlı iterasyon için daha az esneklik

Temel Çıkarımlar

Performans Gerçeği Kontrolü

  1. Cold start'lar çoğu Lambda workload'ı için warm execution'dan daha önemli. Function'ların %95'i I/O-bound, CPU-bound değil. AWS'nin Node.js optimizasyonları 3-4x daha hızlı cold start sağlıyor. Alternatif runtime'lar benchmark'larda kazanıyor ancak kullanıcı deneyiminde kaybediyor.

  2. Deno en iyi alternatif runtime dengesini sunuyor. En hızlı warm invocation'lar (Node.js'ten %36 daha hızlı), cache pre-warming ile makul cold start'lar (~185ms) ve container-based deployment Bun'ın layer yaklaşımından daha olgun.

  3. Bun Lambda production kullanımı için dikkatli değerlendirme gerektiriyor (2025 sonu itibariyle). En yavaş cold start'lar (~548ms ortalama), gelişmiş ama hala evrimleşen AWS SDK uyumluluğu ve daha küçük production kullanıcı tabanı sınırlı troubleshooting kaynakları anlamına geliyor. Lokal geliştirme ve cold start etkisinin ölçüldüğü dikkatli production pilot'ları için uygun.

Maliyet ve Karmaşıklık Trade-off'ları

  1. Container image'ları operasyonel overhead ekliyor. Base image güvenlik güncellemelerini yönetmek gerekiyor, daha uzun deployment süreleri (build + push vs kod upload) ve ECR depolama maliyetleri. Fayda: runtime environment üzerinde tam kontrol.

  2. Lambda Layer'ları daha basit ama daha sınırlı. Daha hızlı deployment'lar ve daha kolay rollback'ler, birden fazla function arasında runtime paylaşımı, ancak 250MB sıkıştırılmamış limit ve mimari eşleştirme gerektiriyor.

  3. Maliyet tasarrufları nadiren karmaşıklığı haklı çıkarıyor. Çoğu workload %20'den az maliyet azalması görüyor ve development overhead genellikle tasarrufları aşıyor. İstisna: yüksek hacimli CPU-intensive workload'lar.

Implementasyon Önerileri

  1. Runtime değiştirmeden önce Node.js optimizasyonu ile başla. Top-level await, ES module'ler, layer kullanımı ve provisioned concurrency genellikle alternatif runtime faydalarının %80'ini sıfır operasyonel karmaşıklık artışıyla sağlar.

  2. Değiştiriyorsan, aşamalı yaklaşım kullan. Kritik olmayan bir function ile proof-of-concept çalıştır (1-2 hafta), sınırlı trafikle production pilot deploy et (2-4 hafta), genişletmeden önce gerçek performansı ve maliyetleri ölç ve rollback kabiliyetini koru.

  3. Container image'ları Lambda custom runtime'larının geleceği. Layer yaklaşımı 300-500ms initialization overhead ekliyor. Container'lar cache pre-warming ve optimizasyon sağlıyor. Sektör trendi konteynerleştirilmiş deployment'ları tercih ediyor.

  4. Benchmark'ları değil workload karakteristiklerini değerlendir. Burst trafikli I/O-bound? Node.js managed runtime kullan. Sabit trafikli CPU-bound? Bun veya derlenmiş dilleri değerlendir. TypeScript'li background job'lar? Container'larla Deno dene. Basit transformasyonlar? Edge'de CloudFront Function'ları kullan.

Production Dersleri

  1. Lokal olarak Lambda benzeri kısıtlamalarla test et. Lambda Runtime Interface Emulator kullan, read-only filesystem ve bellek limitlerini simüle et, warm invocation'lar değil cold start davranışını test et.

  2. Runtime-specific metric'leri izle. Initialization duration'ı execution'dan ayrı olarak track et, cold start yüzdesini ölç (sadece ortalama duration değil) ve uyumluluk sorunları için AWS SDK hatalarında alert oluştur.

  3. En iyi runtime yönetmen gerekmeyen runtime. AWS Node.js Lambda optimizasyonuna yoğun yatırım yapıyor, resmi runtime'lar otomatik olarak güvenlik yamalarını alıyor ve ekosistem tooling'i Node.js'i varsayıyor. Alternatif runtime'lar varsayılan seçim değil, belirli kullanım durumları için mantıklı.

Araçlar ve Kaynaklar

Bun Lambda Entegrasyonu:

Lambda'da Deno:

Deployment Framework'leri:

  • Native Bun desteği ile SST (Serverless Stack)
  • Container ve layer deployment'ları için AWS CDK
  • Serverless Framework custom runtime konfigürasyonları

Test Araçları:

  • Lokal test için Lambda Runtime Interface Emulator
  • Custom runtime tracing için AWS X-Ray
  • Runtime-specific ölçümler için CloudWatch Embedded Metrics Format

İlgili Yazılar