Backpressure dan Pembatasan Laju

Melindungi sistem dari lonjakan beban melalui backpressure dan rate limiting.

Daftar isi

Topik ini membahas cara melindungi sistem ketika beban yang datang melampaui kapasitas pengerjaannya, yaitu backpressure sebagai sinyal balik kepada pengirim, load shedding sebagai keputusan membuang sebagian beban, dan rate limiting sebagai pembatas di gerbang masuk. Sebuah simulasi tiga mode penanganan beban menyertai pembahasan dan menampilkan konsekuensi masing-masing pada angka.

Ketika beban melampaui kapasitas#

Setiap sistem memiliki kapasitas proses tetap pada suatu konfigurasi, misalnya dua worker yang menyelesaikan dua permintaan per tick. Ketika laju kedatangan melebihi kapasitas itu dalam waktu yang cukup lama, permintaan menumpuk pada antrean, latensi naik, dan konsumsi memori bertambah. Tanpa mekanisme pengendali, kegagalan tidak berhenti pada satu komponen, karena broker yang kehabisan memori menjatuhkan koneksi, kegagalan menyebar ke pemanggil, dan pemanggil mencoba ulang dengan lebih agresif sehingga beban bertambah lagi. Lingkaran umpan balik seperti ini mengubah lonjakan biasa menjadi kegagalan menyeluruh yang disebut cascading failure.

Lonjakan pendek yang muat pada antrean berbatas memang masih bisa diserap, sebagaimana ditunjukkan topik Queue. Masalahnya muncul ketika kelebihan beban bertahan lebih lama daripada kemampuan penyangga apa pun, sehingga sistem harus memilih siapa yang menanggung tekanan, bukan sekadar menahannya.

Backpressure#

Backpressure adalah sinyal yang dikirim sistem hulu kepada pengirim ketika kapasitasnya habis, sehingga laju kedatangan menurun tanpa tugas yang hilang. Bentuknya beragam, yaitu consumer berhenti menarik pesan saat antrean penuh, producer yang memakai antrean berbatas ditahan pada operasi masukan sampai ada ruang, atau broker menurunkan jendela kredit bagi pengirim. Ciri bersamanya adalah beban kelebihan dipindahkan ke sisi producer yang paling mengetahui konteksnya, bukan dihilangkan secara diam-diam. Simulasi berikut membandingkan tiga mode pada pola kedatangan yang sama, yaitu mode tanpa kontrol, mode backpressure dengan antrean berbatas berkapasitas empat, dan mode load shedding yang membuang permintaan saat antrean penuh.

python backpressure_sim.py
"""Simulasi backpressure: tanpa kontrol, sinyal balik, dan load shedding.

Tiga mode dijalankan pada pola datang yang sama. Mode tanpa-kontrol
memakai antrean tak berbatas. Mode backpressure membuat producer menahan
pengiriman saat antrean penuh, lalu mengirimnya lagi ketika ada ruang.
Mode load-shedding membuang permintaan yang datang saat antrean penuh.
"""

from collections import deque

DATANG = [4, 4, 4, 4, 4, 4, 0, 0, 0, 0]  # permintaan baru per tick
KAPASITAS_PROSES = 2                      # permintaan yang diselesaikan per tick
KAPASITAS_ANTREAN = 4


def jalankan(mode):
    antrean = deque()
    ditahan = deque()   # permintaan yang ditahan di sisi producer
    diproses = 0
    ditolak = 0
    puncak = 0
    baris = []
    for t, datang in enumerate(DATANG, start=1):
        # producer mencoba mengirim ulang permintaan yang ditahannya dahulu
        while ditahan and len(antrean) < KAPASITAS_ANTREAN:
            antrean.append(ditahan.popleft())
        for _ in range(datang):
            if mode == "backpressure" and len(antrean) >= KAPASITAS_ANTREAN:
                ditahan.append(1)
            elif mode == "load-shedding" and len(antrean) >= KAPASITAS_ANTREAN:
                ditolak += 1
            else:
                antrean.append(1)
        for _ in range(KAPASITAS_PROSES):
            if antrean:
                antrean.popleft()
                diproses += 1
        puncak = max(puncak, len(antrean))
        baris.append((t, datang, len(antrean), len(ditahan), ditolak))
    return baris, diproses, puncak, ditolak, len(antrean), len(ditahan)


hasil_a = jalankan("tanpa-kontrol")
hasil_b = jalankan("backpressure")
hasil_c = jalankan("load-shedding")

print("tick  datang  isi(A)  isi(B)  ditahan(B)  isi(C)  ditolak(C)")
for i in range(len(DATANG)):
    t, datang, _, _, _ = hasil_a[0][i]
    print(f"{t:>4}  {datang:>6}  {hasil_a[0][i][2]:>6}  {hasil_b[0][i][2]:>6}  "
          f"{hasil_b[0][i][3]:>10}  {hasil_c[0][i][2]:>6}  {hasil_c[0][i][4]:>9}")
print()
for label, hasil in (("A tanpa-kontrol", hasil_a), ("B backpressure", hasil_b), ("C load-shedding", hasil_c)):
    _, diproses, puncak, ditolak, sisa, ditahan = hasil
    print(f"{label}: diproses {diproses}, puncak antrean {puncak}, ditolak {ditolak}, "
          f"sisa antrean {sisa}, ditahan di producer {ditahan}")
text Keluaran
tick  datang  isi(A)  isi(B)  ditahan(B)  isi(C)  ditolak(C)
   1       4       2       2           0       2          0
   2       4       4       2           2       2          2
   3       4       6       2           4       2          4
   4       4       8       2           6       2          6
   5       4      10       2           8       2          8
   6       4      12       2          10       2         10
   7       0      10       2           8       0         10
   8       0       8       2           6       0         10
   9       0       6       2           4       0         10
  10       0       4       2           2       0         10

A tanpa-kontrol: diproses 20, puncak antrean 12, ditolak 0, sisa antrean 4, ditahan di producer 0
B backpressure: diproses 20, puncak antrean 2, ditolak 0, sisa antrean 2, ditahan di producer 2
C load-shedding: diproses 14, puncak antrean 2, ditolak 10, sisa antrean 0, ditahan di producer 0

Tabel memperlihatkan tiga nasib berbeda dari 24 permintaan yang datang. Mode A memproses semuanya, tetapi antreannya memuncak pada 12 permintaan dan polanya akan terus tumbuh bila lonjakan tidak berhenti pada tick 6. Mode B tidak kehilangan satu pun tugas, sedangkan 10 permintaan tertahan di sisi producer dan dikirim ulang setelah ada ruang, sehingga tidak ada yang hilang tetapi klien menunggu lebih lama. Mode C menolak 10 permintaan di gerbang sehingga antrean tetap pendek, dan keputusan menolak itu diteruskan kepada klien sebagai error yang jelas. Pilihan di antara ketiganya bergantung pada siapa yang paling siap menanggung tekanan, apakah producer, klien, atau sistem tengah.

Load shedding dan rate limiting#

Load shedding adalah keputusan membuang bagian beban secara sengaja ketika sistem berada di atas kapasitas, biasanya dengan menolak permintaan terbaru atau permintaan yang paling tidak bernilai. Penolakan diteruskan sebagai respons error yang eksplisit, misalnya kode status HTTP 429 Too Many Requests, sehingga klien tahu bahwa permintaan itu tidak dikerjakan dan dapat mencoba lagi nanti. Membuang permintaan tanpa memberi tahu klien jauh lebih buruk, karena klien menunggu respons yang tidak akan pernah datang.

Rate limiting bekerja lebih depan, yaitu di gerbang sebelum beban mencapai antrean, dengan membatasi jumlah permintaan yang diterima dari satu sumber dalam satu jendela waktu. Batas dapat diterapkan per pengguna, per API key, atau per alamat IP, dan permintaan yang melewati batas ditolak dengan sinyal yang sama seperti load shedding. Pembatasan ini melindungi sistem dari satu klien yang agresif sekaligus memberi dasar kuota yang adil bagi semua pengguna. Algoritma umum seperti token bucket dan sliding window dibahas pada studi kasus rate limiter.

StrategiKapan dipakaiYang menanggung tekananRisiko utama
tanpa kontrolbeban masih di bawah kapasitassistem tengahkehabisan memori
backpressureproducer dapat menunggu dan mencoba ulangproducerklien menunggu lebih lama
load sheddingkelebihan beban harus ditolak segeraklienkehilangan permintaan bernilai
rate limitingmelindungi gerbang dari satu sumber yang agresifklien yang melampaui kuotabatas yang terlalu ketat

Kesalahan umum#

  1. Memperbesar antrean setiap kali antrean penuh. Langkah itu hanya menunda kegagalan sambil menambah latensi dan memori. Perbaikannya adalah menambah kapasitas pengerjaan, menerapkan backpressure, atau menolak beban secara sengaja.
  2. Membuang permintaan tanpa respons eksplisit. Klien tidak dapat membedakan kegagalan jaringan dari penolakan beban, sehingga mencoba ulang pada saat yang salah. Perbaikannya adalah menolak dengan kode status yang jelas beserta petunjuk kapan pencobaan ulang boleh dilakukan.
  3. Memasang satu batas laju global untuk semua klien. Satu klien yang agresif menghabiskan kuota bersama dan ikut memblokir klien lain. Perbaikannya adalah membagi batas per kunci, misalnya per pengguna atau per API key.

Ringkasan#

  • Backpressure memindahkan tekanan ke producer lewat sinyal balik sehingga tidak ada tugas yang hilang, sedangkan load shedding menolak permintaan secara eksplisit agar antrean tetap pendek.
  • Rate limiting membatasi laju per sumber di gerbang masuk dan menjadi lapisan perlindungan pertama sebelum beban mencapai antrean.
  • Tanpa pengendali, kelebihan beban berubah menjadi cascading failure melalui antrean yang tumbuh dan pencobaan ulang yang agresif.

Referensi#