This section uses vector search, a beta
feature available in versions
0.25.0 and above. See How Hybrid Search
Works for background on why results are
merged by rank rather than by score.text) and a vector search ordered by distance (vector).
Each branch contributes weight / (k + rank), and the contributions are added.
rankis the row’s position in that branch’s list, starting at 1.weightscales how much that branch contributes, letting you value one search over the other. See Choosing Weights.kis the rank constant. It softens the difference between adjacent positions, so the top few results of a branch do not dominate everything below them.60is the conventional value and the one used throughout this page. See Choosing the Rank Constant.
k = 60 and the text branch weighted 1.0 against the vector branch’s 0.7:
Reciprocal Rank Fusion Query Shape
Each branch is anORDER BY ... LIMIT with a RANK() window function over the same ordering. The WITH clause fuses them.
1.0 and 0.7 are the per-branch weights, here valuing text matches over vector ones, and 60 is k.
The two WHERE clauses differ because each branch retrieves by its own method. description ||| 'running shoes' is the text branch’s query. The vector branch’s query is the query vector in its ORDER BY, so its WHERE is pdb.all(), meaning every row is eligible. To narrow either branch, add filters to both, so that each is drawing from the same set of eligible rows.
Verifying Pushdown
To verify that the hybrid query is being accelerated by ParadeDB, inspect the query plan by runningEXPLAIN:
TopKScanExecState printed for each branch.
TopKScanExecState, the branch is not taking the optimized path.
Choosing the Rank Constant
k controls how sharply rank position is discounted. The difference between rank 1 and rank 2 is 1/61 - 1/62 at k = 60, but 1/2 - 1/3 at k = 1.
k = 60is the widely used default and a good starting point.- Lower
k(20–40) sharpens the curve, so the top few results of each branch dominate. Use when one branch is much stronger and you want its top hits to win outright. - Higher
k(80–100) flattens the curve, spreading influence deeper into each list. Use when both branches are comparable in quality and you want agreement across them to matter more than either one’s top hit.
Choosing Weights
Weights scale each branch’s whole contribution. Equal weights (1.0 and 1.0) treat the two searches as equally trustworthy. Lowering the vector weight to 0.7 means a row must rank meaningfully higher in the vector branch to outrank a strong text match.
Tune weights against a labelled query set rather than by intuition. A useful default is to start equal, then reduce whichever branch produces more false positives on your data.
Choosing the Branch Limit
The fused result can only contain rows that at least one branch returned. A row missing from both candidate lists cannot be recovered later, so the branchLIMIT sets a hard ceiling on quality.
Fetch considerably more per branch than you intend to return — 100 to 200 candidates per branch for a top-10 result is a reasonable starting point. Because both branches push the limit into the index, raising it is much cheaper than it would be with a full sort.
Deterministic Results
Add a tiebreaker after the sort key in both branches so that rows with equal scores or equal distances come back in a stable order:LIMIT cuts through a group of tied rows. Without a tiebreaker, which of the tied rows survive is arbitrary, so the same query can return different candidates from one run to the next — and the fused result changes with them. Duplicate vectors make this common in the vector branch.
All tiebreaker columns must be in the ParadeDB index to keep the Top K optimization. See Deterministic Sorting.