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

5

Retry dan Backoff

Retry di heimdall dipecah menjadi dua kontrak terpisah. Retriable menentukan berapa lama menunggu sebelum percobaan berikutnya, dan Backoff menghitung besaran interval itu. Keduanya hanya punya satu method, sehingga strategi penundaan bisa diganti tanpa menyentuh loop retry di klien.

Dua interface dan komposisinya

Retriable mendefinisikan satu method, NextInterval(retry int) time.Duration retry.go:5-8. Backoff juga satu method, Next(retry int) time.Duration backoff.go:9-12.

NewRetrier membungkus sebuah Backoff menjadi Retriable. Struct retrier menyimpan field backoff, dan NextInterval meneruskan nilai retry ke r.backoff.Next(retry) retry.go:19-28 retry.go:35-38.

graph LR
  CB["NewConstantBackoff"] --> BO["Backoff"]
  EB["NewExponentialBackoff"] --> BO
  BO --> NR["NewRetrier"]
  NR --> RT["Retriable"]
  RF["NewRetrierFunc"] --> RT
  NN["NewNoRetrier"] --> RT
  RT --> CL["Client.retrier.NextInterval"]
  style BO fill:#f59e0b,color:#000
  style RT fill:#f59e0b,color:#000
  style CB fill:#8b5cf6,color:#fff
  style EB fill:#8b5cf6,color:#fff
  style NR fill:#8b5cf6,color:#fff
  style RF fill:#8b5cf6,color:#fff
  style NN fill:#8b5cf6,color:#fff
  style CL fill:#2d7d46,color:#fff
  %% fig: Komposisi Backoff dan Retriable
  %% desc: Dua konstruktor backoff menghasilkan Backoff, NewRetrier membungkusnya menjadi Retriable, sedangkan NewRetrierFunc dan NewNoRetrier langsung menghasilkan Retriable.
Fig. 1: Komposisi Backoff dan RetriableDua konstruktor backoff menghasilkan Backoff, NewRetrier membungkusnya menjadi Retriable, sedangkan NewRetrierFunc dan NewNoRetrier langsung menghasilkan Retriable.

Tabel berikut merangkum lima konstruktor yang tersedia di paket heimdall.

KonstruktorParameterNilai balik
NewConstantBackoffbackoffInterval, maximumJitterInterval time.DurationBackoff backoff.go:19-30
NewExponentialBackoffinitialTimeout, maxTimeout time.Duration, exponentFactor float64, maximumJitterInterval time.DurationBackoff backoff.go:44-57
NewRetrierbackoff BackoffRetriable retry.go:23-28
NewRetrierFuncf RetriableFuncRetriable retry.go:30-33
NewNoRetriertidak adaRetriable retry.go:43-50

Perpindahan percobaan di klien HTTP

Loop di Client.Do memanggil c.retrier.NextInterval(i-1) sebelum percobaan ulang, lalu menunggu lewat internal.SleepInterruptible httpclient/client.go:149-155. Argumen i-1 berarti percobaan ulang pertama memakai retry = 0. Klien baru memakai NewNoRetrier() sebagai nilai default retry.go:43-50 httpclient/client.go:39, dan strategi diganti dengan option WithRetrier httpclient/options.go:30-35. Urutan request secara utuh dijelaskan di Siklus Request dan Retry.

stateDiagram-v2
  [*] --> Attempt
  Attempt --> Success: "errs = nil"
  Attempt --> Sleep: "error atau StatusCode >= 500"
  Sleep --> Attempt: "SleepInterruptible(NextInterval(i-1))"
  Sleep --> Aborted: "ctx.Done()"
  Attempt --> Exhausted: "i == retryCount"
  Success --> [*]
  Exhausted --> [*]
  Aborted --> [*]
  style Success fill:#2d7d46,color:#fff
  style Sleep fill:#f59e0b,color:#000
  style Aborted fill:#dc2626,color:#fff
  style Exhausted fill:#dc2626,color:#fff
  %% fig: Perpindahan percobaan retry
  %% desc: Percobaan yang gagal masuk ke state Sleep dengan durasi dari NextInterval(i-1); pembatalan context memindahkan loop ke Aborted tanpa menunggu lagi.
Fig. 2: Perpindahan percobaan retryPercobaan yang gagal masuk ke state Sleep dengan durasi dari NextInterval(i-1); pembatalan context memindahkan loop ke Aborted tanpa menunggu lagi.

SleepInterruptible memilih antara ctx.Done() dan time.After(d), jadi pembatalan context mengembalikan error dan loop retry dihentikan internal/sleep.go:9-16.

Constant backoff

NewConstantBackoff menyimpan backoffInterval dan maximumJitterInterval dalam satuan milidetik, dan menetralkan jitter negatif menjadi 0 backoff.go:19-30. Next mengembalikan interval tetap ditambah jitter acak rand.Int64N(maximumJitterInterval+1) milidetik backoff.go:32-35. Nilai retry tidak mempengaruhi hasil.

retryNext(retry) dengan backoffInterval 100ms dan jitter 0
0100ms
1100ms
2100ms
3100ms

Angka di atas berasal dari TestConstantBackoffNextTime backoff_test.go:83-92. Lewat NewRetrier, NewConstantBackoff(2ms, 1ms) menghasilkan NextInterval(1) minimal 2ms retry_test.go:19-29.

Exponential backoff

NewExponentialBackoff menerima initialTimeout, maxTimeout, exponentFactor, dan maximumJitterInterval backoff.go:44-57. Next menormalkan retry < 0 menjadi 0, lalu memakai math.Min(initialTimeout*factor^retry, maxTimeout) dan menambahkan jitter acak backoff.go:59-65.

retryNext(retry), initialTimeout 100ms, maxTimeout 1000ms, exponentFactor 2.0, jitter 0
0100ms
1200ms
2400ms
3800ms
41000ms, dipotong maxTimeout

Empat baris pertama diuji di TestExponentialBackoffNextTime backoff_test.go:10-19, sedangkan pemotongan pada retry = 4 diuji di TestExponentialBackoffMaxTimeoutCrossed backoff_test.go:32-38. Jika maxTimeout dinaikkan menjadi 1600ms, nilai Next(4) tepat 1600ms backoff_test.go:32-38. Nilai retry negatif menghasilkan interval percobaan pertama, yaitu 100ms backoff_test.go:48-54. Lewat NewRetrier dengan initialTimeout 2ms dan maxTimeout 10ms, NextInterval(1) minimal 4ms retry_test.go:10-17.

Jitter menambah rentang acak di atas nilai dasar.

maximumJitterIntervalRentang Next(1) dengan initialTimeout 100ms dan exponentFactor 2.0
0tepat 200ms pada 10000 iterasi backoff_test.go:56-63
1ms200ms sampai 201ms backoff_test.go:65-72
50ms200ms sampai 250ms backoff_test.go:74-81

⚠️ maximumJitterInterval negatif dinetralkan menjadi 0 di dalam konstruktor untuk menghindari panic saat membangkitkan jitter acak backoff.go:20-24. Perilaku ini diuji dengan nilai -1ms yang tetap menghasilkan 100ms, 200ms, 400ms, dan 800ms backoff_test.go:21-30. Jangan mengandalkan jitter negatif sebagai pembatas interval.

Adapter fungsi dan null object

RetriableFunc adalah tipe fungsi func(retry int) time.Duration dengan method NextInterval yang hanya memanggil fungsi tersebut retry.go:10-17. NewRetrierFunc mengembalikan f apa adanya sebagai Retriable, sehingga perhitungan interval bisa ditulis langsung tanpa struct backoff retry.go:30-33. Pola ini dipakai pada test retrier linear yang mengembalikan retry milidetik dan 0 untuk retry <= 0 retry_test.go:31-42.

NewNoRetrier menyediakan null object: NextInterval selalu mengembalikan 0 * time.Millisecond retry.go:43-50, dan test memastikan NextInterval(1) bernilai nol retry_test.go:44-50. Ini membuat klien tanpa konfigurasi tidak menunggu di antara percobaan.