Request Collapsing Singleflight: Solusi Mencegah Thundering Herd

Request Collapsing atau yang populer disebut pola Singleflight adalah teknik optimasi backend untuk menggabungkan beberapa permintaan (request) identik yang masuk secara bersamaan ke dalam satu eksekusi tunggal. Ketika ratusan atau ribuan klien meminta data yang sama persis dalam milidetik yang sama, server hanya mengeksekusi satu proses fetch ke database atau layanan hulu (upstream), lalu mendistribusikan hasil tunggal tersebut ke semua peminta yang menunggu. Teknik ini menjadi pertahanan utama terhadap masalah Thundering Herd Problem dan Cache Stampede.

Tanpa mekanisme collapsing, lonjakan trafik mendadak pada data yang belum masuk cache (cache miss) akan membuat database kewalahan karena memproses kueri yang sepenuhnya identik secara berulang-ulang secara paralel. Dengan menerapkan singleflight, latensi sistem terjaga, penggunaan CPU dan memori terkontrol, serta mencegah kegagalan total pada infrastruktur backend.

Mekanisme Kerja Singleflight

Secara arsitektural, pola singleflight bekerja menggunakan struktur data peta (map) yang dikunci (mutex) untuk melacak permintaan yang sedang aktif berjalan. Alur kerjanya dapat dijabarkan melalui tahapan teknis berikut:

  1. Penerimaan Request: Klien A, B, dan C secara bersamaan meminta data dengan kunci unik (misal: product:105).
  2. Pemeriksaan Status: Server memeriksa apakah kunci product:105 sedang diproses dalam memori.
  3. Penggabungan (Collapse): Karena Klien A datang pertama, proses pengambilan data ke database dijalankan. Klien B dan C yang datang sepersekian milidetik kemudian tidak menjalankan kueri baru, melainkan digabungkan (attached) ke channel atau promise dari proses Klien A.
  4. Distribusi Hasil: Ketika data dari database berhasil diterima oleh proses Klien A, hasil tersebut disalin dan dikirimkan secara serentak ke Klien A, B, dan C.
  5. Pembersihan Kunci: Setelah selesai, kunci dihapus dari map agar permintaan berikutnya di masa depan dapat memicu proses baru jika diperlukan.

Matriks Perbandingan: Tanpa Singleflight vs Menggunakan Singleflight

Parameter Tanpa Request Collapsing Dengan Request Collapsing (Singleflight)
Beban Database Tinggi (1000 request = 1000 kueri database) Minimal (1000 request = 1 kueri database)
Penggunaan CPU/RAM Lonjakan drastis saat cache miss Stabil dan terkontrol
Risiko Thundering Herd Sangat Tinggi Sangat Rendah / Tereliminasi
Latensi Rata-rata Meningkat tajam akibat antrean database Rendah, karena klien lain langsung mendapat hasil dari memori

Contoh Implementasi Golang (Package golang.org/x/sync/singleflight)

Bahasa pemrograman Go menyediakan implementasi resmi melalui pustaka standar eksternal yang sangat stabil dan banyak digunakan di lingkungan produksi industri:

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
	"golang.org/x/sync/singleflight"
)

var g singleflight.Group

func slowDBQuery(ctx context.Context, key string) (string, error) {
	// Simulasi latensi database
	time.Sleep(500 * time.Millisecond)
	return "Data hasil dari database untuk: " + key, nil
}

func GetData(ctx context.Context, key string) (string, error) {
	// Do melahirkan eksekusi tunggal untuk key yang sama
	val, err, shared := g.Do(key, func() (interface{}, error) {
		return slowDBQuery(ctx, key)
	})

	if err != nil {
	    return "", err
	}
	
	fmt.Printf("Apakah data dibagikan (shared)? %v\n", shared)
	return val.(string), nil
}

func main() {
	var wg sync.WaitGroup
	ctx := context.Background()

	// Mensimulasikan 5 request bersamaan untuk key yang sama
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			result, _ := GetData(ctx, "user_settings_99")
			fmt.Printf("Goroutine %d mendapat: %s\n", id, result)
		}(i)
	}

	wg.Wait()
}

Penerapan pada Bahasa Lain

Meskipun istilah Singleflight dipopulerkan oleh ekosistem Go, konsep serupa dapat diterapkan di berbagai platform teknologi lain:

  • Node.js / JavaScript: Menggunakan pola Promise Memoization atau pustaka pihak ketiga seperti single-flight untuk mencegah pemanggilan fungsi asinkron ganda.
  • Python (Asyncio): Mengelola kamus tugas aktif (active tasks dictionary) di mana koroutin berikutnya melakukan await pada objek Future/Task yang sama jika kunci sedang dikerjakan.
  • Java: Memanfaatkan CompletableFuture yang dikombinasikan dengan ConcurrentHashMap untuk memastikan eksekusi komputasi berat hanya berjalan sekali per kunci aktif.

Troubleshooting dan Batasan Lapangan

Meskipun sangat efektif, pengembang harus memperhatikan beberapa catatan penting saat menerapkan pola ini di sistem skala besar:

  • Distribusi Multi-Server (Distributed Lock): Singleflight standar hanya bekerja di dalam memori satu instance server (in-process). Jika Anda menggunakan arsitektur load balancer dengan banyak pod/server, singleflight lokal tidak akan mencegah duplikasi request antar server yang berbeda. Untuk skala cluster, diperlukan Redis Distributed Lock atau Redlock.
  • Penanganan Timeout: Pastikan konteks pembatalan (context cancellation) diatur dengan benar agar jika request utama gagal atau timeout, seluruh klien yang ikut mengantre tidak mengalami kegagalan senyap.
  • Stale Data: Karena hasil dibagikan ke banyak klien dalam satu jendela waktu, pastikan data tersebut tidak bersifat sangat unik per detik jika memerlukan personalisasi tinggi.