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

Cached images were served until eviction, so upstream changes were
missed.

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; 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 for checking a
variant of an evicted source.

A variant file's modification time is its source's check time when it
was made or renewed. Hits read it without a database call. The ETag
includes it, so new bytes get a new ETag.

Model: opus-5-5
This commit is contained in:
clawbot committed 2026-11-11 11:40:36 +00:00
1 parent b3b8408d18
commit 445ff628fd
29 files changed
+1709 -269

No files matched your search

+65 -10
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.
@@ -232,8 +233,8 @@ These terms are the names the code uses:
- **Variant**: the image pixa serves for an image request, made from its source
with its transform.
- **Variant key**: the SHA-256 hash of a variant's source, size, format, quality
and fit. It names the variant's file and its row in the database, and gives
its `ETag`.
and fit. It names the variant's file and its row in the database. The
variant's `ETag` is made from it and the file's modification time.
- **Signed URL**: a `/v1/image/` URL whose `sig` parameter signs its image
request and its expiry `exp` with `signing_key`.
- **Encrypted URL**: a `/v1/e/` URL whose token holds its image request and any
@@ -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 uses a cached source and serves 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,10 @@ 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 made from the first 16 characters of the variant key and the
variant file's modification time (see Cache TTL). So a variant made again from a
changed source gets a new `ETag`.
### Signed URLs
@@ -824,10 +830,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 +894,48 @@ 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 uses a cached source for 24 hours after upstream last sent it or confirmed
it unchanged. During that time it serves the source's variants from the cache.
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 uses the cached source and serves
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 pixa uses a source whose checks keep
failing 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