1. Mengapa Rate Limiter Lokal Pasti Runtuh di Lingkungan Multi-Pod?
Pernahkah kamu terbangun panik di tengah malam karena ponselmu dibombardir notifikasi alert sistem saat CPU database utama mendadak menyentuh 100% dan server mulai bertumbangan satu per satu?
Pengalaman menegangkan itulah yang pernah saya dan tim hadapi ketika pertama kali menggelar kampanye penjualan kilat (flash sale) tiket konser dengan puluhan ribu pengguna yang menyerbu endpoint API di detik yang sama. Di lingkungan pengujian lokal atau server tunggal, sistem rate limiterℹ berbasis in-memoryℹ Go (golang.org/x/time/rate) tampak bekerja sempurna. Kami sempat merasa tenang karena seluruh kuota tampak terhitung rapi di RAM proses server.
Namun, begitu arsitektur di-scale secara horizontal ke kluster 30 Podℹ di balik Load Balancerℹ, ilusi rasa aman tersebut hancur seketika:
- Kebocoran Kuotaℹ Massal (Global Quota Leakage): Jika batas aman ditetapkan 10 request/menit per pengguna, klien bot yang cerdas dapat menembak 30 podℹ berbeda dan berhasil mengeksekusi 300 request dalam satu menit tanpa pernah terkena throttle!
- Isolasi Memori Antar-Podℹ: Server Podℹ A tidak memiliki visibilitas terhadap berapa banyak request yang sudah dilayani oleh Podℹ B atau Podℹ C.
- Database Connection Poolℹ Exhaustion: Ribuan request liar yang lolos dari saringan membanjiri PostgreSQL hingga connection poolℹ terkuras habis dan situs mengalami cascading failureℹ.
Dari insiden tersebut, kami menarik satu kesimpulan arsitektural: pada sistem terdistribusi, batas kuota harus disimpan di centralized shared state storeℹ yang memiliki eksekusi atomikℹ. Pilihan terbaik kami jatuh pada Redis 7 karena model eksekusi single-threadedℹ, latensi sub-milidetik, dan dukungannya terhadap script Lua.
Blueprint Solusi: Distributed Sliding-Window Rate Limiterℹ Untuk membasmi kebocoran kuotaℹ tanpa mengorbankan latensi API gateway, kami merancang arsitektur pembatasan terdistribusi berbasis 3 pilar:
- 1Centralized Shared Stateℹ: Menghubungkan seluruh podℹ Go ke kluster Redis berlatensi sub-milidetik (<1ms) untuk melacak jejak kuota global.
- 2Atomicℹ Lua Evaluation: Memindahkan seluruh siklus validasi (pembersihan window lama, penghitungan kuota aktif, dan pencatatan timestamp baru) ke dalam satu script Lua atomikℹ di Redis.
- 3Zero-Allocation Go Client dengan Fail-Open: Mengintegrasikan hash
EVALSHAuntuk menghemat bandwidth jaringan dan kebijakan fail-open agar gangguan Redis tidak mematikan ketersediaan API utama.
2. Peta Topologi dan Matriks Komparasi 5 Algoritma Rate Limiting
Sebelum menentukan algoritma terbaik, tim kami mengevaluasi karakteristik mekanis dan kompromi dari lima algoritma rate limitingℹ standar industri:
3. Menimbang 5 Algoritma Rate Limiting di Medan Produksi
1. Token Bucket: Sahabat Terbaik Pengguna Manusia
Token ditambahkan secara berkala ke dalam wadah berkapasitas tertentu. Begitu request tiba, sistem mengambil satu token.
- Kelebihan: Sangat ramah terhadap pengguna riil karena mengizinkan lonjakan singkat (traffic burstℹ) yang wajar, misalnya saat pengguna me-refresh halaman dashboard.
- Kekurangan: Sinkronisasi pengisian token di kluster multi-region membutuhkan sinkronisasi waktu clock yang presisi.
2. Leaky Bucket: Penyelamat Integrasi Pihak Ketiga
Request yang masuk ditampung dalam antrean dan dialirkan keluar dengan kecepatan konstan yang teratur.
- Kelebihan: Menghaluskan trafik (traffic smoothing) secara sempurna; server downstream tidak akan pernah kaget menerima lonjakan mendadak.
- Kekurangan: Menambah latensi pada request yang mengantre saat trafik padat.
3. Fixed Window Counter: Jebakan Boundary Burst
Membagi waktu ke dalam blok statis (contoh: 10:00:00 - 10:00:59). Setiap request menambah counter integer.
- Bahaya Fatal: Klien dapat menembakkan 100 request di detik
:59dan 100 request lagi di detik:00jendela berikutnya. Akibatnya, 200 request lolos dalam rentang 2 detik saja! Ini berpotensi meruntuhkan database.
4. Sliding Window Log: Presisi Sempurna dengan Biaya RAM
Menyimpan riwayat timestamp setiap request ke dalam Redis Sorted Set (ZSET).
- Kelebihan: Nol kebocoran kuotaℹ.
- Kekurangan: Memakan memori besar jika ada jutaan request aktif per menit karena setiap request memakan 1 elemen ZSET.
5. Sliding Window Counter (Redis Atomic Lua): Pilihan Arsitektur Kami
Menggabungkan keunggulan jendela geser dinamis dengan eksekusi atomikℹ single-roundtrip di mesin Redis.
4. Tragedi Race Condition di Produksi: Mengapa Pola Naif GET-Lalu-INCR Pasti Gagal?
Masalah: Jeda Waktu Read-Then-Write yang Meloloskan Ribuan Bot
Kesalahan paling umum yang sering dibuat pengembang di awal adalah menulis logika rate limiterℹ seperti ini:
Ketika 5.000 request konkurenℹ tiba dalam milidetik yang sama, puluhan podℹ server akan membaca nilai count yang sama (misalnya nilai 9) sebelum ada podℹ yang sempat memanggil INCR. Akibatnya, seluruh request tersebut menganggap kuota masih di bawah batas (9 < 10) dan ribuan request lolos secara ilegal ke database!
Solusi: Eksekusi Atomik Tanpa Jeda dengan Script Lua
Redis menjamin bahwa script Lua dieksekusi secara transactionalℹ dan atomikℹ. Tidak ada perintah klien lain yang dapat menyela di tengah-tengah jalannya script.
Berikut adalah script Lua atomikℹ yang membersihkan data lama, menghitung kuota aktif, dan mencatat request baru dalam 1 langkah tak terputus:
5. Implementasi Golang Skala Tinggi dengan EVALSHA & Fail-Open
Di sisi aplikasi Go, kita membungkus script Lua tersebut menggunakan pustaka go-redis/v9. Dengan memanfaatkan fungsi redis.NewScript, aplikasi secara cerdas mengirimkan hash SHA-1 (EVALSHA) alih-alih mengirimkan teks mentah script di setiap request, menghemat ribuan kilobyte bandwidth jaringan:
6. Hasil Riil di Produksi: Metrik Sebelum vs Sesudah
Pengujian arsitektur dilakukan menggunakan tool k6 pada kluster multi-podℹ Go yang berhadapan langsung dengan beban 200.000+ request per detik:
7. 4 Pembelajaran Penting dari Medan Produksi
- 1Jangan Percaya Local Memory State di Multi-Podℹ: Memori lokal server tidak tahu apa yang sedang terjadi di server sebelah. Selalu gunakan shared state storeℹ untuk proteksi global.
- 2Hindari Read-Then-Write Tanpa Lock Atomikℹ: Jika keputusan penulisan bergantung pada hasil pembacaan sebelumnya, kamu wajib menggunakan perintah atomikℹ (seperti Redis Lua) untuk melenyapkan jeda race conditionℹ.
- 3Terapkan Kebijakan Fail-Open secara Bijak: Pada middleware rate limiterℹ, putuskan kebijakan saat Redis down: lebih baik meloloskan request sementara (fail-open) daripada mematikan seluruh layanan bagi seluruh pelanggan.
- 4Optimalkan dengan EVALSHA: Kirimkan hash SHA-1 dari script Lua, bukan teks mentah, agar bandwidth jaringan internal antar-server tetap longgar.
8. Sandbox & Simulator Interaktif Multi-Algoritma
Kamu dapat mencoba dan mengamati langsung perbedaan mekanis setiap algoritma melalui simulator interaktif di artikel ini:
- Beralih antar tab algoritma (Sliding Windowℹ, Token Bucketℹ, Leaky Bucketℹ, atau Fixed Window).
- Simulasikan request normal dan tombol lonjakan masif (traffic spike) 15 request sekaligus.
- Pada tab Fixed Window, tekan tombol simulasi celah boundary burstℹ untuk menyaksikan langsung bagaimana kuota bisa bocor hingga dua kali lipat di pergantian detik!