fix(api): derive the pool/records TTL from what the query actually costs (#687) - #839
Merged
Merged
Conversation
…sts (#687) `pool/records?window=month` could serve an answer up to five minutes stale. The 300s TTL was not arbitrary — it was sized for a query that measured 7-20s, where recomputing often would hang the pool page. That cost was a property of the DATA, not the query. The month window's cost is dominated by how many rows of `shares_archive` still fall inside it, and the archive stopped being written at the v56 cutover. Measured on ghost-vm6 today: the archive's newest row is 18.7 days old, 550,321 archive rows remain inside the 30-day window against 55,760 live ones, and the query answers in ~0.6s, not ~20s. As the frozen archive ages past the 30-day boundary that term goes to zero and the window converges on the live-shares-only cost — 95ms, measured. A hard-coded 300s would then be five minutes of staleness bought for nothing. So hold the DUTY CYCLE constant instead of the interval: recompute after 60x the query's own measured duration, clamped to a 5s floor and the existing per-window ceiling. Today that serves the month window at ~36s instead of 300s; once the archive ages out it becomes ~6s; and a node whose query regresses backs off by itself rather than melting — which is the protection the fixed ceiling was really providing. Two things this deliberately does NOT do: - It does not make the query cheaper. Ranking by rarity means `reverse_hex(share_hash)` over every row, a function of the column, so no index can serve the `ORDER BY`. The structural fix is an indexed display-order column and a backfill of ~2M archive rows; it is not attempted here. - It does not rank by `difficulty` instead. That was tested and refuted: `difficulty` is the ASSIGNED vardiff target, not the achieved one — four distinct hashes in the current month window share `620606.037971378` to fifteen significant figures, so ordering by it picks an arbitrary one of the four rather than the rarest. Claude-Session: https://claude.ai/code/session_01Td1vvfowptTTnu88qG2iym
defenwycke
added a commit
that referenced
this pull request
Sep 6, 2026
Supersedes v1.11.36, which reached the canaries but not production.
v1.11.36 was cut, gated, built and rolled to all four canaries, where it soaked
cleanly. Production then refused it, correctly:
REFUSED: 4d71781 no longer matches origin/main for: bins/pool-sv2 crates
main has moved (a revert, or newer commits)
#839 and #840 were merged AFTER the release commit, and #839 touches `crates/`,
which builds `pool_sv2`. The guard is doing exactly its job — it is the check
that would have caught the #447 revert (#459) — so the fix is to cut a release
that matches main, not to bypass it.
The lesson is about ordering, not the guard: do not merge other PRs between
cutting a release and finishing its roll. A release SHA has to stay reachable as
main's tip for the paths it builds, for as long as the roll takes.
This release therefore carries everything from v1.11.35 and v1.11.36 plus #687
and #759.
Claude-Session: https://claude.ai/code/session_01Td1vvfowptTTnu88qG2iym
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #687.
pool/records?window=monthcould serve an answer up to five minutes stale.The 300s was not arbitrary — it was sized for a query that no longer exists
The TTL was chosen when the month window measured 7-20s, where recomputing
often would hang the pool page. But that cost is a property of the data,
not the query: the month window's cost is dominated by how many rows of
shares_archivestill fall inside it, and the archive stopped being written atthe v56 cutover.
Measured on
ghost-vm6, 2026-09-06:shares_archiverows (total, static)sharesrows inside the windowThe archive is frozen, so in roughly eleven days zero archive rows fall
inside the month window and the query converges on 95ms. Holding a 95ms answer
for five minutes is staleness bought for nothing.
⚠ That decay is a coincidence of the archive being frozen, not a fix — which is
why this PR does not simply lower the constants to today's numbers. They would
be wrong again, in the other direction, in eleven days.
Hold the duty cycle constant, not the interval
adaptive_records_ttlrecomputes after60xthe query's own measured duration,clamped to a 5s floor and the existing per-window ceiling. The measured cost is
stored in the memo alongside the answer.
the protection the fixed ceiling was really providing, now automatic
The cost is measured after the error arm returns, so a fast failure cannot be
recorded as a cheap success and drive the TTL down.
What this deliberately does not do
reverse_hex(share_hash)over every row — a function of the column, so noindex can serve the
ORDER BY. The structural fix is an indexed display-ordercolumn plus a backfill of ~2M archive rows; that is a migration, not this PR.
difficultyinstead. Tested and refuted:difficultyis the assigned vardiff target, not the achieved one. Four distinct hashes
in the current month window carry
620606.037971378to fifteen significantfigures, so ordering by it returns an arbitrary one of the four rather than
the rarest. The handler's own
winning_assigned_diffnaming agrees.Tests
Four new tests, each naming the behaviour rather than the constant. They were
mutation-checked: reverting
adaptive_records_ttlto the old fixed-TTLbehaviour fails
a_cheap_query_is_not_cached_for_the_window_ceilingand nothingelse, so the suite encodes the fix rather than restating it.
https://claude.ai/code/session_01Td1vvfowptTTnu88qG2iym