Skip to content

Speed up search by ranking from a title map - #3408

Open
enf0rc3 wants to merge 3 commits into
mainfrom
willlaugesen/search-title-map
Open

Speed up search by ranking from a title map#3408
enf0rc3 wants to merge 3 commits into
mainfrom
willlaugesen/search-title-map

Conversation

@enf0rc3

@enf0rc3 enf0rc3 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

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:

Slow 4G before after
first search 8.8 s 7.3 s
second search in a session 5.9 s 4.1 s
network requests per search 36 16

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.

@team-marketing-branch-protections

Copy link
Copy Markdown

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
enf0rc3 force-pushed the willlaugesen/search-title-map branch from 176d4d7 to 399b5a1 Compare August 26, 2026 03:36
@enf0rc3
enf0rc3 marked this pull request as ready for review August 26, 2026 04:53
enf0rc3 and others added 2 commits August 26, 2026 17:04
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
enf0rc3 force-pushed the willlaugesen/search-title-map branch from efad2a1 to 3cf4285 Compare August 26, 2026 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants