From 4426387d1c32482d445569b60febaac8c5b10137 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 8 Jan 2026 12:28:17 -0800 Subject: [PATCH] Fix hot cache to include ContentType and SizeBytes Hot cache entries now store all data needed to serve a cache hit without any database access: - OutputHash (for file lookup) - ContentType (for Content-Type header) - SizeBytes (for Content-Length header) Previously hot cache only stored OutputHash, causing empty Content-Type headers on cached WebP responses. --- Makefile | 6 ++++-- internal/imgcache/cache.go | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 3413100..c595b1a 100644 --- a/Makefile +++ b/Makefile @@ -28,9 +28,11 @@ test: go test -v ./... # Build the binary -build: +build: ./bin/pixad + +./bin/pixad: ./internal/*/*.go ./cmd/pixad/*.go @echo "Building pixad..." - go build -ldflags "$(LDFLAGS)" -o bin/pixad ./cmd/pixad + go build -ldflags "$(LDFLAGS)" -o $@ ./cmd/pixad # Clean build artifacts clean: diff --git a/internal/imgcache/cache.go b/internal/imgcache/cache.go index 665a517..59f73f3 100644 --- a/internal/imgcache/cache.go +++ b/internal/imgcache/cache.go @@ -30,6 +30,13 @@ type CacheConfig struct { HotCacheEnabled bool } +// hotCacheEntry stores all data needed to serve a cache hit without DB access. +type hotCacheEntry struct { + OutputHash string + ContentType string + SizeBytes int64 +} + // Cache implements the caching layer for the image proxy. type Cache struct { db *sql.DB @@ -37,7 +44,7 @@ type Cache struct { dstContent *ContentStorage srcMetadata *MetadataStorage config CacheConfig - hotCache map[string]string // cache_key -> output_hash + hotCache map[string]hotCacheEntry // cache_key -> entry hotCacheMu sync.RWMutex hotCacheEnabled bool } @@ -69,7 +76,7 @@ func NewCache(db *sql.DB, config CacheConfig) (*Cache, error) { } if config.HotCacheEnabled && config.HotCacheSize > 0 { - c.hotCache = make(map[string]string, config.HotCacheSize) + c.hotCache = make(map[string]hotCacheEntry, config.HotCacheSize) } return c, nil @@ -80,6 +87,7 @@ type LookupResult struct { Hit bool OutputHash string ContentType string + SizeBytes int64 CacheStatus CacheStatus } @@ -90,13 +98,15 @@ func (c *Cache) Lookup(ctx context.Context, req *ImageRequest) (*LookupResult, e // Check hot cache first if c.hotCacheEnabled { c.hotCacheMu.RLock() - outputHash, ok := c.hotCache[cacheKey] + entry, ok := c.hotCache[cacheKey] c.hotCacheMu.RUnlock() - if ok && c.dstContent.Exists(outputHash) { + if ok && c.dstContent.Exists(entry.OutputHash) { return &LookupResult{ Hit: true, - OutputHash: outputHash, + OutputHash: entry.OutputHash, + ContentType: entry.ContentType, + SizeBytes: entry.SizeBytes, CacheStatus: CacheHit, }, nil } @@ -114,14 +124,15 @@ func (c *Cache) Lookup(ctx context.Context, req *ImageRequest) (*LookupResult, e // Check database var outputHash, contentType string + var sizeBytes int64 var fetchedAt time.Time err = c.db.QueryRowContext(ctx, ` - SELECT rc.output_hash, oc.content_type, rc.fetched_at + SELECT rc.output_hash, oc.content_type, oc.size_bytes, rc.fetched_at FROM request_cache rc JOIN output_content oc ON rc.output_hash = oc.content_hash WHERE rc.cache_key = ? - `, cacheKey).Scan(&outputHash, &contentType, &fetchedAt) + `, cacheKey).Scan(&outputHash, &contentType, &sizeBytes, &fetchedAt) if errors.Is(err, sql.ErrNoRows) { return &LookupResult{Hit: false, CacheStatus: CacheMiss}, nil @@ -144,7 +155,11 @@ func (c *Cache) Lookup(ctx context.Context, req *ImageRequest) (*LookupResult, e // Update hot cache if c.hotCacheEnabled { c.hotCacheMu.Lock() - c.hotCache[cacheKey] = outputHash + c.hotCache[cacheKey] = hotCacheEntry{ + OutputHash: outputHash, + ContentType: contentType, + SizeBytes: sizeBytes, + } c.hotCacheMu.Unlock() } @@ -159,6 +174,7 @@ func (c *Cache) Lookup(ctx context.Context, req *ImageRequest) (*LookupResult, e Hit: true, OutputHash: outputHash, ContentType: contentType, + SizeBytes: sizeBytes, CacheStatus: CacheHit, }, nil } @@ -277,7 +293,11 @@ func (c *Cache) StoreOutput( // Update hot cache if c.hotCacheEnabled { c.hotCacheMu.Lock() - c.hotCache[cacheKey] = outputHash + c.hotCache[cacheKey] = hotCacheEntry{ + OutputHash: outputHash, + ContentType: contentType, + SizeBytes: size, + } c.hotCacheMu.Unlock() }