Один поиск в директории.
Один непрерывный скан.
Фильтрованный векторный поиск обычно представляет собой scatter-gather по индексу, который ничего не знает о вашем фильтре. Telys управляет физическим размещением напрямую: по ключу партиции поиск ближайших соседей с условием key=x — это O(1)-поиск в директории плюс один последовательный скан непрерывного блока. Эта страница описывает рантайм, поставляемый сегодня, а затем архитектуру, к которой он движется, — с пометкой «спецификация».
Движок управляет расположением каждого вектора.
Коллекция объявляет partition_by при создании, и размещение следует ключу. Базовый сегмент кластеризован по партиции — каждый ключ отображается на один непрерывный срез — а свежие записи хранятся рядом в изменяемой дельте формата Arrow.
Кластеризован по партиции
Векторы хранятся отсортированными по ключу партиции, так что каждая партиция — один непрерывный блок. Запрос с ограничением читает один последовательный участок памяти, а не строки, разбросанные по индексу.
O(1) ключ → срез
Директория отображает каждое значение партиции на его (offset, length) в базовом сегменте. Разрешение фильтра — это поиск в словаре, а не обход индекса.
Записи в формате Arrow
add и upsert добавляют строки сюда, каждая помечена монотонным LSN записи. Дельта доступна для запросов немедленно и объединяется с базовым срезом через единый путь сканирования.
Что выполняет один ограниченный запрос.
col.search(qvec, where={"tenant_id": "acme"}, top_k=10)
Фильтр указывает ключ партиции, с которым была создана коллекция.
O(1)-поиск: значение партиции разрешается в (offset, length) базового сегмента. Никакой генерации кандидатов, никакой точки входа в граф.
SIMD-точное скорирование по одному последовательному блоку. Recall на этом пути равен 1.0 — это скан, а не приближение. Измеренные p50/p95 для этого шага публикуются на странице бенчмарков, каждый привязан к своему порогу recall и стенду.
Строки с одним ключом в Arrow-образной дельте проходят оценку по тому же пути. Запись видна уже следующему запросу.
Фильтр применяется по LSN снимка: удалённые и вытесненные версии не всплывают. Top-k возвращается с прикреплённой полезной нагрузкой explain.
Для слишком больших партиций предусмотрена явная развилка: build_ivf строит пословный IVF над каждой партицией, превышающей пороговое число строк, а запросы маршрутизируются через него с точным переранжированием — откалиброванным по нижней границе полноты и указанным в плане.
Каждый результат называет свой план.
explain=True возвращает физический план, по которому фактически выполнился запрос. По ключу партиции — сканирование непрерывного среза. Слишком большие партиции объявляют развилку IVF. Фильтры не по ключу откатываются к scatter-gather — и полезная нагрузка объясняет почему.
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"Ничто не перезаписывается. Версии вытесняются.
Модель записи — append-only MVCC. upsert записывает новую версию логического id и помечает предыдущую вытесненной; delete размещает tombstone с LSN удаления; snapshot() фиксирует согласованное представление для чтения; compact() сворачивает дельту в базовый сегмент и удаляет всё, что ни один снимок уже не видит.
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)
| Вызов | Что делает |
|---|---|
| добавить / добавить_тексты | Вставка новых строк. Дублирующиеся id отклоняются — вторая физическая строка для одного логического id требует upsert намеренно. |
| обновить / обновить_тексты | Записывает новую версию логического id; предыдущая версия вытесняется, но не перезаписывается на месте. |
| поиск / поиск_текста | Фильтрованный top-k. explain=True прикрепляет физический план к результату. |
| delete | Помечает логические строки tombstone с LSN удаления. |
| compact | Сворачивает дельту в базовый сегмент; удаляет tombstone-строки и вытесненные версии. |
| build_ivf | Строит пословный IVF над партициями, превышающими пороговое число строк, откалиброванный по нижней границе полноты. |
| snapshot | Возвращает LSN, фиксирующий согласованное представление для чтения. |
| save / stats | Сохраняет коллекцию на диск; выводит число строк, партиций и сведения о структуре. |
Всё нижеследующее — спецификация, а не справочник по выпущенному API. Это субстрат, к которому сходится среда выполнения. Шов на стороне SDK заморожен, поэтому замена под ним невидима для вызывающих.
Один планировщик. Одно плоское IR. Один исполнитель.
Три фронтенда опускаются в один планировщик, который выдаёт одно плоское физическое промежуточное представление, исполняемое одним векторизованным исполнителем Mojo непосредственно над Arrow-буферами.
Модель памяти (remember · recall · as_of), API запросов (point_get · scan · search · hybrid_search) и фабрика. Единый входной контракт; никаких отдельных движков на API.
Логическое и физическое планирование в одном месте. Селективность оценивается по статистике сегментов; план явный и возвращается вместе с результатом.
Плоский массив операторов, связанных целочисленными слотами. Межоператорный регистр — это набор строк: битовая карта, отсортированные row-id или вектор выборки.
Векторизованное выполнение над Arrow-буферами и массивами кандидатов FAISS. Горячий путь SIMD остаётся вне Python; сжатые страницы Parquet сначала декодируются в промежуточные буферы.
Слой мутаций
Журнал упреждающей записи для надёжности и упорядочивания и изменяемая Arrow-образная дельта для немедленной видимости — табличный слой, которого Parquet лишён сам по себе.
Запечатанные, неизменяемые
Надёжные сжатые столбцовые сегменты. Статистика футера управляет отсечением сегментов и групп строк; запечатанные файлы никогда не изменяются.
FAISS ANN
Пословные индексы FAISS, привязанные к контрольной сумме и версии сегмента, отображённые только для чтения с временем жизни, привязанным к снимку чтения.
Отсечение и лексический поиск
Zone maps, bloom-фильтры и битовые карты для отсечения; BM25-sidecar для разреженного поиска и слияния оценок.
SCAN FILTER PROJECT POINT_GET RANGE_GET AGGREGATE TOP_K HASH_JOIN HASH_GROUP_BY SORT ANN_SEARCH SPARSE_SEARCH FUSE RERANK MATERIALIZE
Сначала WAL. Видимость немедленная. Запечатывание в фоне.
Фреймы только для добавления с контрольными суммами и монотонными LSN. Восстановление воспроизводит записи после последнего запечатанного LSN и усекает на первой неверной контрольной сумме.
Запись попадает в изменяемую дельту и сразу доступна для запросов — запечатанные сегменты и дельта объединяются через единый путь сканирования.
По достижении порогового значения размера, числа строк или возраста дельта сортируется и записывается как сегмент Parquet с файлами .vidx и sidecar, после чего публикуется атомарной заменой манифеста.
Фоновые слияния объединяют малые сегменты, удаляют помеченные на удаление строки и перестраивают sidecar-файлы — с контролем бюджета, чтобы on-device развёртывания оставались незаметными.
Запечатанные сегменты никогда не изменяются. Чтение фиксирует снимок манифеста и LSN; писатели запечатывают новые сегменты и заменяют манифест, не затрагивая активные операции чтения. Сборка мусора ожидает завершения самого старого живого снимка.
Фильтр выбирает план, а не наоборот.
Filtered ANN — это адаптивное планирование по селективности: планировщик оценивает, сколько строк проходит фильтр по статистике сегментов, и выбирает наиболее дешёвую стратегию, удовлетворяющую контракту на полноту выборки.
Предфильтр → точный
Сначала скалярный или bitmap-префильтр, затем точное SIMD-скоринг по прошедшим строкам. Ниже порогового числа строк ANN пропускается полностью — точное сканирование, recall 1.0.
Обход по allow-bitmap
Обход ANN несёт allow-bitmap, поэтому индекс возвращает только строки, допущенные фильтром.
ANN → постфильтр
Сначала ANN с адаптивной избыточной выборкой, затем пост-фильтрация. Фильтр отсекает немного, поэтому генерация кандидатов идёт первой.
Проверка корректности предшествует любым показателям скорости.
Поставляемый рантайм верифицируется наборами тестов паритета, запускаемых против обеих реализаций движка, и каждый запрос может назвать физический план, по которому он был выполнен. Таков порядок операций: сначала проверка корректности, затем измерения.
Бенчмарки публикуются в рамках контракта честности — согласованный recall, согласованное железо, результаты с разделением по транспорту, с указанием победы / ничьей / поражения. Первые измеренные результаты уже доступны на странице бенчмарков: каждая цифра сопровождается полными условиями — железо, датасет, размерность, селективность, recall и транспорт. Любой множитель остаётся гипотезой за пределами указанных рядом с ним условий.