refactor(config): make the artwork tuning options dev flags

ArtworkWorkerConcurrency and ArtworkExternalMaxRPS become
DevArtworkWorkerConcurrency and DevArtworkExternalMaxRPS, joining the
other DevArtwork* flags. Their defaults should not need tuning, so they
do not belong in the documented, user-facing option set.
This commit is contained in:
Deluan 2026-07-29 21:29:04 -04:00
parent fbcda30473
commit 34f6684922
7 changed files with 11 additions and 11 deletions

View File

@ -62,8 +62,6 @@ type configOptions struct {
ImageCacheSize string
AlbumPlayCountMode string
EnableArtworkPrecache bool
ArtworkWorkerConcurrency int
ArtworkExternalMaxRPS int
AutoImportPlaylists bool
DefaultPlaylistPublicVisibility bool
PlaylistsPath string
@ -146,6 +144,8 @@ type configOptions struct {
DevArtworkThrottleBacklogLimit int
DevArtworkThrottleBacklogTimeout time.Duration
DevArtworkThrottleBuffered bool
DevArtworkWorkerConcurrency int
DevArtworkExternalMaxRPS int
DevArtistInfoTimeToLive time.Duration
DevAlbumInfoTimeToLive time.Duration
DevExternalScanner bool
@ -1075,10 +1075,10 @@ func setViperDefaults() {
viper.SetDefault("devartworkthrottlebuffered", true)
// Half the CPU count (min 2), so local resolution scales with the host but stays under the
// SQLite pool (MaxOpenConns) — leaving connections for the scanner, scrobbles and the UI.
viper.SetDefault("artworkworkerconcurrency", max(2, runtime.NumCPU()/2))
viper.SetDefault("devartworkworkerconcurrency", max(2, runtime.NumCPU()/2))
// External RPS gates outbound calls to third-party services (per service); it is bounded by
// their tolerance, not the host, so it stays a small constant regardless of CPU count.
viper.SetDefault("artworkexternalmaxrps", 2)
viper.SetDefault("devartworkexternalmaxrps", 2)
viper.SetDefault("devartistinfotimetolive", consts.ArtistInfoTimeToLive)
viper.SetDefault("devalbuminfotimetolive", consts.AlbumInfoTimeToLive)
viper.SetDefault("devexternalscanner", true)

View File

@ -65,7 +65,7 @@ var _ = Describe("Acquisition → serve loop", func() {
conf.Server.CoverArtPriority = "cover.jpg"
conf.Server.ArtistArtPriority = "artist.png" // keeps artist resolution offline
conf.Server.EnableMediaFileCoverArt = true
conf.Server.ArtworkWorkerConcurrency = 1
conf.Server.DevArtworkWorkerConcurrency = 1
folderRepo = &fakeFolderRepo{}
libRepo = &tests.MockLibraryRepo{}

View File

@ -90,7 +90,7 @@ func setupResolutionHarness() {
conf.Server.ImageCacheSize = "0"
conf.Server.EnableExternalServices = false
conf.Server.EnableMediaFileCoverArt = true
conf.Server.ArtworkWorkerConcurrency = 1
conf.Server.DevArtworkWorkerConcurrency = 1
rctx = request.WithUser(GinkgoT().Context(), model.User{ID: "admin-1", UserName: "admin", IsAdmin: true})
harness.TruncateDB(userTables)

View File

@ -67,7 +67,7 @@ func (w *Worker) gateFor(name string) *extGate {
if g, ok := w.gates[name]; ok {
return g
}
rps := conf.Server.ArtworkExternalMaxRPS
rps := conf.Server.DevArtworkExternalMaxRPS
limit := rate.Inf
if rps > 0 {
limit = rate.Limit(rps)

View File

@ -71,9 +71,9 @@ func NewWorker(ds model.DataStore, store *ImageStore, ag *agents.Agents, ffmpeg
// rate-limit permit, so a sleeping lookup would crowd out a cover sitting on disk.
func newDrainPools() []*drainPool {
budget := conf.MaxOpenConns() // floored at 4, so both remainders below stay positive
local := min(max(1, conf.Server.ArtworkWorkerConcurrency), budget-1)
local := min(max(1, conf.Server.DevArtworkWorkerConcurrency), budget-1)
// More external slots than the rate allows would only sleep in the limiter.
external := min(max(2, 2*conf.Server.ArtworkExternalMaxRPS), budget-local)
external := min(max(2, 2*conf.Server.DevArtworkExternalMaxRPS), budget-local)
return []*drainPool{
{name: "local", kinds: localDrainKinds, concurrency: local, wake: make(chan struct{}, 1)},
{name: "external", kinds: externalDrainKinds, concurrency: external, wake: make(chan struct{}, 1)},

View File

@ -145,7 +145,7 @@ var _ = Describe("Worker", func() {
ds.MockedAlbum = tests.CreateMockAlbumRepo()
store = NewImageStore(GinkgoT().TempDir())
conf.Server.CoverArtPriority = "cover.jpg, embedded"
conf.Server.ArtworkExternalMaxRPS = 1000 // keep the limiter out of the way of behavior tests
conf.Server.DevArtworkExternalMaxRPS = 1000 // keep the limiter out of the way of behavior tests
broker = &fakeEventBroker{}
imgCache = &recordingCache{FileCache: cache.NewFileCache("WorkerTest", "100MB", "images", 0,
func(ctx context.Context, arg cache.Item) (io.Reader, error) {

View File

@ -93,7 +93,7 @@ var _ = Describe("Artwork Serving", Ordered, func() {
conf.Server.CoverArtPriority = "cover.jpg"
conf.Server.ArtistArtPriority = "artist.png" // offline: artists resolve absent, out of scope here
conf.Server.EnableMediaFileCoverArt = false
conf.Server.ArtworkWorkerConcurrency = 1
conf.Server.DevArtworkWorkerConcurrency = 1
conf.Server.CacheFolder = conf.NewDir(GinkgoT().TempDir())
conf.Server.EnableSharing = true
conf.Server.DevArtworkMaxRequests = 100