Files
descrybe/apps/api/internal/processing/ratelimit.go
T

61 lines
1.4 KiB
Go
Raw Normal View History

package processing
import (
"sync"
"time"
"github.com/google/uuid"
)
// StartLimiter lightly rate-limits processing job starts per company.
// In-process only — not shared across API replicas (effective RPM ≈ N × replicas).
// RATE_LIMIT_REPLICAS does not divide this limiter; multi-replica hard caps need edge/WAF.
// Counts StartJob/RetryJob API calls once each — not per auto-split sibling job,
// and not per product in a bulk StartJob payload (capped by MaxStartProducts).
type StartLimiter struct {
mu sync.Mutex
window time.Duration
max int
events map[uuid.UUID][]time.Time
}
// NewStartLimiter allows maxStarts per window (e.g. 20/min).
func NewStartLimiter(maxStarts int, window time.Duration) *StartLimiter {
if maxStarts <= 0 {
maxStarts = 20
}
if window <= 0 {
window = time.Minute
}
return &StartLimiter{
window: window,
max: maxStarts,
events: make(map[uuid.UUID][]time.Time),
}
}
// Allow reports whether a new job start is permitted for companyID.
func (l *StartLimiter) Allow(companyID uuid.UUID) bool {
if l == nil {
return true
}
now := time.Now()
l.mu.Lock()
defer l.mu.Unlock()
cut := now.Add(-l.window)
ev := l.events[companyID]
kept := ev[:0]
for _, t := range ev {
if t.After(cut) {
kept = append(kept, t)
}
}
if len(kept) >= l.max {
l.events[companyID] = kept
return false
}
kept = append(kept, now)
l.events[companyID] = kept
return true
}