Similar. Connected. In stock.
One query plan.
Most stacks split a recommender across a vector DB, a graph DB and SQL — then pay the integration tax in sync jobs and recall holes. OriginChain composes all three signals inside one plan, against one consistent snapshot.
> POST /v1/tenants/:t/vector/search
{
"schema": "products",
"k": 12,
"dense": { "query": [...], "metric": "cosine" },
"where": "in_stock = true AND region = 'IN'",
"graph": { "rerank_by": "pagerank",
"schema": "co_purchase" }
}
plan
hnsw walk predicate evaluated mid-walk
over-fetch widened for filter selectivity
pagerank rerank co_purchase graph, same snapshot
12 of 12 survivors after the predicate Filtering after kNN is a recall hole.
When similarity, graph signal and inventory live in three systems, every query has to pick from three options — all bad:
Your vector DB doesn't know your inventory. The pre-filter lives in another system, behind another sync job, one skew window away from recommending what you can't sell.
Recall collapses when most of your top-k fail the filter. Ask for 12, filter for in-stock, get back 3 — and the widget renders half-empty.
Fetch 10x the candidates and hope enough survive. Latency tanks on every query to insure against the worst one.
When the query carries a WHERE clause, adaptive over-fetch widens the kNN candidate pool
just enough to leave k survivors
after the predicate — the planner picks the width from the filter's observed selectivity,
so you tune nothing. Recall stays at target; you don't pay full-table-scan latency.
Everything a recommender ranks on, resident in one store.
No connectors between them, no nightly export. Each signal reads the same rows the others do — so the plan can interleave them instead of gluing their outputs together.
Find items like this one. The predicate is evaluated on each candidate as the graph walk visits it — non-matching nodes are pruned mid-walk.
Vector engine →Who bought this also bought that. Embed the interaction graph, or stay symbolic and walk *1..N from the last purchase with per-hop WHERE.
Graph engine →In stock, in region, in budget. Predicates push down to secondary indexes — the planner splits conjuncts and routes each to the cheapest index.
SQL surface →Candidates, filter, graph — inside one plan.
The three signals aren't three round-trips. They are stages of a single plan the optimiser owns end to end — which is what lets it trade candidate width against filter selectivity instead of guessing.
Nearest neighbours over product embeddings via topk — or embed the interaction graph itself with Node2Vec or GraphSAGE and search that space. Both live behind the same endpoint.
topk(embedding, :q, 12) The WHERE predicate runs on each candidate as the HNSW walk visits it. When the filter is selective, adaptive over-fetch widens the search just enough to leave k survivors — the planner picks the width from the filter's observed selectivity.
WHERE in_stock = true Rerank by PageRank on the co-purchase graph, or walk *1..N out from what the user just bought, filtering each hop before it fans out. Same relation keys, same snapshot.
"rerank_by": "pagerank" topk(embedding, :q, 10)
WHERE in_stock = true
AND region = 'EU'
MIN_SCORE 0.72 MATCH (u)-[:BOUGHT*1..2]->(p)
WHERE p.in_stock = true
RETURN DISTINCT p.id, p.name; A recommendation can't point at a row that no longer matches.
On one substrate, the product row, its embedding, its text postings and its co-purchase edges land in a single atomic write. A concurrent query cannot observe a state where the vector exists but the inventory row is stale, or the edge hasn't arrived yet — it is structurally impossible, not best-effort.
In a three-system stack, that consistency is a saga, a reconciler, and a worker that detects skew. The skew window is exactly where a recommender surfaces the sold-out item, the deleted product, the edge to nowhere.
> POST /v1/tenants/:t/rows/products
> { "name": "trail runner", "embedding": […],
> "description": "…", "brand": "b_12" }
committed atomically
row products/p_1 ✓
vector hnsw entry ✓
posting bm25 term postings ✓
edge brand ↔ product (both) ✓
a concurrent reader sees all four — or none