One database. Five query modes.
Same data. Same instance.
OriginChain answers vector search, BM25 full-text, hybrid retrieval, graph traversal, and natural-language questions through a single endpoint. This page is the concept guide — what each algorithm actually does, where it wins, where it loses, and what the tradeoffs look like. For syntax and copy-paste examples, the docs cover that; this page is for the engineer deciding which mode to reach for.
Embeddings turn meaning into geometry.
An embedding model turns text - or an image, or audio - into a coordinate in a high-dimensional space, and places semantically similar inputs near each other: "running shoes" lands near "athletic footwear", far from "fruit smoothie". Search becomes geometry - finding the documents most relevant to a query means finding the document vectors closest to the query vector.
Brute-force distance-and-sort stops scaling around a few hundred thousand vectors. Past that, OriginChain uses HNSW (Hierarchical Navigable Small World, Malkov & Yashunin 2016) - a layered graph of vectors. Queries enter at the top layer, greedily walk toward the query point, and descend as they get closer, visiting a tiny fraction of the corpus while still finding the true top-k with very high probability. The tradeoff at query time is recall versus latency - search wider, get better recall, pay more time. Two named modes expose it:
fast When a downstream reranker rescues recall, or a tight latency SLO makes 37 ms the headline number.
high_recall default Default. When first-pass retrieval correctness matters - product search, RAG, citation lookup.
Both modes above are the in-RAM HNSW index, which is the low-latency choice up to the low tens of millions of vectors. Past that the graph no longer fits one box, so large single-index workloads switch to ivf_pq: on the 100M-vector BIGANN benchmark (real data, published ground truth) it measured recall@10 = 0.979 at p99 333 ms on a single box - inside the DiskANN-class leader band.
Four distance metrics.
"Closest" needs a definition. Pick the metric your embedding model recommends - the wrong choice silently degrades retrieval quality.
Angular closeness - direction matters, magnitude does not. Right for embeddings from text models (OpenAI, Cohere, Anthropic), where the model encodes semantics in the angle of the vector.
Faster than cosine when embeddings are already unit-normalised. Many production models (sentence-transformers, BGE) output unit vectors by default - same ranking as cosine at lower cost.
Absolute distance - magnitude carries signal. Right for embeddings where the model encodes intensity (image embeddings, some multi-modal models).
Sum of per-dimension absolute differences. More robust to outlier dimensions than L2 - useful when a few features dominate the geometry and you want every dimension to weigh in equally.
Four index variants.
Dense HNSW is the default; the other three are opt-in when corpus shape demands it. All four live behind the same /v1/tenants/:t/vector/:table/topk endpoint - the index choice is a per-table declaration, not a separate API.
The default. Floating-point f32 embeddings indexed by Hierarchical Navigable Small World, with SIMD kernels for the distance compute. v3 inline-embeddings build measured 11,700 QPS at recall@10 = 0.94 on SIFT-1M.
For BM25-trained models that emit high-dimensional sparse vectors where most entries are zero. Stored and queried as (index, weight) pairs - 10-100x smaller on disk than dense, and just as fast on selective queries.
Optional quantization layer that compresses a 768-dim f32 embedding to ~96 bytes. Recall drops a few points; storage drops ~30x. Right when the corpus is large enough that f32 storage is the constraint.
When a metadata filter is selective (`region = 'EU'`), the engine widens the HNSW search ef to absorb the filter pre-rate. Customer doesn't tune anything - the planner picks ef based on the filter's observed selectivity.
The ranking formula that beat vector search for fifty years.
BM25 (Best Match 25, Robertson & Sparck Jones, late 1970s; productionised in Okapi in the 1990s) powers Lucene, Elasticsearch, OpenSearch, and almost every full-text engine you have ever used. Vector embeddings are newer, but BM25 still wins on exact-phrase queries, product codes and SKUs, acronyms, and long-tail terms the embedding model has never seen. A production retrieval system needs both. The formula stacks three intuitions:
A query word that appears in 1% of documents is more informative than one that appears in 90% of them. This is the IDF (inverse document frequency) component.
IDF(t) A document containing the query word ten times is more relevant than one containing it once - but not ten times more relevant. BM25 saturates the term-frequency contribution.
tf plateaus Otherwise a thousand-word article would always outscore a hundred-word article on the same topic just because it contains more words.
|d| / avgdl score(q, d) = Σ IDF(t) · [ tf(t,d) · (k1 + 1) ] / [ tf(t,d) + k1 · (1 - b + b · |d| / avgdl) ]
t ∈ q
tf(t,d) = count of term t in document d · |d| = length of d in tokens
avgdl = average document length across the corpus
tuneable: k1 (term-frequency saturation) · b (length normalisation) The two knobs.
Defaults are k1 = 1.2 and b = 0.75. They are right for almost every corpus; the two cases worth tuning:
Term-frequency saturation - how quickly repeated mentions plateau. Lower k1 → repetition stops helping sooner; useful for short, info-dense documents. Higher k1 → repetition keeps mattering; useful for long-form content where the topic legitimately comes up many times.
Length normalisation - interpolates between none (b=0) and full (b=1). The 0.75 default - set by Robertson and Zaragoza after thousands of TREC experiments - works well for prose. Reduce to ~0.5 for documents of similar length; raise toward 1.0 when length varies wildly.
Vector and BM25 in parallel. Then fuse.
Every public benchmark on retrieval quality - BEIR, MTEB, MS MARCO - shows that combining vector and BM25 beats either alone. The two methods make different mistakes: vector search catches semantic matches the keyword search misses; BM25 catches exact terms the embedding model never saw. Run them in parallel, fuse the two ranked lists by rank - not by raw scores, which live on incomparable scales - and you keep what each is good at. The fusion is Reciprocal Rank Fusion (Cormack, Clarke, Büttcher, 2009):
RRF(d) = Σ 1 / (k + rank_i(d))
i ∈ retrievers
with k = 60 (the Cormack default)
rank_i(d) = position of d in retriever i's list, starting at 1
in both lists → rises to the top · in one list → still contributes
k damps the contribution of low-ranked hits
To bias toward one retriever - legal documents typically reward keyword matches more than semantic similarity, conceptual knowledge bases the opposite - use a weighted linear combination of normalised scores:
α · cos_norm(q, d) + (1 − α) · bm25_norm(q, d).
Tune α per domain. There is no universally right number.
Relations are edges. Traversal is a query.
A row can have typed relations to other rows - an order belongs to a customer, a paper cites another paper, an employee reports to a manager. Declare them in the schema and walk them as a graph. Both directions of every relation are first-class: "customer of order O" and "orders for customer C" are the same cost, and the engine maintains both sides atomically when the row is written - no reverse-index schema, no double-write.
Every operation runs against the same row store the SQL and vector layers use - there is no separate graph cluster to provision or sync.
The reverse side is maintained automatically when relations are declared bidirectional - no double-write on insert.
Breadth-first traversal up to a max depth. The reachability variant runs from both endpoints toward the middle - cuts work from O(d^k) to O(d^(k/2)) on power-law graphs.
Single-statement multi-hop traversal. Per-hop WHERE predicates filter intermediate rows before they fan out into the next hop.
Unweighted BFS shortest path plus caller-supplied edge-weight Dijkstra. Negative weights rejected.
Every node-disjoint route between two endpoints, with a max_paths hard cap to bound output on cyclic graphs. The bidirectional variant traverses forward AND reverse edges in the same walk - useful when 'is there any connection' matters more than direction.
Iterative power-method with damping; dangling-node mass redistributed uniformly. Returns nodes sorted by score, ties broken by PK lex for deterministic output.
Classic graph primitives. Components run on Union-Find; triangle enumeration uses the same forward-prefix scan the traversal helpers do.
> every traversal endpoint accepts ?explain=true
per-hop stats rows visited · edges expanded · time per hop
when a path query gets slow: is the cost in fan-out, or in the per-hop predicate? Ask in English. Get rows back.
What makes /ask production-grade rather than a demo: the question is compiled to a structured plan and executed by the engine itself.
POST a question - "top ten customers by revenue this quarter, excluding refunds" - to /ask. No query language.
"q": "top ten customers…" A foundation model reads your schema and your question and drafts a structured query. The LLM is the compiler, not the runtime - the plan passes through the same cost model and the same security boundary as any other query, so an /ask call cannot exfiltrate data the bearer wasn't entitled to see.
same cost model · same boundary The engine executes the plan against your data and streams the rows - grounded in your actual data, never hallucinated. Add ?explain=true to see exactly what SQL was produced, so it stays auditable.
?explain=true → the SQL Bring your own LLM key.
The LLM bill is yours, not ours. Configure your API key once in the dashboard; every /ask call routes through your account. You keep procurement-side control over which models you pay for; we keep the planner, the cache, and the security boundary.
The key is envelope-encrypted at rest under a per-tenant key and decrypted only in-memory when a request needs it.
One write. Every mode sees it.
Insert a row with a vector embedding, a full-text field and a typed relation, and all four updates land together. A query in another connection cannot see a state where the row exists but the vector index hasn't caught up, the posting is missing, or the reverse edge isn't there yet - it is structurally impossible to observe a half-written state. Your faithfulness check, your reranker, your audit log all see a corpus that is internally consistent at every instant.
A Pinecone-plus-Elasticsearch-plus-Postgres stack cannot guarantee this without a saga, a reconciler, and a worker that detects skew - and the cross-system drift is what produces the "sometimes the LLM hallucinates" reports postmortems struggle to explain.
> 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 Your plan, enforced at the engine.
Plans are not honour-system. Throughput limits, concurrent-call limits, and vector-corpus size limits are enforced inside the engine itself - not bolted on at an API gateway you could route around. When a request would exceed a cap, the engine returns a structured rejection with a machine-readable body: the cap, the current usage, and an upgrade URL. Three kinds of cap, three kinds of rejection:
For rate-style breaches - sustained req/s, concurrent /ask calls, active reactive subscriptions. Retry the same request after the header's window and it lands.
For state-style breaches - lifetime vector embeddings indexed. Retrying won't help; the plan needs more headroom. The body carries the upgrade link so client SDKs render a billing prompt directly.
Emitted as Prometheus gauges (oc_tier_*) and surfaced on the dashboard at 80% utilisation. Most customers see their headroom shrinking before any hard rejection fires.
Live visibility from two angles.
The same numbers render in two formats so the customer's dashboard and the operator's monitoring see the same truth.
JSON response: current plan, the full caps table, live counters (rows stored, vector embeddings indexed, concurrent /ask calls, active subscriptions), plus a per-schema breakdown showing which table is consuming what share of the budget. One call powers the dashboard's usage panel without scraping each shape separately.
Standard Prometheus text exposition. Cap gauges (oc_tier_*_cap) and headroom gauges side-by-side so an alert rule of the shape used / cap > 0.8 fires at the right threshold. Constant cardinality - the cap rows render at every scrape regardless of plan.
The decision table.
The features above all exist on the same engine. When you build, you still have to pick which one answers a given user question. Here is the short version.
Vector search Conceptual queries - "running shoes for marathons" matches "endurance trainers" because the embedding model captures intent. Cross-language queries. Semantic deduplication. Recommender systems. Exact identifiers, SKUs, error codes, names of new products that weren't in the embedding model's training data. BM25 full-text Exact phrase matching. Acronyms and product codes. Long-tail queries with unusual terms. Recall on documents that contain the literal keyword. Conceptual queries where the user's wording doesn't match the document's wording. Hybrid (vector + BM25) Production retrieval. Catches both the semantic match and the keyword match, fuses them, and beats either alone on every public benchmark. Nothing - this is the right default for any production RAG or search application. Graph traversal Multi-hop questions - "customers who bought from suppliers I haven't reviewed yet", "shortest path through citations", "who reports to whom". Single-table queries - those belong in SQL. Natural language Letting non-technical users ask questions of structured data without learning SQL. Internal dashboards. Customer-support agents. Cases where the question is ambiguous enough that a typed query would be more honest. One database for your whole AI stack.
Vector, BM25, hybrid, graph, and natural language against the same managed database - one bearer, one endpoint, one instance per tenant. A managed instance comes online in about ninety seconds, and the quickstart walks you from signup to your first English query in under ten minutes.