perf: burst-friendly MB rate limiter for interactive search
The shared 1 req/sec MB rate limiter was serializing the 3 concurrent search calls in Phase 1 to ~3s minimum. Interactive search needs short bursts (3 calls at once) but not sustained throughput. Split into two MB rate limiters: - mbSearchLimiter: burst=3, refill=1/sec — allows one search's 3 concurrent calls to fire immediately, then rate-limits sustained use - mbBackgroundLimiter: strict 1/sec — gates artist image resolution in the indexer to avoid 429s during sustained background work Added NewRateLimiterBurst(n, b) constructor for configurable burst. Expected Phase 1 improvement: ~3.5s → ~1s (3 calls fire in parallel instead of serializing through the limiter).
This commit is contained in:
@@ -36,12 +36,16 @@ type Service struct {
|
|||||||
func NewExploreService(logger *slog.Logger, db *database.DB) *Service {
|
func NewExploreService(logger *slog.Logger, db *database.DB) *Service {
|
||||||
cache := NewCache(db, logger.WithGroup("cache"))
|
cache := NewCache(db, logger.WithGroup("cache"))
|
||||||
lbLimiter := NewRateLimiter()
|
lbLimiter := NewRateLimiter()
|
||||||
mbLimiter := NewRateLimiter() // 1 req/sec, shared across all MB consumers
|
// MB search limiter: burst of 3 (covers one search's 3 concurrent calls)
|
||||||
mb := NewMusicBrainzClient(cache, mbLimiter, logger.WithGroup("musicbrainz"))
|
// then 1/sec refill. The musicbrainzws2 library retries on 429 as backup.
|
||||||
|
mbSearchLimiter := NewRateLimiterBurst(1, 3)
|
||||||
|
// MB background limiter: strict 1/sec for sustained image resolution calls.
|
||||||
|
mbBackgroundLimiter := NewRateLimiter()
|
||||||
|
mb := NewMusicBrainzClient(cache, mbSearchLimiter, logger.WithGroup("musicbrainz"))
|
||||||
lb := NewListenBrainzClient(lbLimiter, cache, logger.WithGroup("listenbrainz"))
|
lb := NewListenBrainzClient(lbLimiter, cache, logger.WithGroup("listenbrainz"))
|
||||||
artProxy := NewCoverArtProxy(db, lbLimiter)
|
artProxy := NewCoverArtProxy(db, lbLimiter)
|
||||||
artistImg := NewArtistImageProvider(
|
artistImg := NewArtistImageProvider(
|
||||||
db, cache, mbLimiter, logger.WithGroup("artist-image"),
|
db, cache, mbBackgroundLimiter, logger.WithGroup("artist-image"),
|
||||||
)
|
)
|
||||||
index := NewSearchIndex(db, lb, artistImg, logger.WithGroup("search-index"))
|
index := NewSearchIndex(db, lb, artistImg, logger.WithGroup("search-index"))
|
||||||
libMBID := NewLibraryMBIDIndex(db)
|
libMBID := NewLibraryMBIDIndex(db)
|
||||||
|
|||||||
@@ -46,6 +46,16 @@ func NewRateLimiterF(f float64) *RateLimiter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRateLimiterBurst returns a rate limiter that allows n requests
|
||||||
|
// per second with a burst size of b. The burst allows short spikes
|
||||||
|
// (e.g. 3 concurrent search calls) without queueing, while still
|
||||||
|
// limiting sustained throughput.
|
||||||
|
func NewRateLimiterBurst(n, b int) *RateLimiter {
|
||||||
|
return &RateLimiter{
|
||||||
|
limiter: rate.NewLimiter(rate.Limit(n), b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wait blocks until the rate limiter allows the caller to proceed
|
// Wait blocks until the rate limiter allows the caller to proceed
|
||||||
// or the context is cancelled. Returns ctx.Err() if the context
|
// or the context is cancelled. Returns ctx.Err() if the context
|
||||||
// expires before a token becomes available.
|
// expires before a token becomes available.
|
||||||
|
|||||||
Reference in New Issue
Block a user