fix(thumbnails): drop derived-hash ETag until the read-order flip
The ETag is computed BEFORE the body. On a cache miss no content_derived_blobs row exists, so thumbnail_content_id returned the source-keyed form — then rendering created that row, and the next request resolved to the derived hash instead. The validator changed as a side effect of producing the body, making every first render immediately stale. Latent until aaf08532: before the consolidation, the on-demand render path never wrote a derived row, so the flip had nothing to trigger it. Fixing one gap exposed the other. Caught by thumbnail_etag_content_keyed.hurl — two consecutive GETs of an unchanged file stopped revalidating to 304. The plan already said derived-hash keying must land WITH the read-order flip and not before; I brought it forward anyway when the attachment case forced the attached half. This is the evidence for the constraint, so the plan now records the attempt and why it failed rather than leaving the note as untested caution. The attached lookup stays — it has no such window, since an upload writes its row synchronously before any read can observe it, and it fixes a real collision: a copy inherits the source hash, so an original and a copy carrying different uploaded previews would otherwise share one validator while serving different bytes. The flip removes the hazard for the derived half too: once that tier is authoritative it is populated before it is consulted, so no row can appear between two reads.
This commit is contained in:
@@ -991,6 +991,22 @@ yet generated and for formats the derived tier does not hold. The
|
||||
`LEFT JOIN` in step 2 above already returns the derived hash in the
|
||||
same query, so the ETag costs no extra round-trip.
|
||||
|
||||
**Attempted early (2026-08-26) and reverted — the constraint above is
|
||||
load-bearing.** The attached half shipped and is correct, because an
|
||||
upload writes its row synchronously before any read can observe it. The
|
||||
*derived* half was brought forward at the same time and had to be backed
|
||||
out: the ETag is computed **before** the body, so on a cache miss no row
|
||||
exists and the handler emits the source-keyed form — then rendering
|
||||
creates the row, and the very next request resolves to the derived hash.
|
||||
The validator changed as a side effect of producing the body, so every
|
||||
first render was immediately stale. Caught by
|
||||
`thumbnail_etag_content_keyed.hurl`, where two consecutive GETs of an
|
||||
unchanged file stopped revalidating to 304.
|
||||
|
||||
The flip is what removes the hazard: once the derived tier is
|
||||
authoritative it is populated before it is consulted, so there is no
|
||||
window in which the row appears between two reads.
|
||||
|
||||
**The disk cache is `CachedBlobBackend`, reused unchanged.** No
|
||||
thumbnail-specific cache, no second root path. Routing derived
|
||||
blobs through the same stack gets, for free:
|
||||
|
||||
Reference in New Issue
Block a user