Check cached file sizes at startup, not per request (closes #252)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
Lookup and LookupSource are as on next again: requests compare no sizes and a hit reads no extra row. The reconciliation pass, at startup and every five minutes, compares each cached source and variant file with the size its row records and removes one that is empty or of another size, with its rows and a warning naming the file; an empty variant file without a row is removed rather than adopted. Source, variant and .meta files all go through writeFileSynced (temp file, sync, rename, directory sync), the .meta file before the variant, and a failed directory sync removes the renamed file. #252 Model: opus-5-5
This commit is contained in:
+15
-77
@@ -229,13 +229,13 @@ type LookupResult struct {
|
||||
CacheStatus CacheStatus
|
||||
}
|
||||
|
||||
// Lookup checks if a processed variant is on disk whole (see
|
||||
// variantFileIsWhole). Hits touch the variant's LRU timestamp; a disabled
|
||||
// cache always misses.
|
||||
// Lookup checks if a processed variant exists on disk. Hits touch the
|
||||
// variant's LRU timestamp; a disabled cache always misses.
|
||||
func (c *Cache) Lookup(ctx context.Context, req *ImageRequest) (*LookupResult, error) {
|
||||
cacheKey := CacheKey(req)
|
||||
|
||||
if !c.disabled && c.variantFileIsWhole(ctx, cacheKey) {
|
||||
// Check variant storage directly - no DB needed for cache hits
|
||||
if !c.disabled && c.variants.Exists(cacheKey) {
|
||||
c.touchVariant(ctx, cacheKey)
|
||||
|
||||
return &LookupResult{
|
||||
@@ -400,11 +400,8 @@ func (c *Cache) StoreVariant(
|
||||
|
||||
// LookupSource checks if we have cached source content for a request.
|
||||
// Returns the content hash and content type if found, or empty values
|
||||
// if not. A content file that is empty, or not the size its
|
||||
// source_content row records, is damaged: it is removed with its rows,
|
||||
// and no cached source is reported, so the source is fetched again. Hits
|
||||
// touch the blob's LRU timestamp; a disabled cache always reports no
|
||||
// cached source.
|
||||
// if not. Hits touch the blob's LRU timestamp; a disabled cache always
|
||||
// reports no cached source.
|
||||
func (c *Cache) LookupSource(
|
||||
ctx context.Context, req *ImageRequest,
|
||||
) (ContentHash, string, error) {
|
||||
@@ -412,18 +409,12 @@ func (c *Cache) LookupSource(
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
var (
|
||||
hashStr, contentType string
|
||||
recordedSize int64
|
||||
)
|
||||
var hashStr, contentType string
|
||||
|
||||
err := c.db.QueryRowContext(ctx, `
|
||||
SELECT source_metadata.content_hash, source_metadata.content_type,
|
||||
source_content.size_bytes
|
||||
FROM source_metadata JOIN source_content USING (content_hash)
|
||||
SELECT content_hash, content_type FROM source_metadata
|
||||
WHERE source_host = ? AND source_path = ? AND source_query = ?
|
||||
`, req.SourceHost, req.SourcePath, req.SourceQuery).
|
||||
Scan(&hashStr, &contentType, &recordedSize)
|
||||
`, req.SourceHost, req.SourcePath, req.SourceQuery).Scan(&hashStr, &contentType)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", "", nil
|
||||
@@ -435,24 +426,8 @@ func (c *Cache) LookupSource(
|
||||
|
||||
contentHash := ContentHash(hashStr)
|
||||
|
||||
fileSize, err := c.srcContent.Size(contentHash)
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if fileSize == 0 || fileSize != recordedSize {
|
||||
c.log.Warn("removing damaged cached source", "content_hash", contentHash,
|
||||
"file_bytes", fileSize, "recorded_bytes", recordedSize)
|
||||
|
||||
err = c.evictSourceBlob(ctx, contentHash)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to remove damaged source: %w", err)
|
||||
}
|
||||
|
||||
// Verify the content file exists
|
||||
if !c.srcContent.Exists(contentHash) {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
@@ -685,10 +660,10 @@ func (c *Cache) readHitAndMissCounts(ctx context.Context, stats *CacheStats) err
|
||||
// hold: it reads the content type from the variant's .meta file and
|
||||
// keeps it in metaCache, unless a StoreVariant has put one there
|
||||
// meanwhile, as the store's is newer. A read that finds no .meta file,
|
||||
// as one can between a store's writing of the variant file and of its
|
||||
// .meta file, serves application/octet-stream and keeps nothing, so
|
||||
// metaCache only ever holds a type read from a .meta file or passed to
|
||||
// StoreVariant.
|
||||
// as one can when the store's .meta write failed or an eviction removed
|
||||
// the file after the read opened the variant, serves
|
||||
// application/octet-stream and keeps nothing, so metaCache only ever
|
||||
// holds a type read from a .meta file or passed to StoreVariant.
|
||||
func (c *Cache) loadVariantWithMeta(
|
||||
cacheKey VariantKey,
|
||||
) (io.ReadCloser, int64, string, error) {
|
||||
@@ -736,43 +711,6 @@ func (c *Cache) writeMetadataSidecar(
|
||||
}
|
||||
}
|
||||
|
||||
// variantFileIsWhole reports whether the variant file of cacheKey is on
|
||||
// disk with the size its variant_content row records. A file that is
|
||||
// empty or of another size is damaged: it is removed with its row, so the
|
||||
// variant is converted again. A variant whose row cannot be read, as
|
||||
// between StoreVariant's writing of its file and of its row, is judged
|
||||
// by its file alone.
|
||||
func (c *Cache) variantFileIsWhole(ctx context.Context, cacheKey VariantKey) bool {
|
||||
fileSize, err := c.variants.Size(cacheKey)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var recordedSize int64
|
||||
|
||||
err = c.db.QueryRowContext(ctx,
|
||||
`SELECT size_bytes FROM variant_content WHERE cache_key = ?`,
|
||||
string(cacheKey)).Scan(&recordedSize)
|
||||
if err != nil {
|
||||
recordedSize = fileSize
|
||||
}
|
||||
|
||||
if fileSize > 0 && fileSize == recordedSize {
|
||||
return true
|
||||
}
|
||||
|
||||
c.log.Warn("removing damaged cached variant", "cache_key", cacheKey,
|
||||
"file_bytes", fileSize, "recorded_bytes", recordedSize)
|
||||
|
||||
err = c.evictVariant(ctx, cacheKey)
|
||||
if err != nil {
|
||||
c.log.Warn("failed to remove damaged cached variant",
|
||||
"cache_key", cacheKey, "error", err)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// touchVariant updates the LRU timestamp of a variant, best-effort:
|
||||
// a failed touch only makes the entry look colder to eviction.
|
||||
func (c *Cache) touchVariant(ctx context.Context, cacheKey VariantKey) {
|
||||
|
||||
Reference in New Issue
Block a user