7
Error Budget untuk Retry
Retry yang tidak dibatasi memperbesar beban ke backend yang sedang bermasalah. internal.ErrorBudget menutup celah itu: sebuah penghitung token berbobot yang menghentikan retry ketika laju kegagalan melewati ambang yang dikonfigurasi. Semua keadaan disimpan pada satu atomic.Int32 sehingga aman dipakai lintas thread tanpa lock.
Struct ini memakai normaliser bernilai 10000 dan konstanta maxAllowedToken yang membatasi masukan agar perkalian tetap muat di int32: internal/error_budget.go:8-91. Nilai maxAllowedToken adalah ((1 << 30) - 1) / normaliser.
Medan dan arti token
Lima medan menyimpan seluruh keadaan budget. Hanya token yang berubah saat runtime; sisanya ditetapkan sekali oleh konstruktor: internal/error_budget.go:12-192.
| Medan | Tipe | Arti |
|---|---|---|
token | atomic.Int32 | Jumlah token saat ini, titik awal maxToken ternormalisasi |
maxToken | int32 | Batas atas token setelah normalisasi |
threshold | int32 | Ambang maxToken/2 yang menentukan over budget |
successToken | int32 | Token yang ditambahkan pada event sukses |
failureToken | int32 | Token yang ditambahkan pada event gagal, nilainya negatif |
NewTokenErrorBudget: batas dan bobot
NewTokenErrorBudget(maxToken, tokenRatio) mengalikan maxToken dengan normaliser, menetapkan threshold sebesar setengahnya, successToken sebesar tokenRatio * normaliser, dan failureToken sebesar -normaliser (yaitu -10000). Token awal disimpan sebesar maxToken ternormalisasi: internal/error_budget.go:21-393.
| Medan | Rumus | Contoh NewTokenErrorBudget(100, 0.1) |
|---|---|---|
maxToken | maxToken * normaliser | 1000000 |
threshold | maxToken / 2 | 500000 |
successToken | int32(tokenRatio * normaliser) | 1000 |
failureToken | -normaliser | -10000 |
⚠️
NewTokenErrorBudgetmelakukanpanicbilamaxTokenmelebihimaxAllowedToken(107374), dengan pesanmax token exceeds allowed limit (%d). Nilai yang terlalu besar tidak dipotong, melainkan menggagalkan proses: internal/error_budget.go:26-284.
NewPercentErrorBudget: dari persentase ke rasio
NewPercentErrorBudget(minFailureVolume, failurePercent) adalah pembungkus yang menerjemahkan parameter operasional menjadi maxToken dan tokenRatio, lalu memanggil NewTokenErrorBudget. maxToken dihitung sebagai minFailureVolume * 2, dan tokenRatio ditentukan oleh tiga cabang: internal/error_budget.go:41-735.
Kondisi failurePercent | tokenRatio | Akibat pada penghitung |
|---|---|---|
<= 0 | 0 | successToken nol, sukses tidak pernah memulihkan budget |
>= 100 | float32(maxToken) | satu event sukses praktis mereset pelacak |
| selain itu | failurePercent / (100 - failurePercent) | rasio kegagalan terhadap keberhasilan yang ditoleransi |
Angka pada test memperlihatkan perilaku cabang default. Dengan minFailureVolume = 1000 dan failurePercent = 10, budget baru melewati ambang setelah sejumlah kegagalan tertentu; jumlah success per failure menggeser titik itu: internal/error_budget_test.go:11-576.
failurePercent | success per failure | failure sampai over budget |
|---|---|---|
| 10 | 0 | 1000 |
| 10 | 1 | 1125 |
| 10 | 2 | 1286 |
| 10 | 3 | 1500 |
| 10 | 4 | 1800 |
Siklus keadaan Success, Failure, dan Reset
Setiap event masuk lewat Success() atau Failure(), keduanya mengembalikan bool yang berarti over budget. Success() menambah successToken lalu menjepit token dengan CompareAndSwap bila melewati maxToken: internal/error_budget.go:75-927. Failure() mengurangi failureToken dan menjepitnya di nol: internal/error_budget.go:94-1118. IsOverBudget() hanya membaca token <= threshold, sedangkan Reset() menyimpan ulang maxToken: internal/error_budget.go:113-1309.
stateDiagram-v2
[*] --> InBudget
InBudget: InBudget token lebih besar dari threshold
OverBudget: OverBudget token sama atau di bawah threshold
InBudget --> InBudget: Failure, token masih di atas threshold
InBudget --> OverBudget: Failure, token melewati threshold
OverBudget --> OverBudget: Failure, langsung return true
OverBudget --> InBudget: Success bila successToken menutup selisih
OverBudget --> InBudget: Reset, token kembali ke maxToken
InBudget --> InBudget: Success sampai batas maxToken
note right of OverBudget
skipRetry menerima true lalu break
end note
style InBudget fill:#2d7d46,color:#fff
style OverBudget fill:#dc2626,color:#fff
%% fig: Perpindahan keadaan ErrorBudget
%% desc: Kegagalan menarik token ke threshold sehingga status berpindah dari InBudget ke OverBudget, sedangkan Reset dan successToken besar dapat mengembalikannya.Dua jalan pintas mempercepat method tersebut. Success() langsung mengembalikan false tanpa menambah token bila token >= maxToken, sehingga nilai tidak pernah melampaui batas atas: internal/error_budget.go:82-8410. Failure() langsung mengembalikan true bila token <= 0, sehingga budget yang sudah habis tidak dihitung ulang: internal/error_budget.go:101-10311.
⚠️ Nilai balik
Success()juga berarti over budget. Pemanggil di jalur retry mengabaikannya dengan_ =, jadi sukses di bawah threshold tidak menghentikan loop; hanyaFailure()yang menentukanbreak.
Titik panggil di jalur retry
httpclient.Client menyimpan budget pada medan retryErrorBudget bertipe *internal.ErrorBudget: httpclient/client.go:16-2512. Budget itu tidak dibuat oleh NewClient; tanpa option, medannya nil: httpclient/client.go:36-4013. Seluruh method aman terhadap receiver nil dan mengembalikan false, jadi budget yang kosong tidak pernah memblokir retry, dan Reset() tidak panic: internal/error_budget_test.go:128-14414.
graph LR
loop["DoWithRetry loop"] -->|"error atau status retryable"| skip["skipRetry(ctx)"]
skip -->|"IsCtxDone"| s["Success() diabaikan"]
skip -->|"selain itu"| f["Failure() bool"]
f -->|"true: over budget"| brk["break, retry berhenti"]
f -->|"false"| nx["iterasi berikutnya"]
s --> brk
style loop fill:#336791,color:#fff
style skip fill:#f59e0b,color:#000
style f fill:#8b5cf6,color:#fff
style s fill:#8b5cf6,color:#fff
style brk fill:#dc2626,color:#fff
style nx fill:#2d7d46,color:#fff
%% fig: ErrorBudget di dalam loop retry httpclient
%% desc: skipRetry memanggil Failure untuk memutuskan break, sementara Success dipanggil saat context selesai atau saat satu iterasi berhasil.Di dalam loop, satu iterasi yang berhasil memanggil Success() lalu break; kegagalan memanggil skipRetry, yang mengembalikan true bila context sudah selesai (sambil mencatat Success()) atau bila Failure() melaporkan over budget: httpclient/client.go:187-20215. Klien hystrix memakai pola yang sama dengan urutan berbeda: Success() dipanggil bila err == nil atau context selesai, dan Failure() menentukan break: hystrix/hystrix_client.go:198-20616.
Budget dipasang lewat WithRetryErrorBudgetToken dan WithRetryErrorBudgetPercent, yang langsung memanggil konstruktor dan menyimpannya ke medan retryErrorBudget: httpclient/options.go:57-8617. Mekanisme option itu sendiri dibahas di Option pada httpclient, dan paket hystrix menyediakan option serupa seperti dijelaskan di Option dan Fallback hystrix.
Helper testSuccessPerFailure merangkum kontraknya: sejumlah failure di bawah ambang mengembalikan false, satu failure yang melewati ambang mengembalikan true, dan Reset() mengembalikan budget ke keadaan awal. Helper ini juga menyisipkan success di antara failure sesuai rasio yang diuji: internal/error_budget_test.go:146-16018.