mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-05 09:48:37 +01:00
perf(artwork): cap the stale-absent recheck at 100 items per kind per hour (#6007)
* feat(artwork): drip the stale-absent recheck instead of bursting it daily Each hourly housekeeping tick now re-queues at most 100 absent states per kind, oldest attempts first, instead of everything older than 24h at once. External agents see a flat ~100 requests/hour per agent instead of hourly bursts of ~2,000, and the effective recheck interval self-scales with the size of the absent pool (~4 days at 10k absent artists) while small libraries keep the 24h floor. * feat(artwork): trust an absent artwork state for a week before rechecking With the recheck now dripped at 100 items per kind per hour, the 24h floor only governed small libraries, where the drip cap never binds; they still re-asked every agent daily. A 7-day floor cuts that cost 7x and, for large libraries, becomes the binding limit over the drip cycle (~5.7k calls/day instead of ~9.6k at 10k absent artists). Among comparable servers, this is still the second-most-eager recheck: gonic retries misses every 30 days, Jellyfin and Funkwhale never do. * refactor(artwork): state the drip's backpressure contract where it bites Review follow-ups: the recheck limit deliberately caps the *selection*, not the insertions — already-queued rows use up budget, so a stalled drain admits no new work instead of building a recovery burst. Say so in the interface doc, mirror it in the mock by truncating the sorted candidates (matching the SQL's LIMIT-before-ON CONFLICT), and teach `artwork status` and the worker doc the post-drip wording. Also pin the one cmd fixture that still assumed a 24h recheck window.
This commit is contained in:
parent
ffc68e29db
commit
07b6411c0b
9 changed files with 70 additions and 23 deletions
|
|
@ -238,7 +238,8 @@ func formatStatus(rep statusReport) string {
|
|||
for _, a := range rep.absent {
|
||||
fmt.Fprintf(w, " %s\t%d\t%d\n", a.kind, a.Total, a.Stale)
|
||||
}
|
||||
fmt.Fprintf(w, " (rechecked once the last attempt is older than %gh)\n", artwork.StaleAbsentAge.Hours())
|
||||
fmt.Fprintf(w, " (eligible once the last attempt is older than %gh; re-queued %d per kind per hour, oldest first)\n",
|
||||
artwork.StaleAbsentAge.Hours(), artwork.StaleAbsentRecheckBatch)
|
||||
|
||||
fmt.Fprintln(w, "\nBackfill")
|
||||
fmt.Fprintf(w, " State:\t%s\n", backfillState(rep))
|
||||
|
|
|
|||
|
|
@ -818,7 +818,7 @@ var _ = Describe("collectStatus", func() {
|
|||
ImageType: model.ImageTypePrimary, Source: source, Hash: hash, AttemptedAt: attempted})).To(Succeed())
|
||||
}
|
||||
put(model.KindArtistArtwork, "ar-1", "external:deezer", "h1", time.Now())
|
||||
put(model.KindArtistArtwork, "ar-2", "", "", time.Now().Add(-48*time.Hour))
|
||||
put(model.KindArtistArtwork, "ar-2", "", "", time.Now().Add(-artwork.StaleAbsentAge-time.Hour))
|
||||
put(model.KindArtistArtwork, "ar-3", "", "", time.Now())
|
||||
put(model.KindAlbumArtwork, "al-1", "folder", "h2", time.Now())
|
||||
Expect(queue.Enqueue(model.ArtworkQueueItem{ItemKind: "ar", ItemID: "ar-9",
|
||||
|
|
@ -909,8 +909,9 @@ var _ = Describe("formatStatus", func() {
|
|||
Expect(absent).To(MatchRegexp(`artist\s+2\s+1`))
|
||||
})
|
||||
|
||||
It("states the recheck window the absent counts are bucketed against", func() {
|
||||
Expect(formatStatus(rep)).To(ContainSubstring("24h"))
|
||||
It("states the recheck window and the drip rate the absent counts are bucketed against", func() {
|
||||
Expect(formatStatus(rep)).To(ContainSubstring("168h"))
|
||||
Expect(formatStatus(rep)).To(ContainSubstring("100 per kind per hour"))
|
||||
})
|
||||
|
||||
It("leads with the queued backlog, which is the finding, not with the fingerprint verdict", func() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue