← Semua pembelajaran / Go Nol → Enterprise
Fase 10 · Deploy di AWS

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.al2023 dengan binari bernama bootstrap.
  • 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

BebanPilihAlasan
API bertrafik stabilECSLebih murah pada volume tinggi; koneksi terkendali
Trafik sangat tidak menentu (0 → lonjakan)LambdaBayar per invokasi; skala seketika
Pemroses peristiwa S3 / SQSLambdaIntegrasi bawaan, tanpa proses yang harus hidup
Job terjadwal jarangLambda + EventBridgeTidak ada yang berjalan saat menganggur
Worker antrean sepanjang hariECSLebih murah untuk beban terus-menerus
Pekerjaan > 15 menitECSBatas keras Lambda
WebSocket, SSEECSLambda 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

  1. ARM64 (Graviton) — lebih murah dan sering lebih cepat.
  2. Binari kecil — -ldflags="-s -w", tanpa dependensi yang tidak perlu.
  3. Bangun dependensi di init(), bukan di dalam handler.
  4. Naikkan memori — CPU di Lambda proporsional dengan memori; 1 GB sering lebih murah dan lebih cepat daripada 512 MB.
  5. Provisioned concurrency hanya kalau cold start benar-benar terasa oleh pengguna.
  6. 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.