Ein Verzeichnis-Lookup.
Ein zusammenhängender Scan.
Gefilterter Vektorsuche ist meist ein Scatter-Gather über einen Index, der nichts über Ihren Filter weiß. Telys besitzt stattdessen das physische Layout: beim Partitionsschlüssel ist nächste-Nachbarn-wo-Schlüssel-gleich-x ein O(1)-Verzeichnis-Lookup plus ein sequenzieller Scan eines zusammenhängenden Blocks. Diese Seite beschreibt die heute ausgelieferte Runtime und anschließend die Architektur, auf die sie zusteuert — als Spec gekennzeichnet.
Die Engine bestimmt, wo jeder Vektor liegt.
Eine Collection deklariert partition_by bei der Erstellung, und das Layout folgt dem Schlüssel. Das Basissegment ist nach Partition geclustert — jeder Schlüssel wird auf einen zusammenhängenden Slice abgebildet — und aktuelle Schreibvorgänge liegen daneben in einem veränderbaren, Arrow-förmigen Delta.
Partitions-geclustert
Vektoren werden nach Partitionsschlüssel sortiert gespeichert, sodass jede Partition ein zusammenhängender Block ist. Eine bereichsbegrenzte Abfrage liest einen einzigen sequenziellen Speicherbereich, keine über einen Index verteilten Zeilen.
O(1) key → slice
Ein Verzeichnis ordnet jedem Partitionswert sein (offset, length) im Basissegment zu. Das Auflösen des Filters ist ein Dictionary-Lookup, keine Index-Traversierung.
Arrow-förmige Schreibvorgänge
add und upsert hängen hier an, jede Zeile mit einem monotonen Schreib-LSN gestempelt. Das Delta ist sofort abfragbar und wird über einen einzigen Scan-Pfad mit dem Basis-Slice vereinigt.
Was eine bereichsbegrenzte Abfrage ausführt.
col.search(qvec, where={"tenant_id": "acme"}, top_k=10)
Der Filter benennt den Partitionsschlüssel, mit dem die Collection erstellt wurde.
O(1)-Lookup: der Partitionswert wird auf (offset, length) im Basissegment aufgelöst. Keine Kandidatengenerierung, kein Graph-Einstiegspunkt.
SIMD-exaktes Scoring über einen einzigen sequenziellen Block. Recall auf diesem Pfad ist 1,0 — es ist ein Scan, keine Approximation. Gemessene p50/p95 für diesen Schritt werden auf der Benchmarks-Seite veröffentlicht, jeweils gebunden an ihr Recall-Gate und ihr System.
Zeilen mit demselben Schlüssel im Arrow-förmigen Delta werden über denselben Pfad bewertet. Ein Schreibvorgang ist für die unmittelbar folgende Abfrage sichtbar.
Treffer werden am Snapshot-LSN gefiltert: tombstoned und verdrängte Versionen tauchen nie auf. Top-k wird mit dem explain-Payload zurückgegeben.
Überdimensionierte Partitionen nehmen einen deklarierten Fork: build_ivf legt einen partitionsweisen IVF über jede Partition oberhalb des Zeilenschwellenwerts, und Abfragen werden mit einem exakten Rerank darüber geroutet — kalibriert gegen einen Recall-Floor und im Plan benannt.
Jedes Ergebnis benennt seinen Plan.
explain=True gibt den physischen Plan zurück, den die Abfrage tatsächlich genommen hat. Beim Partitionsschlüssel erhält man den zusammenhängenden Slice-Scan. Überdimensionierte Partitionen deklarieren den IVF-Fork. Filter außerhalb des Schlüssels fallen auf Scatter-Gather zurück — und der Payload erklärt warum.
hits = col.search(qvec, where={"tenant_id": "acme"}, top_k=10, explain=True)
hits["explain"]["plan"]
# on the partition key → "PartitionSliceExactF32" # one contiguous slice, exact, recall 1.0
# oversized partition → "PartitionIVFRerankF32" # per-partition IVF + exact rerank
# off-key filter → "ScatterGatherExact" # fallback — and it says why:
hits["explain"]["fallback_reason"]
# "path is not the physical partition key"Nichts wird überschrieben. Versionen werden verdrängt.
Das Schreibmodell ist append-only MVCC. upsert schreibt eine neue Version einer logischen ID und markiert die vorherige als verdrängt; delete setzt einen Tombstone an einem Delete-LSN; snapshot() fixiert eine konsistente Leseansicht; compact() faltet das Delta in die Basis und verwirft, was kein Snapshot mehr sehen kann.
col.upsert(vectors, ids=ids, metadata=metadata) # a new version; the prior one is superseded col.delete(["doc-41"]) # tombstone at a delete LSN — no in-place erase lsn = col.snapshot() # pin a consistent read view at an LSN col.compact() # fold delta into base; drop superseded rows col.build_ivf(min_rows=20000, target_recall=0.98)
| Aufruf | Funktion |
|---|---|
| hinzufügen / add_texts | Neue Zeilen einfügen. Doppelte IDs werden abgelehnt — eine zweite physische Zeile für eine logische ID erfordert bewusst upsert. |
| einfügen/aktualisieren / upsert_texts | Eine neue Version einer logischen ID schreiben; die vorherige Version wird verdrängt, nie direkt überschrieben. |
| suchen / search_text | Gefiltertes Top-k. explain=True hängt den physischen Plan an das Ergebnis. |
| delete | Logische Zeilen an einem Delete-LSN mit einem Tombstone versehen. |
| compact | Das Delta in das Basissegment falten; tombstoned und verdrängte Versionen verwerfen. |
| build_ivf | Partitionsweisen IVF über Partitionen oberhalb des Zeilenschwellenwerts aufbauen, kalibriert auf einen Recall-Floor. |
| snapshot | Einen LSN zurückgeben, der eine konsistente Leseansicht fixiert. |
| save / stats | Die Collection auf Disk persistieren; Zeilenzahlen, Partitionen und Layout-Fakten ausgeben. |
Alles Folgende ist Spezifikation, keine ausgelieferte API-Referenz. Es ist das Substrat, auf das die Runtime hinkonvergiert. Die SDK-seitige Naht ist eingefroren, sodass der Austausch darunter für Aufrufer unsichtbar bleibt.
Ein Planner. Ein flaches IR. Ein Executor.
Drei Front-Ends werden in einen Planner abgesenkt, der eine flache physische Zwischendarstellung ausgibt, die von einem Mojo-vektorisierten Executor direkt über Arrow-Buffern ausgeführt wird.
Das Speichermodell (remember · recall · as_of), die Abfrage-API (point_get · scan · search · hybrid_search) und das Fabric. Ein einziger Eingangsvertrag; kein API-spezifischer Engine.
Logische und physische Planung an einem Ort. Selektivität wird aus Segmentstatistiken geschätzt; der Plan ist explizit und wird mit dem Ergebnis zurückgegeben.
Ein flaches Array von Operatoren, gebunden durch Integer-Slots. Das Inter-Operator-Register ist ein Row-Set — eine Bitmap, sortierte Row-IDs oder ein Selektionsvektor.
Vektorisierte Ausführung über Arrow-Buffern und FAISS-Kandidaten-Arrays. Der SIMD-Hot-Path bleibt außerhalb von Python; komprimierte Parquet-Seiten werden zuerst in Scratch-Buffer dekodiert.
Die Mutations-Schicht
Ein Write-Ahead-Log für Dauerhaftigkeit und Ordnung sowie ein veränderbares Arrow-förmiges Delta für sofortige Sichtbarkeit — die Tabellenschicht, die Parquet allein fehlt.
Versiegelt, unveränderlich
Dauerhaft komprimierte Spaltensegmente. Footer-Statistiken steuern Segment- und Row-Group-Pruning; versiegelte Dateien werden nie verändert.
FAISS ANN
Segmentweise FAISS-Indizes, an Prüfsumme und Version des Segments gebunden, read-only gemappt mit einer Lebensdauer, die an den Lese-Snapshot gekoppelt ist.
Pruning und Lexik
Zone-Maps, Bloom-Filter und Bitmaps für Pruning; ein BM25-Sidecar für Sparse-Retrieval und Score-Fusion.
SCAN FILTER PROJECT POINT_GET RANGE_GET AGGREGATE TOP_K HASH_JOIN HASH_GROUP_BY SORT ANN_SEARCH SPARSE_SEARCH FUSE RERANK MATERIALIZE
WAL zuerst. Sofort sichtbar. Im Hintergrund versiegelt.
Append-only-Frames mit Prüfsummen und monotonen LSNs. Die Wiederherstellung spielt Datensätze nach dem letzten versiegelten LSN erneut ab und bricht bei der ersten fehlerhaften Prüfsumme ab.
Der Schreibvorgang landet im veränderbaren Delta und ist sofort abfragbar — versiegelte Segmente und Delta vereinigen sich über einen einzigen Scan-Pfad.
Ab einem Schwellenwert für Größe, Zeilenanzahl oder Alter wird das Delta sortiert und als Parquet-Segment mit zugehöriger .vidx und Sidecars geschrieben, dann per atomarem Manifest-Swap veröffentlicht.
Hintergrund-Merges fassen kleine Segmente zusammen, entfernen tombstoned Rows und bauen Sidecars neu auf — budgetgesteuert, damit On-Device-Deployments ruhig bleiben.
Versiegelte Segmente werden nie verändert. Ein Lesevorgang pinnt (Manifest-Snapshot, LSN); Writer versiegeln neue Segmente und tauschen das Manifest, ohne laufende Lesevorgänge zu stören. Die Garbage Collection wartet auf den ältesten aktiven Snapshot.
Der Filter wählt den Plan — nicht umgekehrt.
Gefiltertes ANN ist adaptive Planung nach Selektivität: Der Planner schätzt anhand von Segmentstatistiken, wie viele Rows den Filter überleben, und wählt dann die günstigste Strategie, die den Recall-Vertrag einhält.
Vorfilter → exakt
Zuerst skalarer oder Bitmap-Prefilter, dann exaktes SIMD-Scoring über die verbleibenden Rows. Unterhalb eines Zeilenschwellenwerts wird ANN vollständig übersprungen — exakter Scan, Recall 1,0.
Allow-Bitmap-Traversal
Die ANN-Traversierung trägt ein Allow-Bitmap, sodass der Index nur Rows liefert, die der Filter zulässt.
ANN → Nachfilter
Zuerst ANN mit adaptivem Over-Fetch, dann Post-Filter. Der Filter entfernt wenig, daher führt die Kandidatengenerierung.
Korrektheitsprüfungen gehen jeder Geschwindigkeitszahl voraus.
Die ausgelieferte Runtime wird durch Paritäts-Suites verifiziert, die gegen beide Engine-Implementierungen laufen, und jede Abfrage kann den physischen Plan benennen, den sie genommen hat. Das ist die Reihenfolge hier: zuerst Korrektheitsprüfungen, dann Messung.
Benchmarks werden unter einem Fairness-Vertrag veröffentlicht — angeglichener Recall, angeglichene Hardware, transport-separierte Ergebnisse, Sieg / Unentschieden / Niederlage ausgewiesen. Die ersten gemessenen Ergebnisse sind jetzt live auf der Benchmarks-Seite — jede Zahl mit ihren vollständigen Bedingungen: Hardware, Datensatz, Dimension, Selektivität, Recall und Transport. Jeder Multiplikator bleibt eine Hypothese außerhalb der angegebenen Bedingungen.