Kronologi Insiden: Worker Pool Starvation pada fasthttp

Rilis integrasi layanan downstream pada microservice gateway berbasis Go Fiber memicu lonjakan p99 latency dari 15ms menjadi 30 detik dalam kurun waktu 3 menit pasca-deployment. Go Fiber berjalan di atas engine fasthttp yang mengalokasikan goroutine dari worker pool internal (secara default menampung hingga 256.000 worker aktif). Karakteristik model konkurensi ini sangat efisien untuk operasi I/O non-blocking, tetapi rentan terhadap fenomena worker starvation.

Ketika layanan downstream mengalami degradasi response time, koneksi HTTP client outbound tertahan tanpa timeout yang membatasi. Setiap incoming request ke Go Fiber menahan satu worker thread dari pool untuk menunggu respons downstream. Dalam hitungan detik, seluruh worker pool terisi penuh oleh koneksi macet. Akibatnya, runtime fasthttp menolak koneksi TCP baru, menyebabkan klien eksternal menerima HTTP 503 Service Unavailable dan lonjakan drastis pada connection reset.

Deteksi Masalah: Observabilitas OTel dan Metrik Prometheus

Analisis kegagalan dilakukan dengan mengkorelasikan metrik HTTP duration pada Prometheus dan distributed tracing OpenTelemetry (OTel). Dashboard Grafana mendeteksi anomali melalui query latensi p99:

histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{app="fiber-gateway"}[1m])) by (le))

Grafik menunjukkan kurva eksponensial bersamaan dengan drop-off metrik fasthttp_open_connections yang menyentuh batas saturasi. Untuk mengidentifikasi titik kemacetan, trace context diekstraksi menggunakan tracing middleware OpenTelemetry.

// Contoh trace context propagation pada fasthttp request
func callDownstream(ctx context.Context, c *fiber.Ctx, url string) ([]byte, error) {
	tr := otel.Tracer("fiber-gateway")
	ctx, span := tr.Start(ctx, "callDownstream")
	defer span.End()

	req := fasthttp.AcquireRequest()
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseRequest(req)
	defer fasthttp.ReleaseResponse(resp)

	req.SetRequestURI(url)
	req.Header.SetMethod(fiber.MethodGet)

	// Inject OTel trace headers ke downstream call
	otel.GetTextMapPropagator().Inject(ctx, &fasthttpHeaderCarrier{h: &req.Header})

	err := client.Do(req, resp)
	if err != nil {
		span.RecordError(err)
		return nil, err
	}
	return resp.Body(), nil
}

Flamegraph OpenTelemetry membuktikan bahwa 99.4% durasi request tertahan pada span downstream HTTP invocation. Gateway menunggu response payload dari service downstream yang mengalami lockup database.

Mitigasi Darurat: Automated Rollback via Kubernetes

Langkah pertama mitigasi kegagalan masif adalah menghentikan traffic ke pod yang bermasalah. Intervensi manual via terminal:

kubectl rollout undo deployment/fiber-gateway -n production

Status rollback dipantau via kubectl rollout status. Untuk mencegah eskalasi manual di masa depan, trigger mitigasi diintegrasikan ke dalam analisis Prometheus SLI menggunakan resource Argo Rollouts:

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: p99-latency-check
spec:
  metrics:
  - name: p99-latency
    interval: 30s
    failureLimit: 1
    provider:
      prometheus:
        address: http://prometheus-server.monitoring:9090
        query: |
          histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{app="fiber-gateway"}[1m])) by (le)) < 2.0

Jika p99 latency melebihi 2 detik selama interval 30 detik pada deployment canary, controller langsung mengaborsi rilis dan mengembalikan traffic ke versi stabil secara otomatis.

Postmortem: Akar Masalah

Investigasi pasca-insiden menyoroti dua kelemahan mendasar pada layer kode:

  • Default Client Timeout Tak Terbatas: Instansiasi fasthttp.Client dibuat secara implisit menggunakan instance global bawaan (fasthttp.Do) yang tidak menetapkan ReadTimeout dan WriteTimeout. Request downstream dapat tertahan selama durasi OS-level TCP keep-alive (jamak mencapai puluhan menit).
  • Absensi Circuit Breaker: Tidak ada isolasi dependensi. Kegagalan atau perlambatan pada satu microservice downstream langsung menjalar ke upstream gateway hingga melumpuhkan seluruh rute endpoint lain yang tidak saling terkait.

Pencegahan: Circuit Breaker dan Konfigurasi fasthttp

Langkah pencegahan mutlak memerlukan konfigurasi instance fasthttp.Client secara eksplisit serta pembungkusan remote call ke dalam pola Circuit Breaker menggunakan pustaka sony/gobreaker.

1. Konfigurasi Ketat fasthttp.Client

var client = &fasthttp.Client{
	ReadTimeout:                   1500 * time.Millisecond,
	WriteTimeout:                  1500 * time.Millisecond,
	MaxIdleConnDuration:          30 * time.Second,
	MaxConnsPerHost:              1000,
	DisableHeaderNamesNormalizing: true,
}

2. Implementasi Middleware / Wrapper sony/gobreaker

package main

import (
	"errors"
	"time"
	"github.com/sony/gobreaker"
	"github.com/valyala/fasthttp"
)

var cb *gobreaker.CircuitBreaker

func init() {
	settings := gobreaker.Settings{
		Name:        "DownstreamServiceBreaker",
		MaxRequests: 5,
		Interval:    10 * time.Second,
		Timeout:     5 * time.Second,
		ReadyToTrip: func(counts gobreaker.Counts) bool {
			failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
			return counts.Requests >= 20 && failureRatio >= 0.5
		},
	}
	cb = gobreaker.NewCircuitBreaker(settings)
}

func ExecuteProtectedCall(req *fasthttp.Request, resp *fasthttp.Response) error {
	_, err := cb.Execute(func() (interface{}, error) {
		err := client.Do(req, resp)
		if err != nil {
			return nil, err
		}
		if resp.StatusCode() >= fasthttp.StatusInternalServerError {
			return nil, errors.New("downstream returned 5xx")
		}
		return nil, nil
	})
	return err
}

3. Definisi Alerting Prometheus

Untuk mendeteksi degradasi downstream sebelum saturasi total pada worker pool terulang, terapkan alert rule berikut:

- alert: DownstreamCircuitBreakerOpen
  expr: rate(circuit_breaker_tripped_total{app="fiber-gateway"}[1m]) > 0
  for: 30s
  labels:
    severity: warning
  annotations:
    summary: "Circuit breaker terbuka pada rute gateway, traffic downstream diputus otomatis."

Pola ini mencegah worker pool starvation pada fasthttp: ketika downstream gagal memenuhi batas SLA, breaker langsung mengembalikan fallback error dalam hitungan mikrodetik tanpa menahan resource pool goroutine.