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.
This commit is contained in:
2026-01-08 12:28:17 -08:00
parent 51a1ae4a13
commit 4426387d1c
2 changed files with 33 additions and 11 deletions

View File

@@ -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:

View File

@@ -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()
}