Negative-cache rows are never purged, so the database grows without bound #244

Closed
opened 2026-10-13 08:46:10 +02:00 by clawbot · 1 comment
Collaborator

Where: internal/imgcache/cache.go lines 433-454 (StoreNegative), 486-497 (CleanExpired, which nothing outside tests calls) and 724-758 (checkNegativeCache); internal/imgcache/service.go lines 438-449, on v1.0.0-rc1.

What is wrong: every upstream error (any status outside 200-299, a 404 included) inserts a row in negative_cache, and an expired row is deleted only when the same source URL is requested again after its five minutes. CleanExpired is dead code. Nothing else removes rows, and cache_max_bytes does not cover the database.

How it fails: on a deployment with an allowlisted host, an outsider requests /v1/image/<host>/<a new random path each time>/1x1.jpg; each costs one upstream 404 and leaves one permanent row holding host, path, query and the error text. At the default 600 requests a minute per address, one address adds 864,000 rows a day; the database file grows until the volume is full, after which every write in pixa fails. No operation of pixa ever shrinks it.

Severity: medium

Definition of done:

  1. Expired negative-cache rows are removed on a schedule (the eviction loop's tick is a natural place) and the table is bounded by age or row count, so it cannot grow without limit under an outsider's control.
  2. CleanExpired is called or deleted.
  3. Test first: a row past its expiry disappears without its URL being asked for again; make check green.

Model: fable-5-1

Where: `internal/imgcache/cache.go` lines 433-454 (`StoreNegative`), 486-497 (`CleanExpired`, which nothing outside tests calls) and 724-758 (`checkNegativeCache`); `internal/imgcache/service.go` lines 438-449, on `v1.0.0-rc1`. What is wrong: every upstream error (any status outside 200-299, a 404 included) inserts a row in `negative_cache`, and an expired row is deleted only when the same source URL is requested again after its five minutes. `CleanExpired` is dead code. Nothing else removes rows, and `cache_max_bytes` does not cover the database. How it fails: on a deployment with an allowlisted host, an outsider requests `/v1/image/<host>/<a new random path each time>/1x1.jpg`; each costs one upstream 404 and leaves one permanent row holding host, path, query and the error text. At the default 600 requests a minute per address, one address adds 864,000 rows a day; the database file grows until the volume is full, after which every write in pixa fails. No operation of pixa ever shrinks it. Severity: medium Definition of done: 1. Expired negative-cache rows are removed on a schedule (the eviction loop's tick is a natural place) and the table is bounded by age or row count, so it cannot grow without limit under an outsider's control. 2. `CleanExpired` is called or deleted. 3. Test first: a row past its expiry disappears without its URL being asked for again; `make check` green. Model: fable-5-1
clawbot self-assigned this 2026-10-13 08:46:10 +02:00
Author
Collaborator

Plan: the eviction loop deletes expired negative-cache rows at start and on every tick, through CleanExpired (kept, now called), so rows last at most their five minutes plus one interval. Nothing else changes in how rows are written or read.

Package: internal/imgcache. Test first: a row past its expiry is gone after the loop runs, without its URL being requested again.

#250 changes the same loop and is started after this lands.

Model: opus-5-5

Plan: the eviction loop deletes expired negative-cache rows at start and on every tick, through `CleanExpired` (kept, now called), so rows last at most their five minutes plus one interval. Nothing else changes in how rows are written or read. Package: `internal/imgcache`. Test first: a row past its expiry is gone after the loop runs, without its URL being requested again. https://git.eeqj.de/sneak/pixa/issues/250 changes the same loop and is started after this lands. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#244