Go di Lambda — kapan masuk akal
Binari statis Go adalah salah satu runtime terbaik untuk Lambda: cold start-nya kecil dan pemakaian memorinya rendah. Yang menentukan cocok atau tidak bukan itu, melainkan bagaimana aplikasimu bicara ke database.
Intisari
- Go di Lambda memakai runtime
provided.al2023dengan binari bernamabootstrap. - Cold start biasanya di bawah 100 ms — jauh lebih baik daripada runtime yang butuh VM.
- Masalah terbesarnya koneksi database: tiap eksekusi punya poolnya sendiri. Pakai RDS Proxy, atau
MaxOpenConns: 1. - Bangun dependensi di luar handler supaya dipakai ulang antar-invokasi.
- Cocok untuk pekerjaan berbasis peristiwa; ECS lebih cocok untuk API bertrafik stabil.
Bentuk handler
package main
import (
"context"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
// Dibangun SEKALI per eksekusi lingkungan, bukan per invokasi.
// Ini yang membuat invokasi kedua dan seterusnya jauh lebih cepat.
var (
db *pgxpool.Pool
svc *produk.Layanan
log *slog.Logger
)
func init() {
cfg, err := config.Muat()
if err != nil {
panic(err) // gagal di init = invokasi gagal, terlihat jelas
}
log = buatLogger(cfg)
db, err = platform.BukaPool(context.Background(), cfg.DSN.Buka())
if err != nil {
panic(err)
}
svc = produk.NewLayanan(postgres.NewRepo(db), cache.Kosong{}, log)
}
func handler(ctx context.Context, ev events.SQSEvent) error {
for _, pesan := range ev.Records {
var m Muatan
if err := json.Unmarshal([]byte(pesan.Body), &m); err != nil {
// JANGAN kembalikan error untuk pesan rusak: seluruh batch
// akan diproses ulang selamanya sampai masuk DLQ.
log.ErrorContext(ctx, "pesan rusak", "err", err, "id", pesan.MessageId)
continue
}
if err := svc.Proses(ctx, m); err != nil {
return err // batch diproses ulang; job HARUS idempoten (Fase 5)
}
}
return nil
}
func main() {
lambda.Start(handler)
}
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \
go build -tags lambda.norpc -ldflags="-s -w" -o bootstrap ./cmd/lambda
zip fungsi.zip bootstrap
aws lambda update-function-code \
--function-name toko-worker --zip-file fileb://fungsi.zip
Berkasnya harus bernama bootstrap untuk runtime
provided.al2023. Nama lain menghasilkan error Runtime.InvalidEntrypoint yang tidak
menjelaskan apa pun. Dan -tags lambda.norpc memangkas ukuran binari dengan membuang jalur
RPC lama yang tidak dipakai lagi.
Masalah koneksi database
ECS: 10 task × 20 koneksi = 200 koneksi, STABIL
Lambda: 500 eksekusi bersamaan × 20 koneksi = 10.000 koneksi
→ database menolak semuanya, termasuk dari layanan lain
Perbaikan:
A. MaxOpenConns = 1 per eksekusi → 500 koneksi. Masih banyak.
B. RDS Proxy → proxy menyatukan; DIREKOMENDASIKAN
C. Data API (Aurora Serverless) → HTTP, tanpa koneksi sama sekali
D. Batas konkurensi terpesan → batasi jumlah eksekusi bersamaan
// Kalau tanpa RDS Proxy
cfg.MaxConns = 1
cfg.MinConns = 0
cfg.MaxConnIdleTime = 30 * time.Second
Inilah alasan utama API bertrafik tinggi lebih cocok di ECS. Lonjakan trafik di Lambda berubah jadi lonjakan koneksi database yang bisa menjatuhkan bukan hanya fungsimu, tapi seluruh layanan yang memakai database yang sama. Di ECS, jumlah koneksi terikat pada jumlah task — angka yang kamu kendalikan dan bisa kamu hitung (Fase 4).
Lambda versus ECS
| Beban | Pilih | Alasan |
|---|---|---|
| API bertrafik stabil | ECS | Lebih murah pada volume tinggi; koneksi terkendali |
| Trafik sangat tidak menentu (0 → lonjakan) | Lambda | Bayar per invokasi; skala seketika |
| Pemroses peristiwa S3 / SQS | Lambda | Integrasi bawaan, tanpa proses yang harus hidup |
| Job terjadwal jarang | Lambda + EventBridge | Tidak ada yang berjalan saat menganggur |
| Worker antrean sepanjang hari | ECS | Lebih murah untuk beban terus-menerus |
| Pekerjaan > 15 menit | ECS | Batas keras Lambda |
| WebSocket, SSE | ECS | Lambda tidak menahan koneksi |
Menjalankan handler net/http di Lambda
import "github.com/awslabs/aws-lambda-go-api-proxy/httpadapter"
// Router chi yang SAMA persis dengan yang berjalan di ECS.
func main() {
h := httpapi.New(svc, log).Rute()
lambda.Start(httpadapter.NewV2(h).ProxyWithContext)
}
Ini membuat pilihan runtime bisa ditunda dan dibalik. Handler-mu tetap http.Handler
biasa (Fase 3); yang berbeda cuma berkas main.go di cmd/. Aplikasi yang sama
bisa berjalan di ECS untuk trafik utama dan di Lambda untuk lingkungan pratinjau — tanpa dua basis kode.
Mengurangi cold start
- ARM64 (Graviton) — lebih murah dan sering lebih cepat.
- Binari kecil —
-ldflags="-s -w", tanpa dependensi yang tidak perlu. - Bangun dependensi di
init(), bukan di dalam handler. - Naikkan memori — CPU di Lambda proporsional dengan memori; 1 GB sering lebih murah dan lebih cepat daripada 512 MB.
- Provisioned concurrency hanya kalau cold start benar-benar terasa oleh pengguna.
- Hindari mengunduh apa pun saat startup — sematkan dengan
//go:embed(Fase 0).
Poin 4 sering berlawanan dengan intuisi: menaikkan memori Lambda juga menaikkan alokasi CPU, sehingga fungsi bisa selesai lebih cepat dan total tagihannya justru turun. Ukur dengan beberapa setelan memori sebelum memutuskan berdasarkan angka per-GB-detik saja.
Latihan: bangun satu fungsi Lambda Go yang memproses pesan SQS, ukur durasi cold start dan
warm start di CloudWatch. Lalu setel MaxConns ke 20, picu 100 invokasi bersamaan, dan amati
metrik DatabaseConnections di RDS — itu masalah yang diselesaikan RDS Proxy.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.