mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-07-09 17:28:42 +00:00
feat(rulake): per-shard over-request for federated top-K (SOTA folklore rule)
Closes the data-skew recall gap the 2026-04-23 cache/federation SOTA agent flagged. Weaviate/Elasticsearch default to k-per-shard which under-recalls when the true top-K is concentrated in one shard. Policy: k' = k + ceil(sqrt(k * ln(S))), clamped to [k, 4k] Examples: k=10, S=2 → k'=13 k=10, S=4 → k'=14 k=10, S=16 → k'=16 k=10, S=64 → k'=17 At k=10 the over-request is ~30-70% of k — cheap insurance against skew. Formula is the folklore rule cited in SPIRE (arxiv 2512.17264), HARMONY (SIGMOD'25), and the OpenSearch recall guide. Extra cost per shard is O(k' × rerank) — negligible vs the scan cost at rerank=20. Single-shard (S=1) returns k unchanged. Callers can still override via search_federated_with_rerank to get exact parity. 21 federation tests passing. Clippy -D warnings clean. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
4f458cd837
commit
a0fdd4d9b0
1 changed files with 31 additions and 1 deletions
|
|
@ -289,10 +289,23 @@ impl RuLake {
|
|||
Some((global / shards).max(Self::MIN_PER_SHARD_RERANK))
|
||||
}
|
||||
});
|
||||
// Per-shard over-request (2026-04-23 SOTA-federation agent):
|
||||
// k' = k + ceil(sqrt(k * ln(S)))
|
||||
// Closes the data-skew gap Weaviate / Elasticsearch leave with
|
||||
// naive k-per-shard fan-out. At k=10, S=4 this gives k'=13;
|
||||
// at k=10, S=16, k'=16. Cheap insurance against one shard
|
||||
// holding disproportionately more of the true top-K.
|
||||
let k_per_shard = Self::over_request_k(k, shards);
|
||||
let shard_hits: Result<Vec<Vec<SearchResult>>> = targets
|
||||
.par_iter()
|
||||
.map(|(backend, collection)| {
|
||||
self.search_one_with_rerank(backend, collection, query, k, rerank_override)
|
||||
self.search_one_with_rerank(
|
||||
backend,
|
||||
collection,
|
||||
query,
|
||||
k_per_shard,
|
||||
rerank_override,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let mut merged: Vec<SearchResult> = shard_hits?.into_iter().flatten().collect();
|
||||
|
|
@ -301,6 +314,23 @@ impl RuLake {
|
|||
Ok(merged)
|
||||
}
|
||||
|
||||
/// Per-shard over-request for federated search:
|
||||
/// `k' = k + ceil(sqrt(k * ln(S)))`, clamped to `[k, 4k]`.
|
||||
///
|
||||
/// The formula is the folklore rule cited in the 2024-2025
|
||||
/// federated-ANN literature (see SPIRE, HARMONY, OpenSearch
|
||||
/// recall guide). Over-requests enough to cover data-skew across
|
||||
/// shards without pulling so many rows that rerank cost explodes.
|
||||
/// Single-shard (S=1) returns k unchanged.
|
||||
#[inline]
|
||||
fn over_request_k(k: usize, shards: usize) -> usize {
|
||||
if shards <= 1 || k == 0 {
|
||||
return k;
|
||||
}
|
||||
let bonus = ((k as f64) * (shards as f64).ln()).sqrt().ceil() as usize;
|
||||
(k + bonus).min(k.saturating_mul(4))
|
||||
}
|
||||
|
||||
/// Like [`search_one`] but with an optional per-call rerank override.
|
||||
/// The federated path uses this to fan out with a reduced rerank
|
||||
/// budget per shard.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue