Repository navigation
Group search: merge local members on ids, load versions for the page alone - #707
Merged
Merged
Conversation
A group asked each local member (hosted, or a proxy searching its cache, as browse does) for its first 1,000 hits: every version of 1,000 ids and their casing, on every page and search, of which the merge used the ids. A member's ids past those were unreachable through the group, and its total stopped there. Local members now answer with the ids of all their hits (RepoReadOps::search_ids: the hosted and proxy backends return the id list their search computes anyway). The group merges, counts and pages the ids, then loads the versions and casing of its page alone, each hit through the member that won it (RepoReadOps::search_hits). The defaults of both page through search_unenriched, so any implementation stays correct. Members are searched four at a time instead of one after another. Since every id is now evaluated, the guard walk takes two shortcuts: an unfiltered graph memoizes one set of visible leaves for all ids, and an id no guard is active for is served by its visible leaves without a guarded walk. Fixes #536 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJXRhLYxFjzrWiwKFKc
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.
Fixes #536
What was wrong
A group asked each local member (hosted, or a proxy searching its cache, which in browse is every member) for its first
MAX_TAKE= 1,000 hits. Each hit came with every version and the id's casing, on every page and every debounced search. The merge used only the ids. The members were searched one after another.Because a local member's search had no window, it was never widened. So a group stopped at each member's first 1,000 ids. With the new test's setup on
main(a proxy cache of 1,500 ids plus a hosted member), browsing the group reports a total of 1,001, and nothing past the proxy's first 1,000 can be paged to.The fix
The fix follows the issue's suggestion. The member search is split in two: first the ids, then the hits of the final page.
RepoReadOpsmethods (crates/core/src/repo/mod.rs):search_ids(repo, params, filter): the ids of every hit of a search of what the repository holds, without the upstream, in its order.search_hits(repo, params, ids): the unenriched hits of those ids, in their order. An id with no matching version left is skipped.search_unenriched, so any implementation (the test stubs,OfflineMember) stays correct.PackageFilteredReadOpsapplies its filter to both.hosted.rs,proxy.rs):search_filteredis split intosearch_names(the sorted id list both already computed: theSELECT DISTINCT name …) andhits_of(versions and casing for some ids). The direct search uses the two halves as before. The proxy's two halves share one set oflistablebounds (ListableBounds).group.rs):MemberSearch::ids). Upstream searches are unchanged.page_hits→search_hits), one batch per member, andenrich_pageenriches them as before.MEMBER_SEARCHES), as are the page loads.visible_leavesmemoizes one entry for all ids;serving_leavesreturns the visible leaves without a guarded walk when no group's guard is active for the id. With no active guard, the guarded walk visits exactly what the unguarded one does.The total is exact for local members, over all their ids, under the guards. That includes ids an owner holds that the cache lists (the second core test). The prefer-hosted ownership lookup therefore now covers every candidate id instead of at most 1,000 per member.
Measured
Same shape as the issue: Postgres 18, a hosted member with 300 ids × 15 versions, and a proxy cache of 4,000 ids with 219,000 versions. Times are psql timings of the queries a page runs (3 runs each), on this session's container:
On top of that, before the change the server decoded and sorted about 55,000 version rows per page, and read the casing of 1,000 ids. Now it handles the page's 20 ids.
Tests
crates/server/tests/browse.rs,a_group_browses_every_cached_id_of_its_proxy:mainit fails with total 1,001.crates/core/tests/group_read.rs:a_group_pages_local_ids_and_loads_only_its_page: two hosted members with 1,200 and 300 ids, 100 of them shared. A recording wrapper shows that each member lists its ids once and loads hits only for the ids it won on the page:search_hitsgets exactly those 10 + 10 ids, and nothing callssearch/search_unenriched. Versions come from the winning member, casing is kept, the total is 1,400, and the last page is reached.a_guard_takes_ids_past_the_first_thousand_off_the_total: a hosted owner holds 10 ids only as unlisted versions, and a cache of 1,100 ids lists them too. The prefer-hosted guard takes them off the total (1,090) and off the page, past the first 1,000.an_offline_proxy_is_searched_like_a_local_member: the stub now sees two unwindowed searches (the ids, then the page's hits) instead of one.Checks run
cargo test -p forge-core: all suites;group_read40 passed,proxy_read58,hosted_read12.cargo test -p forge-server --lib --test browse --test admin --test admin_rbac --test dispatch --test group_proxy --test plugin_contract --test proxy_search: all passed.cargo clippy -p forge-core -p forge-server --all-targets -- -D warnings,cargo fmt --all --check,cargo xtask changelog --check,cargo xtask docs --check..sqlx/is unchanged.Changelog:
docs/changes/2.2.0/536.fixed.md. No admin API change, so no web UI screen.Generated by Claude Code