Векторные индексы
Reindexer поддерживает три типа векторных индексов: brute-force, HNSW, IVF, и три типа метрик для вычисления расстояния между векторами: L2, inner_product, cosine.
Для всех типов векторных индексов следует явно указывать метрику metric и размерность dimension.
Вставлять в индекс и искать в нём можно только векторы указанной размерности. В индекс также можно вставить пустые векторы (пустой массив или null).
Параметры конфигурации для векторных индексов описаны в соответствующих разделах:
Создание индексов и добавление в уже существующий неймспейс описаны в разделах:
KNN и range search
KNN — это поиск k наиболее близких соседних векторов. Параметры, необходимые для KNN-запроса, зависят от конкретного типа индекса.
Range search — это поиск ближайших соседних векторов, расстояние до которых не превышает порогового значения, переданного с помощью параметра radius.
Название параметра
radiusпроисходит из его геометрического смысла дляL2-метрики: он ограничивает векторы выборки сферой заданного радиуса.
Для L2-метрики отсеиваются векторы, ранги которых больше значения параметра radius. Для метрик cosine и inner product — наоборот. Также для метрик cosine и inner product значения radius могут быть отрицательными. Задание неположительного радиуса для L2-метрики не ошибочно, но бессмысленно: результат запроса заведомо будет пустым.
Во избежание путаницы при использовании
L2-метрики следует учитывать, что в целях производительности из рангов векторов не извлекаются квадратные корни, так как это не влияет на порядок выдачи. Поэтому ранги представляют собой квадраты расстояний до вектора запроса. И, хотя параметр и называетсяradius, переданное значение при поиске интерпретируется как квадрат расстояния до вектора запроса.
Общими параметрами KNN являются:
k— максимальное количество документов, возвращаемых из индекса для последующей фильтрации,radius— фильтр по рангам векторов.
Для индексов brute-force и IVF обязательно должен быть задан хотя бы один из этих параметров. Для hnsw параметры k и radius можно не указывать и вместо этого использовать потоковый KNN. При задании обоих параметров фильтрация выполняется так, чтобы удовлетворять обоим условиям.
Так как range search работает на той же алгоритмической базе, что и KNN, далее под KNN-поиском подразумевается любой из них, в том числе их комбинация.
Списки используемых при поиске параметров и их назначение приведены на страницах соответствующих индексов:
- параметры выборки из индекса brute-force,
- параметры выборки из индекса HNSW,
- параметры выборки из индекса IVF.
Особенности реализации KNN-запросов:
-
KNN-фильтр можно использовать в запросах с дополнительной фильтрацией, если условия фильтрации соединены с KNN-фильтром через операторы
ANDиAND NOT:SELECT * FROM test_ns WHERE KNN(vec_ivf, [2.4, 3.5, ...], k=100, radius=21.12) AND value > 10 AND ( id < 7 OR is_active = true ) INNER JOIN ( SELECT * FROM second_ns ) ON test_ns.id = second_ns.id LEFT JOIN ( SELECT * FROM third_ns ) ON test_ns.id = third_ns.id -
Нет возможности использовать несколько KNN-фильтров в запросе.
-
Нет возможности использовать KNN-выборку внутри joined-подзапроса.
-
KNN-условие может быть соединено операторомORтолько с полнотекстовым условием (см. гибридный поиск).
Базовые примеры KNN-запросов для конкретных индексов описаны в следующих разделах:
- Примеры KNN-запросов к brute-force-индексу,
- Примеры KNN-запросов к HNSW-индексу,
- Потоковый KNN (HNSW),
- Примеры KNN-запросов к IVF-индексу.
Выдача векторных полей
По умолчанию векторные поля исключаются из результатов всех запросов к неймспейсам, содержащим векторные индексы.
Если необходимо получить векторные поля, следует указать это явно в запросе, либо перечислив нужные поля, либо запросив выдачу всех векторных полей, используя vectors().
Следует учитывать, что получение значений векторных полей это весьма “дорогая” операция с точки зрения производительности, поэтому не рекомендуется использовать её без необходимости.
Без явного запроса векторных полей:
SELECT * FROM test_ns;
// Оба запроса эквивалентны и не возвращают значения векторных полей
db.Query("test_ns")
db.Query("test_ns").Select("*")
# Оба запроса эквивалентны и не возвращают значения векторных полей
db.new_query("test_ns")
(
db.new_query("test_ns")
.select_fields("*")
)
db.query("test_ns", Item.class);
db.query("test_ns", Item.class)
.select("*");
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/vectors_db/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_ns",
"type": "select",
"select_filter": ["*"]
}'
Результат в JSON-формате:
{"id": 0}
С запросом конкретных полей:
SELECT *, vec_bf FROM test_ns;
db.Query("test_ns").Select("*", "vec_bf")
(
db.new_query("test_ns")
.select_fields("*", "vec_bf")
)
db.query("test_ns", Item.class)
.select("*", "vec_bf");
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/vectors_db/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_ns",
"type": "select",
"select_filter": ["*", "vec_bf"]
}'
Результат в JSON-формате:
{"id": 0, "vec_bf": [0.0, 1.1, ...]}
С запросом всех векторных полей:
SELECT *, vectors() FROM test_ns;
db.Query("test_ns").Select("*", "vectors()")
db.Query("test_ns").SelectAllFields()
(
db.new_query("test_ns")
.select_fields("*", "vectors()")
)
db.query("test_ns", Item.class)
.select("*", "vectors()");
db.query("test_ns", Item.class)
.selectAllFields();
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/vectors_db/query' \
--header 'Content-Type: application/json' \
--data-raw '{
"namespace": "test_ns",
"type": "select",
"select_filter": ["*", "vectors()"]
}'
Результат в JSON-формате:
{
"id": 0,
"vec_bf": [
0.0,
1.1,
...
],
"vec_hnsw": [
1.2,
3.5,
...
],
"vec_ivf": [
5.1,
4.7,
...
]
}
Получение ранга/расстояния
По умолчанию результаты запросов с KNN сортируются по rank (для векторного поиска он соответствует расстоянию между векторами).
Для индексов с метрикой L2 — от меньшего значения к большему, а с метриками inner_product и cosine — от большего значения к меньшему.
Что согласуется с наилучшим соответствием для указанных метрик.
В режиме потокового KNN порядок выдачи следует обходу HNSW: примерно по rank(), но без строгой сортировки. ORDER BY не поддерживается.
Когда есть необходимость узнать rank каждого документа в результате запроса, его нужно запросить явно через функцию RANK() в SQL или WithRank() в GO:
- примеры
rank-запросов для brute-force, - примеры
rank-запросов для HNSW, - примеры
rank-запросов для IVF.
Также rank можно использовать в сортировке по выражению. Подробно описано в разделе сортировка и ограничение выдачи.
Индексация массивов с векторами и KNN поиск по ним
Векторный индекс может индексировать не только одиночные векторы, но и массивы с векторами. Для этого требуется создать индекс is_array: true (либо, в случае Go, просто проиндексировать двумерный слайс или слайс массивов):
// Вариант с добавлением индекса через теги
type Item struct {
Id int `reindex:"id,,pk"`
// Для слайсов из слайсов требуется явно задать тег `dimension`
VecHnsw [][]float32 `reindex:"hnsw_idx_1,hnsw,m=16,dimension=1024,metric=inner_product"`
// Для слайсов из массивов в качестве размерности вектора будет использована размерность массива
VecHnswBF [][256]float32 `reindex:"bf_idx,bf,metric=cosine"`
}
// Вариант с добавлением индекса явно через метод AddIndex
vecOpts := reindexer.FloatVectorIndexOpts{
Metric: "l2",
Dimension: 1024,
M: 16,
EfConstruction: 200,
MultithreadingMode: 1,
}
indexDef := reindexer.IndexDef{
Name: "hnsw_idx",
JSONPaths: []string{"VecHnsw"},
IndexType: "hnsw",
FieldType: "float_vector",
Config: vecOpts,
IsArray: true,
}
err := DB.AddIndex("ns_name", indexDef)
if err != nil {
panic(err)
}
index_definition = {
"name": "hnsw_idx",
"json_paths": ["VecHnsw"],
"field_type": "float_vector",
"index_type": "hnsw",
"is_array": True,
"config": {
"metric": "l2",
"dimension": 1024,
"m": 16,
"ef_construction": 200,
"multithreading": 1
}
}
db.index_add("ns_name", index_definition)
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/vectors_db/namespaces/test_ns/indexes' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "hnsw_idx",
"json_paths": ["VecHnsw"],
"field_type": "float_vector",
"index_type": "hnsw",
"is_array": true,
"config": {
"dimension": 1024,
"metric": "inner_product",
"start_size": 1000,
"m": 16,
"ef_construction": 20,
"multithreading": 1
}
}'
Массив с векторами может быть проиндексирован при помощи любого из доступных векторных индексов. Поисковые запросы для векторных индексов-массивов полностью идентичны запросам по обычным векторным индексам.
В получившуюся индексную структуру каждый вектор массива попадает по отдельности, а KNN поиск выполняется по всем векторам, содержащимся в индексе, вне зависимости от того, к каким документам они принадлежат.
Конечный результат поиска дедублицируется по внутренним id документов. При этом в качестве значения финального ранга выбирается наилучшее из всех, относящихся к конкретному документу.
Как следствие, KNN-запрос с параметром k может вернуть меньше документов, чем было указано в k.
Например, если неймспейс с векторным индексом fv с metric=L2 содержит следующие документы:
{"id": 0, "fv": [[1.0, 1.0], [1.1, 1.0], [1.0, 1.1]]}
{"id": 1, "fv": [[2.0, 0.0]]}
{"id": 2, "fv": [[3.0, 0.0]]}
то результат следующего запроса
SELECT * FROM test_ns WHERE KNN(fv, [1.0, 1.0], k=3)
будет содержать единственный документ, поскольку все 3 наиболее подходящих вектора относятся к этому документу:
{"id": 0, "fv": [[1.0, 1.0], [1.1, 1.0], [1.0, 1.1]]}
Дополнительные action-команды
Эти команды могут быть использованы при помощи их вставки через upsert в #config-неймспейс.
Удаление дискового кеша для ANN-индексов
Формат команды в JSON:
{
"type": "action",
"action": {
"command": "drop_ann_storage_cache",
"namespace": "*",
"index": "*"
}
}
Пример использования через reindexer_tool:
reindexer_tool \
--dsn cproto://127.0.0.1:6534/vectors_db \
-c '\upsert #config {"type":"action","action":{"command":"drop_ann_storage_cache", "namespace":"*", "index":"*"}}'
Команда может быть полезна для случаев, когда требуется форсировать пересоздание дискового кеша для ANN-индексов, либо полностью его отключить (используя совместно с переменной среды RX_DISABLE_ANN_CACHE).
namespace— определяет целевой неймспейс (*— применяет команду ко всем неймспейсам);index— определяет целевой индекс (*— применяет команду ко всем подходящим индексам в неймспейсе).
Переменные среды, влияющие на работу векторных индексов
| Имя переменной | Описание |
|---|---|
| RX_DISABLE_ANN_CACHE | Если эта переменная установлена, то reindexer отключает использование дискового ANN-кеша для построения векторных индексов при загрузке данных с диска во время запуска (в общем случае отключать кеш не рекомендуется, так как это сильно снижает скорость запуска). |
| RX_TARGET_INSTRUCTIONS | Ожидает одно из значений: avx512, avx2, avx или sse. Позволяет выбрать набор векторных инструкций, используемых для функций вычисления расстояния. По умолчанию, если переменная не задана, reindexer использует «лучшие» из доступных для CPU, на котором запущен, но в некоторых случаях avx512 может оказываться менее эффективен, чем avx2/avx в многопоточном окружении. |