Speed up search by ranking from a title map - #3408
Open
enf0rc3 wants to merge 3 commits into
Open
Conversation
|
Pull request environment is available at https://stoctodocspr3408.z22.web.core.windows.net. You can view the ephemeral environment status in Octopus Deploy. This environment will be automatically deprovisioned when the pull request is closed, or after 7 days of inactivity. |
Ranking needed a url and a title, and Pagefind keeps both in the per-page fragment — so ordering thirty results meant fetching thirty files before the panel could draw, and a page ranked past thirty could not be reached at all. The build now writes a map of result id to url and title beside the index, which is the join Pagefind's own result stub already carries. So the whole result set is ranked before anything is fetched, and fragments are fetched only for the rows being drawn: ten per batch, against thirty to thirty-five for every settled query before. On Slow 4G with the map served uncompressed, first results arrive in 7.2s against 8.9s; the map is 28 KB gzipped, so most of that 122 KB is transfer a CDN removes. The shallow-page search this replaces is gone with it — the second Pagefind query, the landing filter, LANDING_DEPTH and the attribute it needed on every page's content div. A page the query names now wins from anywhere in the list rather than from a shortlist of 227. Relevance holds on both traffic-weighted sets: real-searches 57% w-S@1 and 84% w-S@5, top-pages 90% and 98%, unchanged either side. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
enf0rc3
force-pushed
the
willlaugesen/search-title-map
branch
from
August 26, 2026 03:36
176d4d7 to
399b5a1
Compare
enf0rc3
marked this pull request as ready for review
August 26, 2026 04:53
Two things found reviewing the branch before publishing it. The fallback for a missing or mismatched title map drew results in Pagefind's own order and never ranked them, while its comment claimed otherwise. That is the order the reorder exists to correct — a bare BM25 list puts a getting-started page above the section it belongs to. `draw` now orders each batch as it draws it when the list reached it unranked, which is what the engine did before the map existed, and `more()` carries the same flag so later batches match. Verified by blocking the map: three of four sample queries still land on the right page, and the fourth is one whose answer ranks past the batch. A map that matches the index only partly dropped the unmatched results quietly, because the fallback fires only when nothing joins at all. It now warns with the counts. Also removed a stale doc comment left on the Ranked type. Relevance unchanged: real-searches holds at 57% w-S@1 and 84% w-S@5. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An unhashed name lets a cache serve one build's map against another build's index. Pagefind hashes its own chunks for that reason, and the map has to be versioned with them: read against the wrong index it joins against nothing. The build now reads the hash out of pagefind-entry.json and writes docs-titles.<hash>.json; the client reads the same file to build the same name. Two requests where there was one, both while the index is warming and neither on the path of a search. It also means the map can carry an immutable cache header, which an unhashed name could not. The build fails if the index has more than one language, because one map cannot carry two hashes and the client would look under a name that does not exist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
enf0rc3
force-pushed
the
willlaugesen/search-title-map
branch
from
August 26, 2026 05:30
efad2a1 to
3cf4285
Compare
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.
Pagefind keeps each page's URL and title inside that page's own result file, one file per result. Ranking needs both, so putting thirty results in order meant downloading thirty files before the panel could draw a single row. On the last PR a reviewer watched them trickle through six at a time on Slow 4G and asked whether we could fetch fewer.
The build now writes those URLs and titles into one small map beside the index. Ranking reads the map, and result files are downloaded only for the rows on screen.
What improves
Measured on one host, same corpus, same throttling, so the numbers are the change and not the hosting:
The map costs 30 KB gzipped, once per page load, against roughly 28 KB of result files saved on every search. It pays for itself from the second search onward, and more than halves the request count every time — which is what a slow connection feels most.
Results are unchanged. Both traffic-weighted query sets score identically to
main: 57% and 84% top-one and top-five on the real search log, 90% and 98% on the analytics set. Same ranking inputs, same order.One behaviour changes with it: the first screen draws 10 rows where it drew 30, so an answer ranked below ten now arrives on a scroll. That is 6 of 155 measured queries, none ranked any lower and all one scroll away.
It also lets a page the query names win from anywhere in the results, where it previously had to appear in a shortlist of 227. That shortlist, the second Pagefind query per search, and the filter it needed on every page all go away here.
Worth a reviewer's opinion
The map is fetched with the index, so someone who opens the overlay and never types pays 30 KB they did not before. Moving it to the first keystroke would avoid that, at the cost of putting it on the path of the first search.