diff --git a/backend/explore/explore.go b/backend/explore/explore.go index 075d21a..f2a332f 100644 --- a/backend/explore/explore.go +++ b/backend/explore/explore.go @@ -36,12 +36,16 @@ type Service struct { func NewExploreService(logger *slog.Logger, db *database.DB) *Service { cache := NewCache(db, logger.WithGroup("cache")) lbLimiter := NewRateLimiter() - mbLimiter := NewRateLimiter() // 1 req/sec, shared across all MB consumers - mb := NewMusicBrainzClient(cache, mbLimiter, logger.WithGroup("musicbrainz")) + // MB search limiter: burst of 3 (covers one search's 3 concurrent calls) + // 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")) artProxy := NewCoverArtProxy(db, lbLimiter) 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")) libMBID := NewLibraryMBIDIndex(db) diff --git a/backend/explore/ratelimiter.go b/backend/explore/ratelimiter.go index 854f116..d9a06ad 100644 --- a/backend/explore/ratelimiter.go +++ b/backend/explore/ratelimiter.go @@ -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 // or the context is cancelled. Returns ctx.Err() if the context // expires before a token becomes available.