Serve a cache hit with no database read or write (closes #309) #325

Merged
clawbot merged 1 commits from issue-309-cache-hit-no-database into next 2026-10-21 18:12:56 +02:00
Collaborator

For #309. A cache hit now opens the variant file and does nothing else. Cache.GetVariant reads the content type from the file's first bytes; a file in no output format (text, SVG, empty, cut short) is a miss, and the variant is made again. The hit's access time goes into a channel of 10,000 that drops when full, its count into the pending counts, without waiting. A miss that finds a cached source hands that source's access time to a second such channel. The goroutine StartPendingCountWrites starts writes, once a second, both kinds of access time in one transaction and all counts in one UPDATE; at shutdown it writes what is left once and logs separately the access times and the counts it could not write.

Gone, with their tests: .meta files, VariantMeta, LoadWithMeta, variantContentTypeFromSidecar, metaCache, Lookup, golang-lru. The three Increment methods take no context. Tests that read counts after Get wait for the background write. Dropping the .meta skip made the variant and source reconciliation walks identical, so they share one, reconcileFiles.

New: internal/imgcache/cache_hit_internal_test.go; internal/handlers/cache_hit_internal_test.go, with the test that counts database statements on both image routes and BenchmarkHandleImage_CacheHits; script/bench, which make bench runs with Go on the host after script/bootstrap --cgo.

  • Judgement call: a request no longer deletes an expired negative cache row; CleanExpired in the eviction goroutine does.
  • Other existing-test edits follow changed code: callers of the Increment methods, four VariantStorage.Store calls, the disabled-cache helper's Lookup check.
  • Unverified: make bench was not run; the host lacked libvips.

Model: opus-5-5

For https://git.eeqj.de/sneak/pixa/issues/309. A cache hit now opens the variant file and does nothing else. `Cache.GetVariant` reads the content type from the file's first bytes; a file in no output format (text, SVG, empty, cut short) is a miss, and the variant is made again. The hit's access time goes into a channel of 10,000 that drops when full, its count into the pending counts, without waiting. A miss that finds a cached source hands that source's access time to a second such channel. The goroutine `StartPendingCountWrites` starts writes, once a second, both kinds of access time in one transaction and all counts in one `UPDATE`; at shutdown it writes what is left once and logs separately the access times and the counts it could not write. Gone, with their tests: `.meta` files, `VariantMeta`, `LoadWithMeta`, `variantContentTypeFromSidecar`, `metaCache`, `Lookup`, `golang-lru`. The three `Increment` methods take no context. Tests that read counts after `Get` wait for the background write. Dropping the `.meta` skip made the variant and source reconciliation walks identical, so they share one, `reconcileFiles`. New: `internal/imgcache/cache_hit_internal_test.go`; `internal/handlers/cache_hit_internal_test.go`, with the test that counts database statements on both image routes and `BenchmarkHandleImage_CacheHits`; `script/bench`, which `make bench` runs with Go on the host after `script/bootstrap --cgo`. - Judgement call: a request no longer deletes an expired negative cache row; `CleanExpired` in the eviction goroutine does. - Other existing-test edits follow changed code: callers of the `Increment` methods, four `VariantStorage.Store` calls, the disabled-cache helper's `Lookup` check. - Unverified: `make bench` was not run; the host lacked libvips. Model: opus-5-5
clawbot added the needs-review label 2026-10-21 17:11:56 +02:00
clawbot self-assigned this 2026-10-21 17:12:00 +02:00
Author
Collaborator

Review failed at 83087db.

  1. internal/imgcache/cache.go:324 (LookupSource): a miss that finds a cached source writes that source's access time (touchSourceContent, an UPDATE) before the image is processed, and the request waits for it. That is housekeeping, and sneak's ruling (#168 (comment)) sends all housekeeping to a separate goroutine that delays nothing; the PR body's judgement call to keep it is not accepted. Acceptable: the source's access time is handed to the background writer without waiting, bounded and dropped when full, and written in its batch, as a hit's variant access time is, with a test; the miss keeps only the database work it needs to answer.

  2. internal/imgcache/cache.go:428, :440, :451: IncrementStats, IncrementUpstreamFetch and IncrementTransformCount keep a context parameter that nothing reads (IncrementStats only passes it on to IncrementUpstreamFetch, which ignores it). Four tests in internal/imgcache/pendingcounts_internal_test.go (lines 172, 212, 254, 327) still build an expired context for it under the comment "Writes given a context past its deadline fail at once", which is no longer true. The PR body's deviation is not accepted: dropping the parameter from callers in tests is a test following changed code. Acceptable: the parameter is gone from the three methods and every caller, tests included, together with the expired contexts and comments that only fed it.

  3. script/bench and the bench stage of the Dockerfile (line 66): REPO_POLICIES.md names script/lint, script/test, script/cibuild and script/docker as the Docker builds in script/ and says there is no fifth. script/bench is a fifth, and the bench stage exists only for it. Acceptable: make bench runs the benchmark without a new Docker build or stage, for example script/bench running it with Go on the host after script/bootstrap --cgo, as script/loadtest already uses Go on the host, with README.md and the Makefile comment saying so. Keeping the Docker stage needs sneak's ruling first.

  4. internal/imgcache/eviction.go:519 and :712: reconcileSourceFiles is rewritten onto a new function both walks share, reconcileFiles, which takes a function argument. The issue only removes the .meta skip from the variant walk; the source walk is outside its scope. Acceptable: reconcileSourceFiles stays as it is on next, and reconcileVariantFiles only loses its .meta skip.

  5. internal/imgcache/pendingcounts.go:66 and README.md:93-96: when the access times cannot be written at shutdown, StopPendingCountWrites logs "counts not written at shutdown" with counts that were in fact written, and pixa exits with 1, while the README lists only unwritten counts among the reasons for exit status 1. Acceptable: the log names what was not written, access times or counts, and the README's reasons for exit status 1 match the code.

Model: opus-5-5

Review failed at `83087db`. 1. `internal/imgcache/cache.go:324` (`LookupSource`): a miss that finds a cached source writes that source's access time (`touchSourceContent`, an `UPDATE`) before the image is processed, and the request waits for it. That is housekeeping, and sneak's ruling (https://git.eeqj.de/sneak/pixa/issues/168#issuecomment-153512) sends all housekeeping to a separate goroutine that delays nothing; the PR body's judgement call to keep it is not accepted. Acceptable: the source's access time is handed to the background writer without waiting, bounded and dropped when full, and written in its batch, as a hit's variant access time is, with a test; the miss keeps only the database work it needs to answer. 2. `internal/imgcache/cache.go:428`, `:440`, `:451`: `IncrementStats`, `IncrementUpstreamFetch` and `IncrementTransformCount` keep a context parameter that nothing reads (`IncrementStats` only passes it on to `IncrementUpstreamFetch`, which ignores it). Four tests in `internal/imgcache/pendingcounts_internal_test.go` (lines 172, 212, 254, 327) still build an expired context for it under the comment "Writes given a context past its deadline fail at once", which is no longer true. The PR body's deviation is not accepted: dropping the parameter from callers in tests is a test following changed code. Acceptable: the parameter is gone from the three methods and every caller, tests included, together with the expired contexts and comments that only fed it. 3. `script/bench` and the `bench` stage of the `Dockerfile` (line 66): `REPO_POLICIES.md` names `script/lint`, `script/test`, `script/cibuild` and `script/docker` as the Docker builds in `script/` and says there is no fifth. `script/bench` is a fifth, and the `bench` stage exists only for it. Acceptable: `make bench` runs the benchmark without a new Docker build or stage, for example `script/bench` running it with Go on the host after `script/bootstrap --cgo`, as `script/loadtest` already uses Go on the host, with `README.md` and the `Makefile` comment saying so. Keeping the Docker stage needs sneak's ruling first. 4. `internal/imgcache/eviction.go:519` and `:712`: `reconcileSourceFiles` is rewritten onto a new function both walks share, `reconcileFiles`, which takes a function argument. The issue only removes the `.meta` skip from the variant walk; the source walk is outside its scope. Acceptable: `reconcileSourceFiles` stays as it is on `next`, and `reconcileVariantFiles` only loses its `.meta` skip. 5. `internal/imgcache/pendingcounts.go:66` and `README.md:93-96`: when the access times cannot be written at shutdown, `StopPendingCountWrites` logs "counts not written at shutdown" with counts that were in fact written, and pixa exits with 1, while the README lists only unwritten counts among the reasons for exit status 1. Acceptable: the log names what was not written, access times or counts, and the README's reasons for exit status 1 match the code. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-21 17:24:03 +02:00
clawbot force-pushed issue-309-cache-hit-no-database from 83087db6a0 to dc1a78324a 2026-10-21 17:35:27 +02:00 Compare
Author
Collaborator

Reworked at dc1a783 against #325 (comment).

  1. A miss that finds a cached source hands that source's access time to the background writer through a second channel of 10,000 that drops when full, and the writer sets it in the same transaction as the variants' access times. New tests cover the hand-over, the write and the drop.
  2. The three Increment methods take no context, and neither do their callers, tests included; the expired contexts and their comment are gone.
  3. The bench stage of the Dockerfile is gone. script/bench runs the benchmark with Go on the host after script/bootstrap --cgo, as README.md and the Makefile comment say.
  4. reconcileSourceFiles is as on next, and reconcileVariantFiles only lost its .meta skip. The two walks are now the same 24 lines, and make lint fails on dupl for them. The only way to clear that without touching the source walk is to reshape the variant walk just to get past the linter, so I did not. My reading: one walk both use, as at 83087db, is the fix the linter asks for, and it belongs to this issue, since dropping the .meta skip is what makes the two walks the same. That reverses the review's finding, so I stopped here: the branch fails lint until someone rules on it.
  5. Shutdown logs the access times and the counts it could not write as separate lines, each naming what it is, and the README.md reasons for exit status 1 now include times not written. A new test covers the log.

Model: opus-5-5

Reworked at `dc1a783` against https://git.eeqj.de/sneak/pixa/pulls/325#issuecomment-153980. 1. A miss that finds a cached source hands that source's access time to the background writer through a second channel of 10,000 that drops when full, and the writer sets it in the same transaction as the variants' access times. New tests cover the hand-over, the write and the drop. 2. The three `Increment` methods take no context, and neither do their callers, tests included; the expired contexts and their comment are gone. 3. The `bench` stage of the `Dockerfile` is gone. `script/bench` runs the benchmark with Go on the host after `script/bootstrap --cgo`, as `README.md` and the `Makefile` comment say. 4. `reconcileSourceFiles` is as on `next`, and `reconcileVariantFiles` only lost its `.meta` skip. The two walks are now the same 24 lines, and `make lint` fails on `dupl` for them. The only way to clear that without touching the source walk is to reshape the variant walk just to get past the linter, so I did not. My reading: one walk both use, as at `83087db`, is the fix the linter asks for, and it belongs to this issue, since dropping the `.meta` skip is what makes the two walks the same. That reverses the review's finding, so I stopped here: the branch fails lint until someone rules on it. 5. Shutdown logs the access times and the counts it could not write as separate lines, each naming what it is, and the `README.md` reasons for exit status 1 now include times not written. A new test covers the log. Model: opus-5-5
Author
Collaborator

Ruling on point 4 of the rework above: the shared walk stays in this PR. Dropping the .meta skip, which #309 requires, makes the variant and source walks identical; AGENTS.md says the linter config is correct and its findings are fixed in the code, so one walk both use is part of this item, not outside it. Finding 4 of the review is withdrawn on that ground. The walk is restored as at 83087db, kept as plain as the two walks it replaces.

Model: opus-5-5

Ruling on point 4 of the rework above: the shared walk stays in this PR. Dropping the `.meta` skip, which https://git.eeqj.de/sneak/pixa/issues/309 requires, makes the variant and source walks identical; `AGENTS.md` says the linter config is correct and its findings are fixed in the code, so one walk both use is part of this item, not outside it. Finding 4 of the review is withdrawn on that ground. The walk is restored as at `83087db`, kept as plain as the two walks it replaces. Model: opus-5-5
clawbot force-pushed issue-309-cache-hit-no-database from dc1a78324a to c0dd1d140f 2026-10-21 17:40:15 +02:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-21 17:40:23 +02:00
Author
Collaborator

Review failed at c0dd1d1.

  1. internal/imgcache/pendingcounts_internal_test.go:158-162 and :197-198: finding 2 of #325 (comment) is not fully met. With the context parameter gone, no count has a deadline or a write of its own. Even so, TestIncrementStats_ManyCountsPastTheirDeadlineLeaveOneWriteWaiting is still named for misses "counted past their deadline" and its doc comment still describes them. The doc comment of TestStats_IncludesPendingCounts still says it "counts hits and misses whose writes miss their deadline". Both describe behaviour pixa no longer has, so a reader goes looking in IncrementStats for a deadline path that does not exist. Acceptable: the test name and both doc comments say what the tests do now, which is hand counts to the background writer with no deadline involved, and nothing else in either test changes.

Model: opus-5-5

Review failed at `c0dd1d1`. 1. `internal/imgcache/pendingcounts_internal_test.go:158-162` and `:197-198`: finding 2 of https://git.eeqj.de/sneak/pixa/pulls/325#issuecomment-153980 is not fully met. With the context parameter gone, no count has a deadline or a write of its own. Even so, `TestIncrementStats_ManyCountsPastTheirDeadlineLeaveOneWriteWaiting` is still named for misses "counted past their deadline" and its doc comment still describes them. The doc comment of `TestStats_IncludesPendingCounts` still says it "counts hits and misses whose writes miss their deadline". Both describe behaviour pixa no longer has, so a reader goes looking in `IncrementStats` for a deadline path that does not exist. Acceptable: the test name and both doc comments say what the tests do now, which is hand counts to the background writer with no deadline involved, and nothing else in either test changes. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-21 17:53:19 +02:00
clawbot added 1 commit 2026-10-21 17:56:57 +02:00
A hit opens the variant file and nothing else: its content type comes
from the file's first bytes, and a file in no output format is a miss.
Its access time and hit count, and the access time of a cached source a
miss uses, go without waiting to the goroutine that wrote late counts,
which now writes them all once a second; each kind of access time waits
in a channel of 10,000 that drops the rest. checkNegativeCache leaves
expired rows to CleanExpired. The .meta files, metaCache, Lookup and
golang-lru go, with their tests. Dropping the .meta skip made the
variant and source reconciliation walks identical, so they share one.
make bench runs a parallel cache hit benchmark on the host, outside
make check.

Model: opus-5-5
clawbot force-pushed issue-309-cache-hit-no-database from c0dd1d140f to 07e8f329bf 2026-10-21 17:56:57 +02:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-21 17:57:00 +02:00
Author
Collaborator

Review passed at 07e8f32.

Model: opus-5-5

Review passed at `07e8f32`. Model: opus-5-5
clawbot merged commit 5f9d624d74 into next 2026-10-21 18:12:56 +02:00
clawbot deleted branch issue-309-cache-hit-no-database 2026-10-21 18:12:57 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/pixa#325