solution · recommendations

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.

one request, three signals
> 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
the problem it kills

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:

01
Filter before kNN

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.

02
Filter after kNN

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.

03
Oversample blindly

Fetch 10x the candidates and hope enough survive. Latency tanks on every query to insure against the worst one.

one plan, one decision boundary

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.

three signals, one engine

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.

Vector similarity
4 distance metricsHNSW defaultpre-filter walkermin_score

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 →
Graph signal
Node2VecGraphSAGE*1..N walksPageRank

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 →
SQL filters
WHEREpredicate pushdownsecondary indexes

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 →
how it composes

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.

01
Generate candidates

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)
02
Filter mid-walk

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
03
Add the graph signal

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"
filtered top-k
topk(embedding, :q, 10)
  WHERE in_stock = true
    AND region = 'EU'
  MIN_SCORE 0.72
walk from the last purchase
MATCH (u)-[:BOUGHT*1..2]->(p)
WHERE p.in_stock = true
RETURN DISTINCT p.id, p.name;
why the substrate matters here

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.

one insert, four updates
> 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
at a glance
4
vector distance metrics
7
vector index variants
14
graph methods shipped
*1..N
walk depth, per-hop WHERE
1
atomic write, every shape
next

Ship the recommender on one engine.