Serverless Patterns & Cold Start — DevOps

Pola Arsitektur Serverless Serverless bukan hanya Lambda. Ini adalah mindset: decompose aplikasi menjadi fungsi kecil yang event-driven. Common Patterns 1…

Serverless patterns adalah cara menyusun banyak function kecil yang dipicu event supaya bersama-sama mengerjakan tugas besar: membagi kerja lewat antrean, bertahan saat event terkirim dua kali, dan mengatur cold start. Bayangin dapur katering di hari pesanan besar. 40 nota masuk sekaligus, dan kepala dapur membagikannya ke beberapa juru masak (fan-out). Tiap juru masak yang baru datang harus menata mejanya dulu (cold start): lamanya tergantung seberapa berat tas peralatannya (ukuran paket), jenis kompornya (runtime), dan persiapan yang dia lakukan sebelum memasak (init code). Printer nota kadang mencetak nota yang sama dua kali, jadi juru masak yang rapi mengecek buku cap dulu sebelum memasak (handler idempotent).

🧪 Tebakan cepat: supaya gak kena cold start, Lambda di-ping tiap 5 menit (keep-warm). Lalu 20 pesan datang bersamaan dan konkurensinya 20. Berapa environment yang tetap kena cold start? Tebak dulu, jawabannya ada di bagian "Provisioned concurrency vs keep-warm".

Cold start di jam virtual

Pelajaran AWS Lambda Deep Dive memperlihatkan Init Duration di baris REPORT. Isinya terdiri dari beberapa bagian, dan tiap bagian punya penyebabnya sendiri. Angka contoh dari Lab:

Bagian cold start Penyebab Contoh di Lab
Memuat paket ukuran kode dan dependency paket 1 MB: 30 ms, paket 60 MB: 450 ms
Menyalakan runtime bahasa dan mesinnya Node.js 22: 120 ms, Python 3.13: 100 ms, Java 21: 900 ms
VPC dulu membuat network interface saat cold start 0 ms (sejak 2019 dibuat saat deploy)
Init code kode di luar handler buat client: 20 ms, ambil secret + buka koneksi DB: 380 ms

Jadi Node.js dengan node_modules 60 MB dan init berat butuh 450 + 120 + 380 = 950 ms. Paket yang di-bundle jadi 1 MB dengan init ringan cuma 30 + 120 + 20 = 170 ms. Java dengan paket 60 MB dan init berat: 1.730 ms. VPC dulu terkenal menambah sampai sekitar 10 detik, tapi sejak 2019 AWS membuat network interface-nya saat function dibuat atau diubah, bukan saat cold start, jadi sekarang hampir gak berpengaruh.

Menekan cold start

Provisioned concurrency vs keep-warm

Provisioned concurrency menyiapkan sejumlah environment yang sudah di-init sebelum request datang. Dengan 5 environment 1 GB, biayanya sekitar $54 per bulan (harga contoh), dibayar walau gak ada request sama sekali. Di Lab, konkurensi 5 + provisioned 5 membuat ekspor jalan tanpa satu pun cold start.

Keep-warm adalah trik lama: EventBridge memanggil function tiap 5 menit supaya environment-nya gak dibuang. Murah, tapi satu ping cuma menjaga satu environment. Saat 20 pesan datang bersamaan, 1 environment sudah warm dan 19 lainnya tetap cold start. Itu jawaban tebakannya. Kalau latency harus stabil dan tim siap membayar, pakai provisioned concurrency.

Fan-out lewat antrean

Tombol "Ekspor semua" di catatan-api gak membuat 40 PDF di satu request. API memasukkan satu pesan per catatan ke antrean SQS ekspor-catatan, lalu Lambda catatan-ekspor-worker mengambilnya. Maximum concurrency di trigger SQS menentukan berapa environment yang bekerja bersamaan, sekaligus melindungi database dari banjir koneksi (pelajaran Connection Pooling). Hasil di Lab, 42 kiriman (lihat bagian berikutnya), 400 ms per PDF, cold start 950 ms:

Maximum concurrency Cold start Selesai dalam
2 2 9,35 detik
5 5 4,55 detik
20 20 2,15 detik
5 + provisioned 5 0 3,6 detik

Pola ini namanya fan-out: satu event dipecah jadi banyak kerja kecil yang jalan paralel. Pasangannya fan-in: menunggu semuanya selesai lalu menggabungkan hasilnya, misalnya menghitung item yang sudah selesai di tabel lalu membuat ZIP. Contoh klasiknya video yang baru di-upload: tiga Lambda jalan paralel untuk thumbnail, transcode ke HLS, dan metadata, lalu database di-update setelah ketiganya selesai. Pola lain: pipeline (tiap langkah menunggu langkah sebelumnya, misalnya S3 → validasi → SQS → proses) dan API composition (tiap route HTTP satu function).

Retry dan handler idempotent

SQS standard queue menjamin pesan terkirim at-least-once: paling sedikit sekali, kadang dua kali. Pesan yang gagal atau melewati visibility timeout juga dikirim ulang. Event async seperti S3 dicoba ulang Lambda sampai 2 kali kalau function-nya error. Di Lab, pesan #7 dan #23 datang dua kali (attributes.ApproximateReceiveCount = "2" di kiriman kedua), jadi ada 42 kiriman untuk 40 catatan.

Handler yang idempotent memberi hasil akhir yang sama walau event yang sama diproses dua kali, persis seperti Idempotency-Key di pelajaran Idempotency:

// catatan-ekspor-worker, dipicu SQS ekspor-catatan (batch size 1)
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
import { buatPdf, simpanPdf } from "./pdf.mjs";

// INIT: sekali per environment, dipakai ulang di warm start
const db = new DynamoDBClient({});

export const handler = async (event) => {
  for (const record of event.Records) {
    const { eksporId, catatanId } = JSON.parse(record.body);
    const key = `ekspor/${eksporId}/${catatanId}.pdf`; // nama tetap: ditulis ulang, gak dobel
    await simpanPdf(key, await buatPdf(catatanId));
    try {
      await db.send(new PutItemCommand({
        TableName: "ekspor-item",
        Item: { pk: { S: `${eksporId}#${catatanId}` }, file: { S: key } },
        ConditionExpression: "attribute_not_exists(pk)",
      }));
    } catch (err) {
      if (err.name !== "ConditionalCheckFailedException") throw err;
      console.log(`duplikat dilewati: ${record.messageId}`);
    }
  }
};

Kapan bukan serverless

⚠️ Jebakan yang sering ketemu

TL;DR: Cold start = memuat paket (ukuran) + menyalakan runtime (Node.js dan Python cepat, Java lambat tanpa SnapStart) + init code; VPC sejak 2019 hampir gak menambah apa-apa. Di Lab: 950 ms jadi 170 ms dengan paket 1 MB dan init ringan. Import modular dari AWS SDK v3 (v2 sudah berhenti didukung), dan buat client seperti new DynamoDBClient({}) sekali di luar handler; di dalam handler ia dibuat ulang di tiap invocation. Provisioned concurrency menjaga environment tetap warm dengan biaya tetap; keep-warm cuma menjaga satu, jadi lonjakan 20 pesan masih 19 cold start. Fan-out lewat SQS dengan maximum concurrency membagi kerja secara paralel tanpa membanjiri database, dan fan-in menggabungkan hasilnya (contoh: thumbnail, HLS, metadata paralel). SQS dan event async bisa mengirim event yang sama dua kali, jadi handler harus idempotent: nama hasil yang tetap dan conditional write. Serverless gak cocok untuk proses lebih dari 15 menit, WebSocket dan game server yang stateful, dan trafik tinggi yang stabil.

Yang akan kamu pelajari