Sobre SIFT 1M (128 dimensions, recall ≈ 0,99) una consulta única sosté ~356 qps, i ~700 qps sota càrrega concurrent en 8 nuclis. L’índex és 14–21× més petit que els índexs HNSW amb què es compara (pgvector, Qdrant) — aproximadament 39 MB per milió de vectors —, o sigui que cap en una fracció de la RAM. La construcció és paral·lela i en flux (≈186 s → 64 s), i no materialitza mai el conjunt de dades, de manera que escala a desenes de milions de vectors en una màquina modesta.

Arkeion emmagatzema vectors, no els fabrica

Arkeion no inclou cap model ni cap dependència d’ML — això és una regla inamovible, i és el que manté el motor determinista i auditable. Els embeddings els produeix el teu propi model, al teu costat, i s’entreguen a Arkeion com a dades. El motor emmagatzema floats opacs i els cerca; no els interpreta mai. A canvi, els teus vectors hereten tot allò que el fitxer ja garanteix: versionat, l’historial encadenat per hashos, AS OF i verify().

Un vector només són dades

Un vector és un BLOB de f32 en little-endian — cap tipus de columna nou, cap canvi de format. Construeixes i compares vectors amb un grapat de funcions escalars pures:

-- vector(x, y, …)          pack floats into a BLOB
-- cosine_distance(a, b)    1 − cosine similarity; lower = more similar
-- l2_distance(a, b)        Euclidean distance;    lower = more similar
-- dot(a, b)                dot product;           higher = more similar

CREATE TABLE docs (id INTEGER PRIMARY KEY, body TEXT, emb BLOB);
INSERT INTO docs (body, emb) VALUES ('…', vector(0.1, 0.2, 0.3));  -- or a BLOB from your client

SELECT id, body
FROM docs
ORDER BY cosine_distance(emb, vector(0.11, 0.19, 0.31))
LIMIT 10;

Les distàncies s’acumulen en f64, i una discrepància de dimensions és un error, no pas una resposta errònia silenciosa. Aquell ORDER BY … LIMIT ja és un K-nearest-neighbour exacte: recorre les files, en calcula la distància a cadascuna i es queda les K millors. Com que només són dades dins de l’arbre versionat, afegir-hi AS OF VERSION n cerca el passat semànticament — la mateixa consulta, contra la base de dades tal com era en qualsevol commit.

El KNN exacte és el fonament

El KNN exacte — força bruta sobre tots els candidats — és determinista, complet i reproduïble. No són virtuts accidentàries: són allò amb què es mesura un índex ANN (proporciona la veritat de referència per al recall) i allò sobre el qual es construeix un pas de re-rank. Per a col·leccions de fins a ~1M de vectors sovint ja n’hi ha prou, sobretot amb vectors int8 (més avall). Tot el que és més ràpid es construeix a sobre seu, no pas en lloc seu.

Quantització int8

vector_i8() emmagatzema un vector com un factor d’escala més un byte amb signe per dimensió (quantització simètrica per vector, max|v| / 127). Una etiqueta d’un byte distingeix els formats (0x00 = f32, 0x01 = int8), i les funcions de distància desempaqueten tots dos de manera transparent — una consulta en f32 contra vectors emmagatzemats en int8 simplement funciona. El resultat és ~4× menys emmagatzematge amb una pèrdua de precisió petita i acotada.

IVF i IVF-PQ: cerca que s’estalvia la major part de les dades

La força bruta ho llegeix tot. A escala, el que vols és llegir gairebé res. Un índex IVF (inverted file) agrupa els vectors amb k-means i emmagatzema, per cada clúster, una llista de postings amb els seus membres. Una consulta només ha d’escanejar el grapat de clústers més propers — nprobe d’ells — en comptes de tota la col·lecció.

k-means particiona els vectors en cel·les; la consulta escaneja només les nprobe més properes consulta nprobe = 3 cel·les escanejades · la resta de la col·lecció ni es toca
Els centroides es trien una sola vegada amb k-means; una consulta ordena els centroides, obre les nprobe cel·les més properes i només ordena els vectors que hi ha a dins.

En crees un amb SQL, i el planificador hi encamina automàticament les consultes que hi encaixen:

CREATE VECTOR INDEX vi ON docs(emb) USING cosine LISTS 1024 PROBES 32;

-- an ORDER BY distance … LIMIT k (no WHERE) is now served by the index
SELECT id, body FROM docs
ORDER BY cosine_distance(emb, vector(0.11, 0.19, 0.31))
LIMIT 10;

LISTS és el nombre de clústers, PROBES (nprobe) és quants n’escaneja una consulta — l’únic comandament entre recall i velocitat (per defecte ceil(lists / 10)). L’índex viu al catàleg a l’esquema v9, amb el mode de re-rank afegit a v10.

Per què IVF i no HNSW

Els índexs de graf que dominen els benchmarks (HNSW) muten a cada inserció. Això és incompatible amb un fitxer versionat i copy-on-write: no pots tenir un rastre d’auditoria sobre una estructura que es reescriu constantment. Els centroides d’IVF, en canvi, són dades versionades — triats en un esdeveniment discret de CREATE o REBUILD i després llegits, no mutats a cada inserció. Les files noves s’assignen al centroide existent més proper, així que l’agrupament deriva lentament; REBUILD VECTOR INDEX el reentrena sobre les dades actuals quan el vols fresc. Canvies una mica de recall punta per un índex vectorial que viatja en el temps i es verifica com la resta de les teves dades.

Codis PQ vs re-rank int8 en línia

Per fer petits els postings, l’índex emmagatzema un codi de quantització de producte (PQ) per vector — aproximadament dim / 8 bytes, unes 8× més petit que el mateix vector en f32. Això és el que fa que tot l’índex sigui 14–21× més petit que l’HNSW de pgvector o de Qdrant. El PQ redueix el camp a una shortlist; després l’ORDER BY … LIMIT exacte reordena aquesta shortlist per guanyar precisió.

D’on llegeix els vectors el re-rank és un compromis real:

Per defecte — codis PQ posting: codi PQ (~dim/8 B) índex més petit (~8× vs f32) reordenar porta un f32 per fila — dispers shortlist → cerca puntual per candidat accés aleatori — s'encareix a escala RERANK int8 posting: vector int8 (~1 B/dim) índex ~2× més gran (tot i així ≪ pgvector) reordena en línia — sense portar la fila puntua durant l'escaneig del clúster escaneig seqüencial ANN sobre SQL viable amb 1M
PQ manté l'índex tan petit com es pot, però reordena amb lectures de fila disperses; RERANK int8 guarda una mica més per posting i reordena en línia, així que no cal cap cerca per candidat.

Tots dos modes comparteixen un sol camí de codi — la construcció, la inserció/actualització/esborrat incremental, el REBUILD i la cerca es basen tots en si hi ha llibres de codis PQ o no —, de manera que canviar de mode no bifurca el motor. Ara bé, canviar-lo sí que canvia el format dels postings, així que un índex existent s’ha de reconstruir.

Cerca híbrida: lèxica + semàntica, fusionades per rang

La cerca de text complet (BM25) troba les paraules exactes; els vectors troben significats semblants. Normalment les vols totes dues i, com que tant BM25 com el cosinus ja existeixen com a SQL, la cerca híbrida no necessita cap funcionalitat nova del motor — només una consulta. La manera robusta de combinar-les és la Reciprocal Rank Fusion (RRF), que fusiona per rang en comptes de per puntuació crua, de manera que l’escala il·limitada de BM25 i l’escala [0, 2] del cosinus no s’han de reconciliar mai:

WITH ranked AS (
  SELECT id,
    ROW_NUMBER() OVER (ORDER BY bm25(body, 'rust') DESC)                       AS lex,
    ROW_NUMBER() OVER (ORDER BY cosine_distance(emb, vector(1.0, 0.0, 0.0)) ASC) AS sem
  FROM docs
)
SELECT id FROM ranked
ORDER BY 1.0 / (60 + lex) + 1.0 / (60 + sem) DESC
LIMIT 10;
Lèxic (BM25) Semàntic (vector) Fusionat (RRF) 1 · doc B 2 · doc D 3 · doc A 1 · doc D 2 · doc C 3 · doc B 1 · doc D 2 · doc B 3 · doc C puntuació(doc) = Σ 1 / (60 + posició en cada llista) el doc D puntua bé en totes dues → guanya
RRF premia els documents que queden ben situats en diversos senyals. Un resultat fort alhora en cerca lèxica i semàntica guanya al millor de qualsevol de les dues per separat.

Un sol fitxer, tres maneres de fer una pregunta — SQL exacte, text complet i vectors — i una manera de combinar-les, tot versionat i demostrable alhora. Consulta els benchmarks per veure les xifres vectorials completes.