|
| 1 | +package throttler |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "sync" |
| 7 | + "time" |
| 8 | + |
| 9 | + "github.com/rudderlabs/rudder-go-kit/config" |
| 10 | +) |
| 11 | + |
| 12 | +type timer struct { |
| 13 | + frequency *config.Reloadable[time.Duration] |
| 14 | + limitReached map[string]bool |
| 15 | + mu sync.Mutex |
| 16 | + limitSet bool |
| 17 | + cancel context.CancelFunc |
| 18 | +} |
| 19 | + |
| 20 | +type adaptiveConfig struct { |
| 21 | + enabled bool |
| 22 | + minLimit *config.Reloadable[int64] |
| 23 | + maxLimit *config.Reloadable[int64] |
| 24 | + minChangePercentage *config.Reloadable[int64] |
| 25 | + maxChangePercentage *config.Reloadable[int64] |
| 26 | +} |
| 27 | + |
| 28 | +type Adaptive struct { |
| 29 | + shortTimer *timer |
| 30 | + longTimer *timer |
| 31 | + config adaptiveConfig |
| 32 | +} |
| 33 | + |
| 34 | +func NewAdaptive(config *config.Config) *Adaptive { |
| 35 | + shortTimeFrequency := config.GetReloadableDurationVar(5, time.Second, "Router.throttler.adaptiveRateLimit.shortTimeFrequency") |
| 36 | + longTimeFrequency := config.GetReloadableDurationVar(15, time.Second, "Router.throttler.adaptiveRateLimit.longTimeFrequency") |
| 37 | + |
| 38 | + shortTimer := &timer{ |
| 39 | + frequency: shortTimeFrequency, |
| 40 | + limitReached: make(map[string]bool), |
| 41 | + } |
| 42 | + longTimer := &timer{ |
| 43 | + frequency: longTimeFrequency, |
| 44 | + limitReached: make(map[string]bool), |
| 45 | + } |
| 46 | + |
| 47 | + go shortTimer.run() |
| 48 | + go longTimer.run() |
| 49 | + |
| 50 | + return &Adaptive{ |
| 51 | + shortTimer: shortTimer, |
| 52 | + longTimer: longTimer, |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +func (a *Adaptive) loadConfig(destName, destID string) { |
| 57 | + a.config.enabled = config.GetBoolVar(true, fmt.Sprintf(`Router.throttler.adaptiveRateLimit.%s.%s.enabled`, destName, destID), fmt.Sprintf(`Router.throttler.adaptiveRateLimit.%s.enabled`, destName), `Router.throttler.adaptiveRateLimit.enabled`) |
| 58 | + a.config.minLimit = config.GetReloadableInt64Var(1, 1, fmt.Sprintf(`Router.throttler.adaptiveRateLimit.%s.%s.minLimit`, destName, destID), fmt.Sprintf(`Router.throttler.adaptiveRateLimit.%s.minLimit`, destName), `Router.throttler.adaptiveRateLimit.minLimit`, fmt.Sprintf(`Router.throttler.%s.%s.limit`, destName, destID), fmt.Sprintf(`Router.throttler.%s.limit`, destName)) |
| 59 | + a.config.maxLimit = config.GetReloadableInt64Var(250, 1, fmt.Sprintf(`Router.throttler.adaptiveRateLimit.%s.%s.maxLimit`, destName, destID), fmt.Sprintf(`Router.throttler.adaptiveRateLimit.%s.maxLimit`, destName), `Router.throttler.adaptiveRateLimit.maxLimit`, fmt.Sprintf(`Router.throttler.%s.%s.limit`, destName, destID), fmt.Sprintf(`Router.throttler.%s.limit`, destName)) |
| 60 | + a.config.minChangePercentage = config.GetReloadableInt64Var(30, 1, fmt.Sprintf(`Router.throttler.adaptiveRateLimit.%s.%s.minChangePercentage`, destName, destID), fmt.Sprintf(`Router.throttler.adaptiveRateLimit.%s.minChangePercentage`, destName), `Router.throttler.adaptiveRateLimit.minChangePercentage`) |
| 61 | + a.config.maxChangePercentage = config.GetReloadableInt64Var(10, 1, fmt.Sprintf(`Router.throttler.adaptiveRateLimit.%s.%s.maxChangePercentage`, destName, destID), fmt.Sprintf(`Router.throttler.adaptiveRateLimit.%s.maxChangePercentage`, destName), `Router.throttler.adaptiveRateLimit.maxChangePercentage`) |
| 62 | +} |
| 63 | + |
| 64 | +func (a *Adaptive) Limit(destName, destID string, limit int64) int64 { |
| 65 | + a.loadConfig(destName, destID) |
| 66 | + if !a.config.enabled || a.config.minLimit.Load() > a.config.maxLimit.Load() { |
| 67 | + return limit |
| 68 | + } |
| 69 | + if limit <= 0 { |
| 70 | + limit = a.config.maxLimit.Load() |
| 71 | + } |
| 72 | + newLimit := limit |
| 73 | + if a.shortTimer.getLimitReached(destID) && !a.shortTimer.limitSet { |
| 74 | + newLimit = limit - (limit * a.config.minChangePercentage.Load() / 100) |
| 75 | + a.shortTimer.limitSet = true |
| 76 | + } else if !a.longTimer.getLimitReached(destID) && !a.longTimer.limitSet { |
| 77 | + newLimit = limit + (limit * a.config.maxChangePercentage.Load() / 100) |
| 78 | + a.longTimer.limitSet = true |
| 79 | + } |
| 80 | + newLimit = max(a.config.minLimit.Load(), min(newLimit, a.config.maxLimit.Load())) |
| 81 | + return newLimit |
| 82 | +} |
| 83 | + |
| 84 | +func (a *Adaptive) SetLimitReached(destID string) { |
| 85 | + a.shortTimer.updateLimitReached(destID) |
| 86 | + a.longTimer.updateLimitReached(destID) |
| 87 | +} |
| 88 | + |
| 89 | +func (a *Adaptive) ShutDown() { |
| 90 | + a.shortTimer.cancel() |
| 91 | + a.longTimer.cancel() |
| 92 | +} |
| 93 | + |
| 94 | +func (t *timer) run() { |
| 95 | + ctx, cancel := context.WithCancel(context.Background()) |
| 96 | + t.cancel = cancel |
| 97 | + for { |
| 98 | + select { |
| 99 | + case <-ctx.Done(): |
| 100 | + return |
| 101 | + case <-time.After(t.frequency.Load()): |
| 102 | + t.resetLimitReached() |
| 103 | + } |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +func (t *timer) updateLimitReached(destID string) { |
| 108 | + t.mu.Lock() |
| 109 | + defer t.mu.Unlock() |
| 110 | + t.limitReached[destID] = true |
| 111 | +} |
| 112 | + |
| 113 | +func (t *timer) getLimitReached(destID string) bool { |
| 114 | + t.mu.Lock() |
| 115 | + defer t.mu.Unlock() |
| 116 | + return t.limitReached[destID] |
| 117 | +} |
| 118 | + |
| 119 | +func (t *timer) resetLimitReached() { |
| 120 | + t.mu.Lock() |
| 121 | + defer t.mu.Unlock() |
| 122 | + clear(t.limitReached) |
| 123 | + t.limitSet = false |
| 124 | +} |
0 commit comments