Check cached sources with upstream after the cache TTL (closes #69)
check / check (push) Canceled after 0s

A cached image was served until eviction, so a change at the same URL
was never picked up.

After 24 hours a request checks the source with upstream, sending its
stored ETag and Last-Modified. A 304 renews it with no download or
conversion. New bytes replace it, and older variants are converted
again. A failed check serves the cached image and puts the URL in the
negative cache. Images not checked for 30 days are evicted.

Variant rows keep the source's ETag and Last-Modified, so a variant
whose source was evicted is checked the same way.

A variant file's modification time is its source's check time when it
was made or renewed, so a hit reads no database row.

Model: opus-5-5
This commit is contained in:
clawbot committed 2026-11-11 10:41:33 +00:00
1 parent b3b8408d18
commit 3f487ee0ce
28 files changed
+1556 -233

No files matched your search

+60 -8
View File
@@ -6,8 +6,9 @@ results.
A web page asks pixa for an image at a size and in a format. pixa fetches the
original image, makes the requested image from it, and serves that. It keeps
both on disk. Later requests for the same image are served from disk, without a
fetch or a conversion.
both on disk. Later requests for the same image are served from disk. After 24
hours pixa first asks the server that holds the original whether it changed (see
Cache TTL).
In this document, the upstream host is the server that holds an original image.
@@ -244,9 +245,12 @@ These terms are the names the code uses:
after a fetch failed in one of these ways:
- the upstream answered with an error status;
- the body's first bytes were not those of the format its `Content-Type`
names.
names;
- a check of a cached image failed in any way (see Cache TTL).
- **Image cache**: the part of pixa that keeps the sources, the variants and the
negative cache (see Storage).
- **Cache TTL**: how long pixa serves a cached source and its variants before it
checks the source with upstream again: 24 hours (see Cache TTL).
- **Disk cache limit** (`cache_max_bytes`): the size in bytes that the cached
sources and variants may reach together.
- **Image proxy**: the part of pixa that serves a variant from the image cache
@@ -517,8 +521,8 @@ seconds left until then, at most one year. So no browser or proxy cache keeps
the image after pixa would refuse the URL. A URL with no expiry gets one year.
`immutable` only stops a client revalidating while its copy is fresh. The
response also carries `X-Pixa-Cache`: `HIT` for an image served from the cache,
and `MISS` for one made for this request. The `ETag` is the first 16 characters
of the variant key, in quotes.
and `MISS` for one made for this request or older than 24 hours (see Cache TTL).
The `ETag` is the first 16 characters of the variant key, in quotes.
### Signed URLs
@@ -824,10 +828,17 @@ and `state.sqlite3-shm` beside the file while pixa runs.
The image cache keeps these tables in the database:
- `source_content`: a row for each source, by source key, with its size and when
it was last used
- `source_content`: a row for each source, by source key, with:
- its size and when it was last used
- when it was fetched, and when upstream last sent it or confirmed it
unchanged
- the `ETag` and `Last-Modified` headers upstream sent with it
- `variant_content`: a row for each variant, by variant key (column
`cache_key`), with its size and when it was last used
`cache_key`), with:
- its size and when it was last used
- its source's check time when the variant was made or last renewed
- the `ETag` and `Last-Modified` headers upstream sent with the source it
was made from
- `cache_usage`: the total size of the cached sources and variants, which
eviction reads
- `negative_cache`: a row for each source URL in the negative cache, with when
@@ -881,6 +892,47 @@ within `cache_max_bytes`. It runs after each store, and every five minutes. It
also removes the expired rows of the negative cache every five minutes, at most
1000 per statement, so a request waits behind 1000 deletions at most.
### Cache TTL
pixa serves a cached source and its variants for 24 hours after upstream last
sent the source or confirmed it unchanged. After that, the next request for an
image of that source checks the source with upstream first:
- pixa asks upstream to send the source only if it changed. It sends the
source's `ETag` as `If-None-Match` and its `Last-Modified` as
`If-Modified-Since`, when upstream gave them. With neither, upstream sends the
whole image again.
- When upstream answers 304 Not Modified, pixa serves the cached source and its
variants for another 24 hours. Nothing is downloaded or converted again.
- When upstream sends the image, it replaces the cached source. Each variant
made from the old image is converted again from the new one when it is next
requested.
- When the check fails, pixa serves the cached image. A failure can be an error
status, a timeout or no connection. The failure puts the source URL in the
negative cache, so pixa does not ask upstream again for 5 minutes, however
often it serves the image.
pixa evicts a source and its variants 30 days after upstream last sent the
source or confirmed it unchanged. So a source whose checks keep failing is
served for at most 30 days. Eviction to stay under the disk cache limit can
remove images sooner.
That eviction can remove a source and keep its variants. Each variant's row
keeps the `ETag` and `Last-Modified` of the source it was made from. So pixa
checks such a variant with upstream in the same way after 24 hours:
- When upstream answers 304 Not Modified, pixa serves the cached variant for
another 24 hours. Nothing is downloaded or converted again.
- When upstream sends the image, pixa caches it as the source and converts the
variant again.
- When the check fails, pixa serves the cached variant and puts the source URL
in the negative cache.
A variant file's modification time is its source's check time when the variant
was made or last renewed. So a cache hit reads no database row to know whether
the variant is older than 24 hours. A request that checks its source with
upstream counts as a miss.
### Configuration
Every setting can be given as an environment variable, in a config file