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ó.
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:
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;
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.