Le mécanisme · 0.1.0b4

Une seule résolution de répertoire.
Un seul parcours contigu.

La recherche vectorielle filtrée est généralement une dispersion-collecte sur un index qui ignore votre filtre. Telys contrôle la disposition physique à la place : sur la clé de partition, les voisins-les-plus-proches-où-clé-égale-x représentent une résolution de répertoire en O(1) suivie d'un seul parcours séquentiel d'un bloc contigu. Cette page décrit le runtime livré aujourd'hui, puis l'architecture vers laquelle il converge — identifiée comme spec.

Panel 01 · Aujourd'hui — le runtime livré
01 Disposition physique

Le moteur contrôle l'emplacement de chaque vecteur.

Une collection déclare partition_by à la création, et la disposition suit la clé. Le segment de base est ordonné par partition — chaque clé correspond à une tranche contiguë — et les écritures récentes se trouvent à côté dans un delta mutable de forme Arrow.

Segment de base

Ordonné par partition

Vecteurs stockés triés par clé de partition, de sorte que chaque partition forme un bloc contigu. Une requête délimitée lit une seule séquence mémoire, non des lignes dispersées dans un index.

Répertoire de partitions

O(1) clé → tranche

Un répertoire associe chaque valeur de partition à son (offset, length) dans le segment de base. La résolution du filtre est une recherche dans un dictionnaire, non un parcours d'index.

Delta mutable

Écritures de forme Arrow

add et upsert s'ajoutent ici, chaque ligne estampillée d'un LSN d'écriture monotone. Le delta est interrogeable immédiatement et s'unit à la tranche de base via un seul chemin de parcours.

02 Le chemin de lecture

Ce qu'exécute une requête délimitée.

Requête

col.search(qvec, where={"tenant_id": "acme"}, top_k=10)

Le filtre désigne la clé de partition avec laquelle la collection a été créée.

Répertoire de partitions

Résolution en O(1) : la valeur de partition est résolue en (offset, length) dans le segment de base. Aucune génération de candidats, aucun point d'entrée de graphe.

Parcours de tranche contiguë

Scoring exact SIMD sur un seul bloc séquentiel. Le rappel sur ce chemin est de 1,0 — c'est un parcours, non une approximation. Les p50/p95 mesurés pour cette étape sont publiés sur la page des benchmarks, chacun lié à son seuil de recall et à son rig.

Union delta

Les lignes partageant la même clé dans le delta de forme Arrow sont scorées par le même chemin. Une écriture est visible dès la requête suivante.

Visibilité MVCC

Les hits sont filtrés au LSN du snapshot : les versions tombstonées et supersédées ne remontent jamais. Le top-k est retourné avec le payload explain attaché.

Les partitions surdimensionnées empruntent un fork déclaré : build_ivf place un IVF par partition sur toute partition dépassant le seuil de lignes, et les requêtes y sont routées avec un rerank exact — calibré contre un plancher de rappel, et nommé dans le plan.

03 Le fork explain

Chaque résultat nomme son plan.

explain=True retourne le plan physique effectivement emprunté par la requête. Sur la clé de partition, vous obtenez le scan par tranche contiguë. Les partitions surdimensionnées déclarent le fork IVF. Les filtres hors clé se rabattent sur le scatter-gather — et le payload indique pourquoi.

explain
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"
04 Écritures et temps

Rien n'est écrasé. Les versions sont supersédées.

Le modèle d'écriture est un MVCC append-only. upsert écrit une nouvelle version d'un id logique et marque la précédente comme supersédée ; delete place un tombstone à un LSN de suppression ; snapshot() ancre une vue de lecture cohérente ; compact() fusionne le delta dans la base et supprime ce qu'aucun snapshot ne peut voir.

python
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)
AppelRôle
ajouter / ajouter_textesInsère de nouvelles lignes. Les ids en double sont rejetés — une deuxième ligne physique pour un id logique requiert upsert, délibérément.
insérer / insérer_textesÉcrit une nouvelle version d'un id logique ; la version précédente est supersédée, jamais écrasée en place.
rechercher / rechercher_texteTop-k filtré. explain=True attache le plan physique au résultat.
deletePose un tombstone sur les lignes logiques à un LSN de suppression.
compactFusionne le delta dans le segment de base ; supprime les versions tombstonées et supersédées.
build_ivfConstruit un IVF par partition sur les partitions dépassant le seuil de lignes, calibré sur un plancher de rappel.
snapshotRetourne un LSN qui ancre une vue de lecture cohérente.
save / statsPersiste la collection sur disque ; rapporte le nombre de lignes, les partitions et les caractéristiques de disposition.
Panel 02 · Direction — la spec d'architecture

Tout ce qui suit est une spécification, non une référence d'API livrée. C'est le substrat vers lequel le runtime converge. La couture côté SDK est figée : la substitution sous-jacente est invisible pour les appelants.

05 Le substrat cible

Un planificateur. Un IR plat. Un exécuteur.

Trois front ends se réduisent en un seul planificateur, qui émet une représentation intermédiaire physique plate unique, exécutée par un exécuteur vectorisé Mojo directement sur des buffers Arrow.

Front ends

Le modèle mémoire (remember · recall · as_of), l'API de requête (point_get · scan · search · hybrid_search) et le fabric. Un contrat d'entrée unique ; aucun moteur par API.

Un planificateur

Planification logique et physique en un seul endroit. La sélectivité est estimée à partir des statistiques de segment ; le plan est explicite et retourné avec le résultat.

Un IR plat

Un tableau plat d'opérateurs liés par des slots entiers. Le registre inter-opérateurs est un ensemble de lignes — un bitmap, des row-ids triés ou un vecteur de sélection.

Un exécuteur Mojo

Exécution vectorisée sur des buffers Arrow et des tableaux de candidats FAISS. Le chemin chaud SIMD reste hors de Python ; les pages Parquet compressées sont d'abord décodées dans des buffers temporaires.

Arrow delta + WAL

La couche de mutation

Un write-ahead log pour la durabilité et l'ordonnancement, et un delta mutable de forme Arrow pour une visibilité immédiate — la couche table que Parquet ne fournit pas seul.

Segments Parquet

Scellés, immuables

Segments colonaires compressés et durables. Les statistiques de footer pilotent l'élagage des segments et des row-groups ; les fichiers scellés ne sont jamais mutés.

Sidecars .vidx

FAISS ANN

Index FAISS par segment, liés au checksum et à la version du segment, mappés en lecture seule avec une durée de vie attachée au snapshot de lecture.

Sidecars scalaires + sparse

Élagage et lexical

Zone maps, filtres de Bloom et bitmaps pour l'élagage ; un sidecar BM25 pour la récupération sparse et la fusion de scores.

IR plat — ensemble d'opérateurs
SCAN  FILTER  PROJECT  POINT_GET  RANGE_GET  AGGREGATE  TOP_K
HASH_JOIN  HASH_GROUP_BY  SORT
ANN_SEARCH  SPARSE_SEARCH  FUSE  RERANK  MATERIALIZE
06 Le chemin d'écriture

WAL en premier. Visible immédiatement. Scellé en arrière-plan.

Ajout WAL

Trames append-only avec checksums et LSNs monotones. La récupération rejoue les enregistrements au-delà du dernier LSN scellé et tronque au premier checksum invalide.

Arrow delta

L'écriture atterrit dans le delta mutable et est immédiatement interrogeable — segments scellés et delta s'unissent via un seul chemin de scan.

Scellement en arrière-plan

Passé un seuil de taille, de lignes ou d'ancienneté, le delta est trié et écrit comme segment Parquet avec son .vidx et ses sidecars, puis publié par un échange atomique de manifest.

Compaction

Les fusions en arrière-plan regroupent les petits segments, suppriment les lignes marquées tombstone et reconstruisent les sidecars — à budget contrôlé, pour que les déploiements on-device restent discrets.

Les segments scellés ne sont jamais mutés. Une lecture épingle (snapshot de manifest, LSN) ; les writers scellent de nouveaux segments et échangent le manifest sans perturber les lectures en cours. Le garbage collection attend le snapshot live le plus ancien.

07 ANN filtré comme planification

Le filtre choisit le plan, pas l'inverse.

L'ANN filtré est une planification adaptative par sélectivité : le planificateur estime le nombre de lignes survivant au filtre à partir des statistiques de segment, puis choisit la stratégie la moins coûteuse qui respecte le contrat de rappel.

Sélectif

Préfiltre → exact

Prefilter scalaire ou bitmap d'abord, puis scoring SIMD exact sur les survivants. En dessous d'un seuil de lignes, l'ANN est entièrement ignoré — scan exact, rappel 1,0.

Modéré

Parcours par allow-bitmap

La traversée ANN porte un allow-bitmap, de sorte que l'index ne remonte que les lignes admises par le filtre.

Large

ANN → post-filtre

ANN en premier avec sur-fetch adaptatif, puis post-filter. Le filtre élimine peu, donc la génération de candidats mène.

08 Statut

Les contrôles de correction précèdent tout chiffre de performance.

Le runtime livré est vérifié par des suites de parité exécutées contre les deux implémentations du moteur, et chaque requête peut nommer le plan physique emprunté. Tel est l'ordre des opérations ici : correction d'abord, mesure ensuite.

Les benchmarks sont publiés sous un contrat d'équité — rappel apparié, matériel apparié, résultats séparés par transport, victoire / égalité / défaite rapportés. Les premiers résultats mesurés sont désormais disponibles sur la page des benchmarks, chaque chiffre accompagné de ses conditions complètes : matériel, jeu de données, dimension, sélectivité, recall et transport. Tout multiplicateur reste une hypothèse en dehors des conditions énoncées à ses côtés.

Lire la méthodologie des benchmarksVoir la surface produit