fix(artwork): version the resized cache key to backfill blurhashes on upgrade

Resized entries cached by a pre-blurhash version serve straight from the cache:
the resized reader never runs, the original is never read, and the tee never gets
a chance to compute a hash — clients that only request sized images (Jellyfin
maxwidth) would keep receiving the fake blurhash indefinitely for those items.

Versioning the resized cache key makes every post-upgrade sized request miss and
refill. The refill pulls the original through Get, which usually hits the cached
original (original keys are unchanged), so the backfill costs one decode+re-encode
per resized variant with no source or external-provider I/O. Orphaned entries are
evicted by the cache's LRU as usual.
This commit is contained in:
Deluan 2026-07-17 22:30:38 -04:00
parent 07af29fbb4
commit 9a36047096
2 changed files with 21 additions and 1 deletions

View File

@ -66,8 +66,12 @@ func resizedFromOriginal(ctx context.Context, a *artwork, artID model.ArtworkID,
return r, nil
}
// resizedKeyVersion invalidates resized entries cached by pre-blurhash versions: the refill is what
// pulls the original through the tee, so warm entries would otherwise never backfill a hash.
const resizedKeyVersion = "v1"
func (a *resizedArtworkReader) Key() string {
baseKey := fmt.Sprintf("%s.%d", a.cacheKey, a.size)
baseKey := fmt.Sprintf("%s.%d.%s", a.cacheKey, a.size, resizedKeyVersion)
if a.square {
return baseKey + ".square"
}

View File

@ -6,12 +6,28 @@ import (
"errors"
"io"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/conf/configtest"
"github.com/navidrome/navidrome/core/ffmpeg"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("resizedArtworkReader.Key", func() {
BeforeEach(func() {
DeferCleanup(configtest.SetupConfig())
conf.Server.CoverArtQuality = 75
})
It("includes the cache version so pre-blurhash resized entries are invalidated", func() {
r := &resizedArtworkReader{cacheKey: "al-1.123", size: 300}
Expect(r.Key()).To(Equal("al-1.123.300.v1.75"))
r.square = true
Expect(r.Key()).To(Equal("al-1.123.300.v1.square"))
})
})
var _ = Describe("resizeImage", func() {
var mockFF *tests.MockFFmpeg
var r *resizedArtworkReader