Lompat ke konten
dokumentasi.id
Commit ecba1e8
v2
Star2,8 rb

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

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-9. 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-19.

MedanTipeArti
tokenatomic.Int32Jumlah token saat ini, titik awal maxToken ternormalisasi
maxTokenint32Batas atas token setelah normalisasi
thresholdint32Ambang maxToken/2 yang menentukan over budget
successTokenint32Token yang ditambahkan pada event sukses
failureTokenint32Token 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-39.

MedanRumusContoh NewTokenErrorBudget(100, 0.1)
maxTokenmaxToken * normaliser1000000
thresholdmaxToken / 2500000
successTokenint32(tokenRatio * normaliser)1000
failureToken-normaliser-10000

⚠️ NewTokenErrorBudget melakukan panic bila maxToken melebihi maxAllowedToken (107374), dengan pesan max token exceeds allowed limit (%d). Nilai yang terlalu besar tidak dipotong, melainkan menggagalkan proses: internal/error_budget.go:26-28.

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-73.

Kondisi failurePercenttokenRatioAkibat pada penghitung
<= 00successToken nol, sukses tidak pernah memulihkan budget
>= 100float32(maxToken)satu event sukses praktis mereset pelacak
selain itufailurePercent / (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-57.

failurePercentsuccess per failurefailure sampai over budget
1001000
1011125
1021286
1031500
1041800

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-92. Failure() mengurangi failureToken dan menjepitnya di nol: internal/error_budget.go:94-111. IsOverBudget() hanya membaca token <= threshold, sedangkan Reset() menyimpan ulang maxToken: internal/error_budget.go:113-130.

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.
Fig. 1: Perpindahan keadaan ErrorBudgetKegagalan 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-84. Failure() langsung mengembalikan true bila token <= 0, sehingga budget yang sudah habis tidak dihitung ulang: internal/error_budget.go:101-103.

⚠️ Nilai balik Success() juga berarti over budget. Pemanggil di jalur retry mengabaikannya dengan _ =, jadi sukses di bawah threshold tidak menghentikan loop; hanya Failure() yang menentukan break.

Titik panggil di jalur retry

httpclient.Client menyimpan budget pada medan retryErrorBudget bertipe *internal.ErrorBudget: httpclient/client.go:16-25. Budget itu tidak dibuat oleh NewClient; tanpa option, medannya nil: httpclient/client.go:36-40. 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-144.

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.
Fig. 2: ErrorBudget di dalam loop retry httpclientskipRetry 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-202. 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-206.

Budget dipasang lewat WithRetryErrorBudgetToken dan WithRetryErrorBudgetPercent, yang langsung memanggil konstruktor dan menyimpannya ke medan retryErrorBudget: httpclient/options.go:57-86. 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-160.