Remove expired negative cache rows in the eviction loop (closes #244)
check / check (push) Waiting to run
check / check (push) Waiting to run
A negative cache row was deleted only when its URL was requested again, so a client asking for a new missing path each time grew the database without limit. The eviction goroutine now removes expired rows at startup and on every tick, so a row stays about NegativeTTL plus one eviction interval. It deletes at most 1000 rows per statement, so a request on the one database connection waits behind one batch at most. The goroutine now also runs when the disk cache is disabled, since the negative cache is used either way. Model: opus-5-5
This commit was merged in pull request #262.
This commit is contained in:
+52
-11
@@ -32,6 +32,12 @@ const httpStatusOK = 200
|
||||
// variant not among them is served as before, reading its .meta file.
|
||||
const metaCacheSize = 10000
|
||||
|
||||
// cleanExpiredBatchSize is the most expired negative cache rows one
|
||||
// statement of CleanExpired deletes. Each batch is a statement of its
|
||||
// own, so a request waits behind one batch at most, however many rows
|
||||
// have expired.
|
||||
const cleanExpiredBatchSize = 1000
|
||||
|
||||
// CacheConfig holds cache configuration.
|
||||
type CacheConfig struct {
|
||||
StateDir string
|
||||
@@ -50,8 +56,9 @@ type CacheConfig struct {
|
||||
|
||||
// DisableDiskCache turns the disk cache off entirely: no cache
|
||||
// directories are created, lookups always miss, stores are
|
||||
// no-ops, and no eviction machinery runs. The config layer sets
|
||||
// this when the operator configures cache_max_bytes: 0.
|
||||
// no-ops, and the eviction goroutine only removes the expired
|
||||
// negative cache rows. The config layer sets this when the
|
||||
// operator configures cache_max_bytes: 0.
|
||||
DisableDiskCache bool
|
||||
|
||||
// Logger receives accounting and eviction log output. A nil
|
||||
@@ -482,17 +489,27 @@ func (c *Cache) GetSourceContent(
|
||||
return c.srcContent.LoadWithSize(contentHash)
|
||||
}
|
||||
|
||||
// CleanExpired removes expired entries from the cache.
|
||||
// CleanExpired removes the negative cache rows past their expiry, one
|
||||
// batch per statement, until none is left. Once ctx ends it starts no
|
||||
// further batch and returns ctx's error. The eviction goroutine calls it
|
||||
// at startup and on every periodic tick, so a row stays about
|
||||
// NegativeTTL plus one eviction interval, whether or not its URL is
|
||||
// requested again.
|
||||
func (c *Cache) CleanExpired(ctx context.Context) error {
|
||||
// Clean expired negative cache entries
|
||||
_, err := c.db.ExecContext(ctx, `
|
||||
DELETE FROM negative_cache WHERE expires_at < CURRENT_TIMESTAMP
|
||||
`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clean negative cache: %w", err)
|
||||
}
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
deleted, err := c.cleanExpiredBatch(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if deleted < cleanExpiredBatchSize {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stats returns cache statistics.
|
||||
@@ -756,3 +773,27 @@ func (c *Cache) checkNegativeCache(
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// cleanExpiredBatch deletes up to cleanExpiredBatchSize negative cache
|
||||
// rows past their expiry in one statement, and returns how many it
|
||||
// deleted. DELETE ... LIMIT needs a SQLite build option, so the subquery
|
||||
// picks the rows.
|
||||
func (c *Cache) cleanExpiredBatch(ctx context.Context) (int64, error) {
|
||||
result, err := c.db.ExecContext(ctx, `
|
||||
DELETE FROM negative_cache WHERE id IN (
|
||||
SELECT id FROM negative_cache
|
||||
WHERE expires_at < CURRENT_TIMESTAMP
|
||||
LIMIT ?
|
||||
)
|
||||
`, cleanExpiredBatchSize)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to clean negative cache: %w", err)
|
||||
}
|
||||
|
||||
deleted, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to count cleaned negative cache rows: %w", err)
|
||||
}
|
||||
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user