> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> Документация по точному и приближённому векторному поиску

# Точный и приближённый векторный поиск

Задача поиска N ближайших точек в многомерном (векторном) пространстве для заданной точки известна как [поиск ближайших соседей](https://en.wikipedia.org/wiki/Nearest_neighbor_search) или, кратко, векторный поиск.
Существуют два основных подхода к решению задачи векторного поиска:

* Точный векторный поиск вычисляет расстояние между заданной точкой и всеми точками векторного пространства. Это обеспечивает максимально возможную точность, то есть возвращённые точки гарантированно являются истинными ближайшими соседями. Поскольку векторное пространство просматривается полностью, точный векторный поиск может быть слишком медленным для практического применения.
* Приближённый векторный поиск — это группа методов (например, специальные структуры данных, такие как графы и случайные леса), которые позволяют получать результаты гораздо быстрее, чем точный векторный поиск. Точность результата обычно является "достаточно хорошей" для практического использования. Многие приближённые методы предоставляют параметры для настройки компромисса между точностью результата и временем поиска.

Векторный поиск (точный или приближённый) можно записать в SQL следующим образом:

```sql theme={null}
WITH [...] AS reference_vector
SELECT [...]
FROM table
WHERE [...] -- предложение WHERE необязательно
ORDER BY <DistanceFunction>(vectors, reference_vector)
LIMIT <N>
```

Точки в векторном пространстве хранятся в столбце `vectors` с типом Array, например [Array(Float64)](/docs/ru/reference/data-types/array), [Array(Float32)](/docs/ru/reference/data-types/array) или [Array(BFloat16)](/docs/ru/reference/data-types/array).
Опорный вектор — это константный массив, заданный в виде общего табличного выражения.
`<DistanceFunction>` вычисляет расстояние между опорной точкой и всеми сохранёнными точками.
Для этого можно использовать любую из доступных [функций расстояния](/docs/ru/reference/functions/regular-functions/distance-functions).
`<N>` указывает, сколько соседей нужно вернуть.

<div id="exact-nearest-neighbor-search">
  ## Точный векторный поиск
</div>

Точный векторный поиск можно выполнить, используя приведённый выше запрос SELECT без каких-либо изменений.
Время выполнения таких запросов, как правило, пропорционально количеству сохранённых векторов и их размерности, то есть числу элементов массива.
Кроме того, поскольку ClickHouse выполняет полный перебор всех векторов, время выполнения также зависит от числа потоков, используемых запросом (см. настройку [max\_threads](/docs/ru/reference/settings/session-settings#max_threads)).

<div id="exact-nearest-neighbor-search-example">
  ### Пример
</div>

```sql theme={null}
CREATE TABLE tab(id Int32, vec Array(Float32)) ENGINE = MergeTree ORDER BY id;

INSERT INTO tab VALUES (0, [1.0, 0.0]), (1, [1.1, 0.0]), (2, [1.2, 0.0]), (3, [1.3, 0.0]), (4, [1.4, 0.0]), (5, [1.5, 0.0]), (6, [0.0, 2.0]), (7, [0.0, 2.1]), (8, [0.0, 2.2]), (9, [0.0, 2.3]), (10, [0.0, 2.4]), (11, [0.0, 2.5]);

WITH [0., 2.] AS reference_vec
SELECT id, vec
FROM tab
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 3;
```

возвращает

```result theme={null}
   ┌─id─┬─vec─────┐
1. │  6 │ [0,2]   │
2. │  7 │ [0,2.1] │
3. │  8 │ [0,2.2] │
   └────┴─────────┘
```

<div id="approximate-nearest-neighbor-search">
  ## Приближённый векторный поиск
</div>

<div id="vector-similarity-index">
  ### Индексы векторного сходства
</div>

ClickHouse предоставляет специальный индекс векторного сходства для выполнения приближённого векторного поиска.

<Note>
  Индексы векторного сходства доступны в ClickHouse версии 25.8 и выше.
  Если у вас возникнут проблемы, пожалуйста, создайте issue в [репозитории ClickHouse](https://github.com/clickhouse/clickhouse/issues).
</Note>

<div id="creating-a-vector-similarity-index">
  #### Создание индекса векторного сходства
</div>

Индекс векторного сходства можно создать для новой таблицы следующим образом:

```sql theme={null}
CREATE TABLE table
(
  [...],
  vectors Array(Float*),
  INDEX <index_name> vectors TYPE vector_similarity(<type>, <distance_function>, <dimensions>) [GRANULARITY <N>]
)
ENGINE = MergeTree
ORDER BY [...]
```

Или, чтобы добавить индекс векторного сходства в существующую таблицу:

```sql theme={null}
ALTER TABLE table ADD INDEX <index_name> vectors TYPE vector_similarity(<type>, <distance_function>, <dimensions>) [GRANULARITY <N>];
```

Индексы векторного сходства — это особый тип индексов пропуска данных (см. [здесь](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-data_skipping-indexes) и [здесь](/docs/ru/concepts/features/performance/skip-indexes/skipping-indexes)).
Соответственно, приведённый выше оператор `ALTER TABLE` строит индекс только для новых данных, которые будут вставлены в таблицу в будущем.
Чтобы построить индекс и для существующих данных, его нужно материализовать:

```sql theme={null}
ALTER TABLE table MATERIALIZE INDEX <index_name> SETTINGS mutations_sync = 2;
```

Функция `<distance_function>` должна принимать одно из следующих значений:

* `L2Distance` — [евклидово расстояние](https://en.wikipedia.org/wiki/Euclidean_distance), то есть длину отрезка между двумя точками в евклидовом пространстве,
* `cosineDistance` — [косинусное расстояние](https://en.wikipedia.org/wiki/Cosine_similarity#Cosine_distance), то есть угол между двумя ненулевыми векторами, или
* `dotProduct` — [скалярное произведение](https://en.wikipedia.org/wiki/Dot_product) (внутреннее произведение), то есть сумму попарных произведений элементов двух векторов. Для нормализованных данных эквивалентно `cosineDistance`.

Для нормализованных данных `L2Distance` обычно является оптимальным выбором; в противном случае рекомендуется `cosineDistance`, чтобы компенсировать различия в масштабе.

<Note>
  Для функций расстояния `L2Distance` и `cosineDistance` меньшее значение означает более высокое сходство, тогда как для `dotProduct` большее значение означает более высокое сходство.
  Поэтому векторные индексы с `L2Distance` и `cosineDistance` могут использоваться только в запросах `SELECT [...] ORDER BY [...] ASC` (`ASC` — значение по умолчанию для `ORDER BY`), тогда как векторные индексы, построенные для `dotProduct`, могут использоваться только в запросах `SELECT [...] ORDER BY [...] DESC`.
</Note>

`<dimensions>` задаёт мощность массива (число элементов) в исходном столбце.
Если ClickHouse обнаружит массив с другой мощностью во время создания индекса, индекс будет отброшен и возвращена ошибка.

Необязательный параметр GRANULARITY `<N>` задаёт размер гранул индекса (см. [здесь](/docs/ru/concepts/features/performance/skip-indexes/skipping-indexes)).
В отличие от обычных индексов пропуска данных, для которых гранулярность индекса по умолчанию равна 1, индексы векторного сходства по умолчанию используют гранулярность 100 миллионов.
Это значение позволяет гарантировать, что даже для больших частей будет внутренне построено лишь небольшое число индексов.
Мы рекомендуем изменять гранулярность индекса только опытным пользователям, которые понимают последствия своих действий (см. [ниже](#differences-to-regular-skipping-indexes)).

Индексы векторного сходства являются универсальными в том смысле, что поддерживают разные методы приближённого поиска.
Фактически используемый метод задаётся параметром `<type>`.
На данный момент доступен только метод HNSW ([научная статья](https://arxiv.org/abs/1603.09320)) — популярная современная техника приближённого векторного поиска, основанная на иерархических графах близости.
Если в качестве типа используется HNSW, пользователь при желании может указать дополнительные параметры, специфичные для HNSW:

```sql theme={null}
CREATE TABLE table
(
  [...],
  vectors Array(Float*),
  INDEX index_name vectors TYPE vector_similarity('hnsw', <distance_function>, <dimensions>[, <quantization>, <hnsw_max_connections_per_layer>, <hnsw_candidate_list_size_for_construction>]) [GRANULARITY N]
)
ENGINE = MergeTree
ORDER BY [...]
```

Доступны следующие параметры HNSW:

* `<quantization>` управляет квантованием векторов в графе близости. Возможные значения: `f64`, `f32`, `f16`, `bf16`, `i8` или `b1`. Значение по умолчанию — `bf16`. Обратите внимание, что этот параметр не влияет на представление векторов в исходном столбце.
* `<hnsw_max_connections_per_layer>` управляет числом соседей для каждого узла графа, также известным как гиперпараметр HNSW `M`. Значение по умолчанию — `32`. Значение `0` означает использование значения по умолчанию.
* `<hnsw_candidate_list_size_for_construction>` управляет размером динамического списка кандидатов при построении графа HNSW, также известным как гиперпараметр HNSW `ef_construction`. Значение по умолчанию — `128`. Значение `0` означает использование значения по умолчанию.

Значения по умолчанию для всех параметров, специфичных для HNSW, достаточно хорошо подходят для большинства сценариев использования.
Поэтому мы не рекомендуем изменять параметры, специфичные для HNSW.

Также действуют дополнительные ограничения:

* Индексы векторного сходства можно создавать только для столбцов типа [Array(Float32)](/docs/ru/reference/data-types/array), [Array(Float64)](/docs/ru/reference/data-types/array) или [Array(BFloat16)](/docs/ru/reference/data-types/array). Массивы nullable- и low-cardinality-значений с плавающей точкой, такие как `Array(Nullable(Float32))` и `Array(LowCardinality(Float32))`, не допускаются.
* Индексы векторного сходства должны создаваться для одиночных столбцов.
* Индексы векторного сходства можно создавать для вычисляемых выражений (например, `INDEX index_name arraySort(vectors) TYPE vector_similarity([...])`), но такие индексы впоследствии нельзя использовать для приближённого поиска соседей.
* Индексы векторного сходства требуют, чтобы все массивы в исходном столбце содержали по `<dimension>` элементов — это проверяется при создании индекса. Чтобы как можно раньше выявлять нарушения этого требования, пользователи могут добавить [ограничение](/docs/ru/reference/statements/create/table#constraints) для векторного столбца, например `CONSTRAINT same_length CHECK length(vectors) = 256`.
* Аналогично, значения массива в исходном столбце не должны быть пустыми (`[]`) или иметь значение по умолчанию (тоже `[]`).

**Оценка потребления хранилища и памяти**

Вектор, созданный для использования с типичной ИИ-моделью (например, большой языковой моделью, [LLM](https://en.wikipedia.org/wiki/Large_language_model)), состоит из сотен или тысяч значений с плавающей точкой.
Таким образом, одно значение вектора может занимать несколько килобайт памяти.
Пользователи, которые хотят оценить объём хранилища, необходимый для исходного векторного столбца в таблице, а также объём оперативной памяти, необходимый для индекса векторного сходства, могут использовать две приведённые ниже формулы:

Потребление хранилища векторным столбцом в таблице (в несжатом виде):

```text theme={null}
Storage consumption = Number of vectors * Dimension * Size of column data type
```

Пример для [датасета DBpedia](https://huggingface.co/datasets/KShivendu/dbpedia-entities-openai-1M):

```text theme={null}
Storage consumption = 1 million * 1536 * 4 (for Float32) = 6.1 GB
```

Индекс векторного сходства должен быть полностью загружен с диска в оперативную память для выполнения поиска.
Аналогично, векторный индекс также полностью строится в памяти, а затем сохраняется на диск.

Объём памяти, необходимый для загрузки векторного индекса:

```text theme={null}
Память для векторов в индексе (mv) = Количество векторов * Размерность * Размер квантованного типа данных
Память для графа в памяти (mg) = Количество векторов * hnsw_max_connections_per_layer * Bytes_per_node_id (= 4) * Layer_node_repetition_factor (= 2)

Потребление памяти: mv + mg
```

Пример для [датасета DBpedia](https://huggingface.co/datasets/KShivendu/dbpedia-entities-openai-1M):

```text theme={null}
Память для векторов в индексе (mv) = 1 миллион * 1536 * 2 (для BFloat16) = 3072 МБ
Память для графа в оперативной памяти (mg) = 1 миллион * 64 * 2 * 4 = 512 МБ

Потребление памяти = 3072 + 512 = 3584 МБ
```

Приведённая выше формула не учитывает дополнительную память, необходимую индексам векторного сходства для выделения служебных структур данных во время выполнения, таких как предварительно выделенные буферы и кэши.

<div id="using-a-vector-similarity-index">
  #### Использование индекса векторного сходства
</div>

<Note>
  Чтобы использовать индексы векторного сходства, параметр [compatibility](/docs/ru/reference/settings/session-settings) должен быть равен `''` (значение по умолчанию), либо `'25.1'` или выше.
</Note>

Индексы векторного сходства поддерживают запросы SELECT следующего вида:

```sql theme={null}
WITH [...] AS reference_vector
SELECT [...]
FROM table
WHERE [...] -- предложение WHERE не является обязательным
ORDER BY <DistanceFunction>(vectors, reference_vector)
LIMIT <N>
```

Оптимизатор запросов ClickHouse пытается сопоставить приведённый выше шаблон запроса и задействовать доступные индексы векторного сходства.
Запрос может использовать индекс векторного сходства только в том случае, если функция расстояния в запросе SELECT совпадает с функцией расстояния, указанной при определении индекса.

Опытные пользователи могут задать произвольное значение для параметра [hnsw\_candidate\_list\_size\_for\_search](/docs/ru/reference/settings/session-settings#hnsw_candidate_list_size_for_search) (также известного как гиперпараметр HNSW "ef\_search"), чтобы настроить размер списка кандидатов при поиске (например, `SELECT [...] SETTINGS hnsw_candidate_list_size_for_search = <value>`).
Значение по умолчанию 256 хорошо подходит для большинства сценариев использования.
Более высокие значения параметра обеспечивают лучшую точность за счёт снижения производительности.

Если запрос может использовать индекс векторного сходства, ClickHouse проверяет, что значение LIMIT `<N>`, указанное в SELECT-запросах, находится в допустимых пределах.
В частности, возвращается ошибка, если `<N>` превышает значение параметра [max\_limit\_for\_vector\_search\_queries](/docs/ru/reference/settings/session-settings#max_limit_for_vector_search_queries) со значением по умолчанию 100.
Слишком большие значения LIMIT могут замедлить поиск и, как правило, указывают на ошибку в использовании.

Чтобы проверить, использует ли запрос SELECT индекс векторного сходства, можно добавить `EXPLAIN indexes = 1` перед запросом.

В качестве примера выполним запрос

```sql theme={null}
EXPLAIN indexes = 1
WITH [0.462, 0.084, ..., -0.110] AS reference_vec
SELECT id, vec
FROM tab
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 10;
```

может вернуть

```result theme={null}
┌─explain─────────────────────────────────────────────────────────────────────────────────────────┐
 1. │ Expression (Project names)                                                                      │
 2. │   Limit (preliminary LIMIT (without OFFSET))                                                    │
 3. │     Sorting (Sorting for ORDER BY)                                                              │
 4. │       Expression ((Before ORDER BY + (Projection + Change column names to column identifiers))) │
 5. │         ReadFromMergeTree (default.tab)                                                         │
 6. │         Indexes:                                                                                │
 7. │           PrimaryKey                                                                            │
 8. │             Condition: true                                                                     │
 9. │             Parts: 1/1                                                                          │
10. │             Granules: 575/575                                                                   │
11. │           Skip                                                                                  │
12. │             Name: idx                                                                           │
13. │             Description: vector_similarity GRANULARITY 100000000                                │
14. │             Parts: 1/1                                                                          │
15. │             Granules: 10/575                                                                    │
    └─────────────────────────────────────────────────────────────────────────────────────────────────┘
```

В данном примере 1 миллион векторов из [датасета dbpedia](https://huggingface.co/datasets/KShivendu/dbpedia-entities-openai-1M), каждый с размерностью 1536, хранится в 575 гранулах, то есть по 1,7 тыс. строк на гранулу.
Запрос ищет 10 ближайших соседей, и индекс векторного сходства находит их в 10 отдельных гранулах.
Эти 10 гранул будут прочитаны при выполнении запроса.

Индексы векторного сходства используются, если вывод содержит `Skip`, а также имя и тип векторного индекса (в примере — `idx` и `vector_similarity`).
В данном случае индекс векторного сходства отсеял две из четырёх гранул, то есть 50% данных.
Чем больше гранул удаётся отсеять, тем эффективнее используется индекс.

<Tip>
  Чтобы принудительно задать использование индекса, можно выполнить запрос SELECT с настройкой [force\_data\_skipping\_indexes](/docs/ru/reference/settings/session-settings#force_data_skipping_indices) (укажите имя индекса в качестве значения настройки).
</Tip>

**Постфильтрация и префильтрация**

При необходимости можно указать секцию `WHERE` с дополнительными условиями фильтрации для запроса SELECT.
ClickHouse вычисляет эти условия фильтрации, используя стратегию постфильтрации или префильтрации.
Если кратко, обе стратегии определяют порядок применения фильтров:

* Постфильтрация означает, что сначала используется индекс векторного сходства, а затем ClickHouse применяет дополнительные фильтры, указанные в предложении `WHERE`.
* Префильтрация означает, что порядок применения фильтра становится обратным.

Каждая стратегия имеет свои компромиссы:

* У постфильтрации есть общая проблема: она может вернуть меньше строк, чем указано в секции `LIMIT <N>`. Это происходит, когда одна или несколько строк результата, возвращённых индексом векторного сходства, не проходят дополнительные фильтры.
* Префильтрация в общем случае остаётся нерешённой задачей. Некоторые специализированные векторные базы данных поддерживают алгоритмы префильтрации, но большинство реляционных баз данных (включая ClickHouse) переходят к точному поиску ближайших соседей, то есть к полному перебору без индекса.

То, какая стратегия будет использоваться, зависит от условия фильтрации.

*Дополнительные фильтры входят в ключ партиционирования*

Если условие дополнительной фильтрации входит в ключ партиционирования, ClickHouse применит отсечение партиций.
Например, таблица партиционирована по диапазонам столбца `year`, и выполняется следующий запрос:

```sql theme={null}
WITH [0., 2.] AS reference_vec
SELECT id, vec
FROM tab
WHERE year = 2025
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 3;
```

ClickHouse отсечёт все партиции, кроме партиции за 2025 год.

*Дополнительные фильтры нельзя вычислить по индексам*

Если дополнительные условия фильтрации нельзя вычислить по индексам (индексу первичного ключа, индексу пропуска данных), ClickHouse применит постфильтрацию.

*Дополнительные фильтры можно вычислить с помощью индекса первичного ключа*

Если дополнительные условия фильтрации можно вычислить с помощью [первичного ключа](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree#primary-key) (то есть они образуют префикс первичного ключа) и

* условие фильтрации отбрасывает хотя бы одну строку в пределах части, ClickHouse переключится на префильтрацию для "оставшихся" диапазонов внутри этой части,
* условие фильтрации не отбрасывает ни одной строки в пределах части, ClickHouse выполнит постфильтрацию для этой части.

На практике второй случай встречается довольно редко.

*Дополнительные фильтры можно вычислить с помощью индекса пропуска данных*

Если дополнительные условия фильтрации можно вычислить с помощью [индексов пропуска данных](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-data_skipping-indexes) (индекса minmax, индекса set и т. д.), ClickHouse выполняет постфильтрацию.
В таких случаях индекс векторного сходства вычисляется первым, поскольку ожидается, что он отсеет больше всего строк по сравнению с другими индексами пропуска данных.

Для более точного выбора между постфильтрацией и префильтрацией можно использовать два параметра:

Параметр [vector\_search\_filter\_strategy](/docs/ru/reference/settings/session-settings#vector_search_filter_strategy) (по умолчанию: `auto`, реализующий описанные выше эвристики) можно установить в значение `prefilter`.
Это полезно, если нужно принудительно включить префильтрацию в случаях, когда дополнительные условия фильтрации крайне избирательны.
Например, следующий запрос может выиграть от префильтрации:

```sql theme={null}
SELECT bookid, author, title
FROM books
WHERE price < 2.00
ORDER BY cosineDistance(book_vector, getEmbedding('Books on ancient Asian empires'))
LIMIT 10
```

Если предположить, что лишь очень небольшое число книг стоит меньше 2 долларов, постфильтрация может вернуть ноль строк, поскольку все 10 лучших совпадений, возвращённых векторным индексом, могут иметь цену выше 2 долларов.
Если принудительно включить префильтрацию (добавив `SETTINGS vector_search_filter_strategy = 'prefilter'` в запрос), ClickHouse сначала находит все книги с ценой ниже 2 долларов, а затем выполняет векторный поиск полным перебором по найденным книгам.

В качестве альтернативного способа решить описанную выше проблему параметр [vector\_search\_index\_fetch\_multiplier](/docs/ru/reference/settings/session-settings#vector_search_index_fetch_multiplier) (по умолчанию: `1.0`, максимум: `1000.0`) можно установить в значение > `1.0` (например, `2.0`).
Число ближайших соседей, извлекаемых из векторного индекса, умножается на значение этого параметра, после чего к этим строкам применяется дополнительный фильтр, чтобы вернуть число строк, соответствующее LIMIT.
Например, можно снова выполнить запрос, но с множителем `3.0`:

```sql theme={null}
SELECT bookid, author, title
FROM books
WHERE price < 2.00
ORDER BY cosineDistance(book_vector, getEmbedding('Books on ancient Asian empires'))
LIMIT 10
SETTING vector_search_index_fetch_multiplier = 3.0;
```

ClickHouse получит 3.0 x 10 = 30 ближайших соседей из векторного индекса в каждой части, а затем применит дополнительные фильтры.
Будут возвращены только десять ближайших соседей.
Отметим, что параметр `vector_search_index_fetch_multiplier` может смягчить эту проблему, но в крайних случаях (при очень селективном условии WHERE) всё же возможно, что будет возвращено менее N запрошенных строк.

**Пересчёт оценок**

Индекс пропуска данных в ClickHouse обычно фильтрует на уровне гранул, то есть поиск в индексе пропуска данных (внутренне) возвращает список потенциально подходящих гранул, что уменьшает объём читаемых данных при последующем сканировании.
В целом это хорошо работает для индексов пропуска данных, но в случае индексов векторного сходства возникает "несоответствие гранулярности".
Если говорить подробнее, индекс векторного сходства определяет номера строк N наиболее похожих векторов для заданного опорного вектора.
При настройке `vector_search_with_rescoring = 1` ClickHouse считывает исходные векторы полной точности для строк-кандидатов и вычисляет итоговое расстояние в обычном SQL-конвейере.
Когда это допускает план запроса, ClickHouse перед итоговым вычислением расстояния ограничивает сканирование строками-кандидатами, возвращёнными векторным индексом.
Этот шаг называется пересчётом оценок и может повысить точность, особенно при использовании квантизованных векторных индексов, поскольку итоговое ранжирование использует сохранённые векторы, а не расстояния из индекса.
Если дополнительные фильтры отбрасывают слишком много кандидатов или требуется более высокая полнота, увеличьте значение настройки `vector_search_index_fetch_multiplier`, чтобы векторный индекс возвращал больше строк-кандидатов для пересчёта оценок.

Поэтому ClickHouse предоставляет оптимизацию, которая отключает пересчёт оценок и возвращает наиболее похожие векторы и расстояния до них напрямую из индекса.
Эта оптимизация включена по умолчанию, см. настройку [vector\_search\_with\_rescoring](/docs/ru/reference/settings/session-settings#vector_search_with_rescoring).
В общих чертах это работает так: ClickHouse делает наиболее похожие векторы и расстояния до них доступными в виде виртуального столбца `_distance`.
Чтобы увидеть это, выполните запрос векторного поиска с `EXPLAIN header = 1`:

```sql theme={null}
EXPLAIN header = 1
WITH [0., 2.] AS reference_vec
SELECT id
FROM tab
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 3
SETTINGS vector_search_with_rescoring = 0
```

```result theme={null}
Query id: a2a9d0c8-a525-45c1-96ca-c5a11fa66f47

    ┌─explain─────────────────────────────────────────────────────────────────────────────────────────────────┐
 1. │ Expression (Project names)                                                                              │
 2. │ Header: id Int32                                                                                        │
 3. │   Limit (preliminary LIMIT (without OFFSET))                                                            │
 4. │   Header: L2Distance(__table1.vec, _CAST([0., 2.]_Array(Float64), 'Array(Float64)'_String)) Float64     │
 5. │           __table1.id Int32                                                                             │
 6. │     Sorting (Sorting for ORDER BY)                                                                      │
 7. │     Header: L2Distance(__table1.vec, _CAST([0., 2.]_Array(Float64), 'Array(Float64)'_String)) Float64   │
 8. │             __table1.id Int32                                                                           │
 9. │       Expression ((Before ORDER BY + (Projection + Change column names to column identifiers)))         │
10. │       Header: L2Distance(__table1.vec, _CAST([0., 2.]_Array(Float64), 'Array(Float64)'_String)) Float64 │
11. │               __table1.id Int32                                                                         │
12. │         ReadFromMergeTree (default.tab)                                                                 │
13. │         Header: id Int32                                                                                │
14. │                 _distance Float32                                                                       │
    └─────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```

<Note>
  Запрос, выполняемый без пересчёта оценок (`vector_search_with_rescoring = 0`) и при включенных параллельных репликах, может всё же перейти к пересчёту оценок.
</Note>

<div id="performance-tuning">
  #### Настройка производительности
</div>

**Настройка сжатия**

Практически во всех случаях векторы в исходном столбце являются плотными и плохо поддаются сжатию.
В результате [сжатие](/docs/ru/reference/statements/create/table#column_compression_codec) замедляет вставку и чтение данных из векторного столбца.
Поэтому мы рекомендуем отключить сжатие.
Для этого укажите `CODEC(NONE)` для векторного столбца следующим образом:

```sql theme={null}
CREATE TABLE tab(id Int32, vec Array(Float32) CODEC(NONE), INDEX idx vec TYPE vector_similarity('hnsw', 'L2Distance', 2)) ENGINE = MergeTree ORDER BY id;
```

**Настройка создания индекса**

Жизненный цикл индексов векторного сходства связан с жизненным циклом частей.
Иными словами, всякий раз, когда создаётся новая часть с заданным индексом векторного сходства, этот индекс тоже создаётся.
Обычно это происходит при [вставке](/docs/ru/concepts/features/operations/insert/inserting-data) данных или во время [слияний](/docs/ru/concepts/core-concepts/merges).
К сожалению, для HNSW характерно длительное создание индекса, что может существенно замедлить вставки и слияния.
В идеале индексы векторного сходства следует использовать только для неизменяемых или редко изменяемых данных.

Чтобы ускорить создание индекса, можно использовать следующие методы:

Во-первых, создание индекса можно распараллелить.
Максимальное число потоков для создания индекса настраивается с помощью настройки сервера [max\_build\_vector\_similarity\_index\_thread\_pool\_size](/docs/ru/reference/settings/server-settings/settings#max_build_vector_similarity_index_thread_pool_size).
Для оптимальной производительности значение этой настройки следует установить равным числу ядер CPU.

Во-вторых, чтобы ускорить операторы INSERT, пользователи могут отключить создание индексов пропуска данных для новых частей при вставке с помощью настройки сеанса [materialize\_skip\_indexes\_on\_insert](/docs/ru/reference/settings/session-settings#materialize_skip_indexes_on_insert).
Для SELECT-запросов к таким частям будет использоваться точный поиск.
Поскольку вставленные части обычно невелики по сравнению с общим размером таблицы, влияние на производительность, как ожидается, будет незначительным.

В-третьих, чтобы ускорить слияния, пользователи могут отключить создание индексов пропуска данных для слитых частей с помощью настройки сеанса [materialize\_skip\_indexes\_on\_merge](/docs/ru/reference/settings/merge-tree-settings#materialize_skip_indexes_on_merge).
Это в сочетании с оператором [ALTER TABLE \[...\] MATERIALIZE INDEX \[...\]](/docs/ru/reference/statements/alter/skipping-index#materialize-index) даёт явный контроль над жизненным циклом индексов векторного сходства.
Например, создание индекса можно отложить до тех пор, пока не будут приняты все данные, или до периода низкой нагрузки на систему, например до выходных.

**Настройка использования индекса**

Чтобы использовать индексы векторного сходства, SELECT-запросам нужно загрузить их в оперативную память.
Чтобы один и тот же индекс векторного сходства не загружался в оперативную память повторно, ClickHouse предоставляет специальный кэш в оперативной памяти для таких индексов.
Чем больше этот кэш, тем меньше будет лишних загрузок.
Максимальный размер кэша настраивается с помощью настройки сервера [vector\_similarity\_index\_cache\_size](/docs/ru/reference/settings/server-settings/settings#vector_similarity_index_cache_size).
По умолчанию размер кэша может достигать 5 ГБ.

Следующие сообщения лога (`system.text_log`) указывают на то, что индекс векторного сходства загружается.
Если такие сообщения повторяются для разных запросов векторного поиска, это указывает на то, что размер кэша слишком мал.

```text theme={null}
2026-02-03 07:39:10.351635 [1386] f0ac5c85-1b1c-4f35-8848-87a1d1aa00ba : VectorSimilarityIndex Start loading vector similarity index

<...>

2026-02-03 07:40:25.217603 [1386] f0ac5c85-1b1c-4f35-8848-87a1d1aa00ba : VectorSimilarityIndex Loaded vector similarity index: max_level = 2, connectivity = 64, size = 1808111, capacity = 1808111, memory_usage = 8.00 GiB, bytes_per_vector = 4096, scalar_words = 1024, nodes = 1808111, edges = 51356964, max_edges = 233395072
```

<Note>
  Кэш индекса векторного сходства хранит гранулы векторного индекса.
  Если размер отдельных гранул векторного индекса превышает размер кэша, они не будут кэшироваться.
  Поэтому обязательно вычислите размер векторного индекса (по формуле из "Оценка потребления хранилища и памяти" или [system.data\_skipping\_indices](/docs/ru/reference/system-tables/data_skipping_indices)) и соответствующим образом подберите размер кэша.
</Note>

*Еще раз подчеркнем: при анализе медленных запросов векторного поиска первым шагом должна быть проверка кэша векторного индекса и, при необходимости, увеличение его размера.*

Текущий размер кэша индекса векторного сходства приведен в [system.metrics](/docs/ru/reference/system-tables/metrics):

```sql theme={null}
SELECT metric, value
FROM system.metrics
WHERE metric = 'VectorSimilarityIndexCacheBytes'
```

Количество попаданий в кэш и промахов кэша для запроса с определённым Query id можно получить из [system.query\_log](/docs/ru/reference/system-tables/query_log):

```sql theme={null}
SYSTEM FLUSH LOGS query_log;

SELECT ProfileEvents['VectorSimilarityIndexCacheHits'], ProfileEvents['VectorSimilarityIndexCacheMisses']
FROM system.query_log
WHERE type = 'QueryFinish' AND query_id = '<...>'
ORDER BY event_time_microseconds;
```

Для использования в продакшне мы рекомендуем выделять достаточно большой кэш, чтобы все векторные индексы постоянно находились в памяти.

**Настройка квантования**

[Квантование](https://huggingface.co/blog/embedding-quantization) — это метод уменьшения объёма памяти, занимаемой векторами, а также вычислительных затрат на построение и обход векторных индексов.
Векторные индексы ClickHouse поддерживают следующие варианты квантования:

| Квантование    | Название                          | Хранилище на размерность |
| -------------- | --------------------------------- | ------------------------ |
| f32            | Одинарная точность                | 4 байта                  |
| f16            | Половинная точность               | 2 байта                  |
| bf16 (default) | Половинная точность (brain float) | 2 байта                  |
| i8             | Четвертная точность               | 1 байт                   |
| b1             | Двоичное                          | 1 бит                    |

Квантование снижает точность векторного поиска по сравнению с поиском по исходным значениям с полной точностью (`f32`) и плавающей запятой.
Однако на большинстве датасетов квантование half-precision brain float (`bf16`) даёт пренебрежимо малую потерю точности, поэтому индексы векторного сходства по умолчанию используют именно его.
Квантование с четвертной точностью (`i8`) и двоичное квантование (`b1`) приводят к заметной потере точности при векторном поиске.
Мы рекомендуем оба этих варианта только в том случае, если размер индекса векторного сходства значительно превышает доступный объём DRAM.
В этом случае мы также рекомендуем включить пересчёт оценок ([vector\_search\_index\_fetch\_multiplier](/docs/ru/reference/settings/session-settings#vector_search_index_fetch_multiplier), [vector\_search\_with\_rescoring](/docs/ru/reference/settings/session-settings#vector_search_with_rescoring)), чтобы повысить точность.
Двоичное квантование рекомендуется только 1) для нормализованных эмбеддингов (то есть длина вектора = 1; модели OpenAI обычно нормализованы) и 2) если в качестве функции расстояния используется косинусное расстояние.
При построении и поиске в графе близости двоичное квантование внутренне использует расстояние Хэмминга.
На этапе пересчёта оценок используются исходные векторы полной точности, хранящиеся в таблице, чтобы определить ближайших соседей по косинусному расстоянию.

**Настройка передачи данных**

Опорный вектор в запросе векторного поиска задаётся пользователем и обычно получается вызовом большой языковой модели (LLM).
Типичный код Python, выполняющий векторный поиск в ClickHouse, может выглядеть так

```python theme={null}
search_v = openai_client.embeddings.create(input = "[Good Books]", model='text-embedding-3-large', dimensions=1536).data[0].embedding

params = {'search_v': search_v}
result = chclient.query(
   "SELECT id FROM items
    ORDER BY cosineDistance(vector, %(search_v)s)
    LIMIT 10",
    parameters = params)
```

Эмбеддинг-векторы (`search_v` в приведённом выше фрагменте) могут иметь очень большую размерность.
Например, OpenAI предоставляет модели, которые генерируют эмбеддинг-векторы размерностью 1536 или даже 3072.
В приведённом выше коде Python-драйвер ClickHouse подставляет эмбеддинг-вектор в виде человекочитаемой строки, а затем отправляет запрос SELECT целиком как строку.
Если предположить, что эмбеддинг-вектор состоит из 1536 значений с плавающей точкой одинарной точности, длина отправляемой строки достигает 20 кБ.
Это приводит к высокой загрузке процессора из-за токенизации, разбора и тысяч преобразований строк в числа с плавающей точкой.
Кроме того, файл журнала сервера ClickHouse тоже занимает значительный объём, что также вызывает разрастание `system.query_log`.

Обратите внимание, что большинство LLM-моделей возвращают эмбеддинг-вектор в виде списка или массива NumPy из native float.
Поэтому мы рекомендуем Python-приложениям передавать параметр опорного вектора в бинарной форме, используя следующий стиль:

```python theme={null}
search_v = openai_client.embeddings.create(input = "[Good Books]", model='text-embedding-3-large', dimensions=1536).data[0].embedding

params = {'$search_v_binary$': np.array(search_v, dtype=np.float32).tobytes()}
result = chclient.query(
   "SELECT id FROM items
    ORDER BY cosineDistance(vector, reinterpret($search_v_binary$, 'Array(Float32)'))
    LIMIT 10"
    parameters = params)
```

В этом примере опорный вектор отправляется в бинарном виде как есть и на сервере интерпретируется как массив чисел с плавающей точкой.
Это экономит процессорное время на стороне сервера и позволяет избежать разрастания серверных журналов и `system.query_log`.

<div id="administration">
  #### Администрирование и мониторинг
</div>

Размер индексов векторного сходства на диске можно узнать из [system.data\_skipping\_indices](/docs/ru/reference/system-tables/data_skipping_indices):

```sql theme={null}
SELECT database, table, name, formatReadableSize(data_compressed_bytes)
FROM system.data_skipping_indices
WHERE type = 'vector_similarity';
```

Пример вывода:

```result theme={null}
┌─database─┬─table─┬─name─┬─formatReadab⋯ssed_bytes)─┐
│ default  │ tab   │ idx  │ 348.00 MB                │
└──────────┴───────┴──────┴──────────────────────────┘
```

<div id="differences-to-regular-skipping-indexes">
  #### Отличия от обычных индексов пропуска данных
</div>

Как и обычные [индексы пропуска данных](/docs/ru/concepts/features/performance/skip-indexes/skipping-indexes), индексы векторного сходства строятся по гранулам, и каждый индексируемый блок состоит из `GRANULARITY = [N]` гранул (`[N]` = 1 по умолчанию для обычных индексов пропуска данных).
Например, если гранулярность первичного индекса таблицы равна 8192 (настройка `index_granularity = 8192`) и `GRANULARITY = 2`, то каждый индексируемый блок будет содержать 16384 строки.
Однако структуры данных и алгоритмы приблизительного поиска ближайших соседей по своей природе ориентированы на строки.
Они хранят компактное представление набора строк, а также возвращают строки для запросов векторного поиска.
Из-за этого в поведении индексов векторного сходства возникают довольно неочевидные отличия по сравнению с обычными индексами пропуска данных.

Когда пользователь определяет индекс векторного сходства для столбца, ClickHouse внутренне создает «подиндекс» векторного сходства для каждого индексного блока.
Подиндекс является «локальным» в том смысле, что он знает только о строках своего индексного блока.
В предыдущем примере, если предположить, что столбец содержит 65536 строк, мы получим четыре индексных блока (охватывающих восемь гранул) и по одному подиндексу векторного сходства для каждого индексного блока.
Теоретически подиндекс способен напрямую вернуть строки с N ближайшими точками в пределах своего индексного блока.
Для запросов с `vector_search_with_rescoring = 1` ClickHouse может использовать эти позиции строк для фильтрации строк перед вычислением итогового расстояния по сохранённым векторам, когда план запроса допускает такую оптимизацию.
Без пересчёта оценок ClickHouse использует расстояния из векторного индекса напрямую через виртуальный столбец `_distance`.
В обоих режимах для планирования чтения всё равно используются окружающие диапазоны гранул, что отличается от обычных индексов пропуска данных, которые пропускают данные на уровне индексных блоков.

Параметр `GRANULARITY` определяет, сколько подиндексов векторного сходства будет создано.
Чем больше значение `GRANULARITY`, тем меньше создается подиндексов векторного сходства, но тем они крупнее, вплоть до случая, когда у столбца (или части данных столбца) имеется только один подиндекс.
В этом случае подиндекс имеет «глобальное» представление обо всех строках столбца и может напрямую вернуть все гранулы столбца (части), содержащие релевантные строки (таких гранул не более `LIMIT [N]`).
При `vector_search_with_rescoring = 1` ClickHouse затем может прочитать позиции совпадающих строк и вычислить точное расстояние для этих строк.
При небольшом значении `GRANULARITY` каждый подиндекс может вернуть до `LIMIT N` строк-кандидатов.
В результате может потребоваться прочитать больше строк-кандидатов и выполнить дополнительную постфильтрацию.
Обратите внимание, что точность поиска в обоих случаях одинакова, различается только производительность обработки.
Обычно для индексов векторного сходства рекомендуется использовать большое значение `GRANULARITY`, а к меньшим значениям `GRANULARITY` прибегать только при возникновении проблем, например чрезмерного потребления памяти структурами векторного сходства.
Если для индексов векторного сходства `GRANULARITY` не указана, по умолчанию используется значение 100 миллионов.

<div id="approximate-nearest-neighbor-search-example">
  #### Пример
</div>

Запросы:

```sql title="Query" theme={null}
CREATE TABLE tab(id Int32, vec Array(Float32), INDEX idx vec TYPE vector_similarity('hnsw', 'L2Distance', 2)) ENGINE = MergeTree ORDER BY id;

INSERT INTO tab VALUES (0, [1.0, 0.0]), (1, [1.1, 0.0]), (2, [1.2, 0.0]), (3, [1.3, 0.0]), (4, [1.4, 0.0]), (5, [1.5, 0.0]), (6, [0.0, 2.0]), (7, [0.0, 2.1]), (8, [0.0, 2.2]), (9, [0.0, 2.3]), (10, [0.0, 2.4]), (11, [0.0, 2.5]);

WITH [0., 2.] AS reference_vec
SELECT id, vec
FROM tab
ORDER BY L2Distance(vec, reference_vec) ASC
LIMIT 3;
```

```result title="Response" theme={null}
   ┌─id─┬─vec─────┐
1. │  6 │ [0,2]   │
2. │  7 │ [0,2.1] │
3. │  8 │ [0,2.2] │
   └────┴─────────┘
```

Другие демонстрационные наборы данных для приближённого векторного поиска:

* [LAION-400M](/docs/ru/get-started/sample-datasets/laion)
* [LAION-5B](/docs/ru/get-started/sample-datasets/laion5b)
* [dbpedia](/docs/ru/get-started/sample-datasets/dbpedia)
* [hackernews](/docs/ru/get-started/sample-datasets/hacker-news-vector-search)

<div id="vector-search-with-quantized-codecs">
  ### Векторный поиск с квантизованными кодеками
</div>

<Note>
  Кодек `Quantized` — экспериментальный. Включите его командой `SET allow_experimental_codecs = 1`.
  Если у вас возникнут проблемы, пожалуйста, создайте issue в [репозитории ClickHouse](https://github.com/clickhouse/clickhouse/issues).
</Note>

<div id="quantized-codecs-introduction">
  #### Введение
</div>

[Индекс векторного сходства](#vector-similarity-index) отвечает на запрос поиска ближайших соседей, выполняя обход графа, и работает очень эффективно, когда граф помещается в памяти.
Его применимость ограничивают два условия:

* **Масштаб.** Время, необходимое для построения графа, и объём памяти для его хранения — помимо самих векторов — становятся основными затратами.
* **Фильтрация.** При селективном предложении `WHERE` обход графа становится неэффективным, поскольку либо не удаётся добраться до небольшого множества строк, удовлетворяющих предикату, либо приходится проверять непропорционально большое число кандидатов, чтобы их найти.

Полное сканирование не имеет ни одного из этих ограничений: оно не требует вспомогательных структур, части объединяются конкатенацией, а фильтр просто уменьшает число строк, которые нужно просканировать.
Его единственный недостаток — объём данных, который необходимо прочитать: при сканировании векторов, хранящихся с полной точностью `Float32`, основная нагрузка приходится на I/O подсистемы хранения, поскольку с диска (или из Объектного хранилища) приходится читать весь столбец векторов — а для плотного столбца эмбеддингов это самый большой столбец в таблице, который к тому же плохо сжимается.

Кодек столбца `Quantized` решает эту проблему.
Каждый вектор хранится дважды: исходные значения полной точности, без изменений, и вместе с ними компактный *квантованный код* в отдельном потоке.
Запрос векторного поиска сначала сканирует коды с помощью недорогой функции расстояния, хорошо подходящей для SIMD, чтобы собрать shortlist наиболее перспективных кандидатов, а затем заново ранжирует этот shortlist по векторам полной точности.
Поскольку код занимает лишь часть размера исходного вектора, сканирование shortlist считывает из хранилища значительно меньше байтов — и обращается к столбцу полной точности только для небольшого числа кандидатов из shortlist — при этом итоговое ранжирование остаётся точным.

<div id="quantized-codecs-declaring">
  #### Объявление кодека
</div>

Добавьте кодек `Quantized(...)` к столбцу типа `Array(Float32)` (или `Array(Float64)` / `Array(BFloat16)`).
Кодек экспериментальный, поэтому сначала включите `allow_experimental_codecs`:

```sql theme={null}
SET allow_experimental_codecs = 1;

CREATE TABLE vectors
(
    id UInt32,
    vec Array(Float32) CODEC(Quantized('rabitq', 1536))
)
ENGINE = MergeTree ORDER BY id;
```

Данные с полной точностью хранятся как обычно; кодек лишь добавляет сопутствующий поток кодов.
Кодек задаётся при создании таблицы, и его нельзя добавить или изменить с помощью `ALTER TABLE`.

<div id="quantized-codecs-methods">
  #### Методы квантования
</div>

Каждый метод предлагает свой компромисс между размером, точностью и метрикой. Аргумент `dimensions` задаёт длину вектора.

* `Quantized('rabitq', dimensions)` — один знаковый бит на координату плюс несмещённый коэффициент коррекции косинуса (`dimensions/8 + 4` байта). Небольшой, быстрый для `popcount`, хороший вариант по умолчанию. Только `cosineDistance`.
* `Quantized('turboquant', dimensions)` — два бита на координату (1-битный код MSE и 1-битный код остатка) для кандидатов с более высокой точностью (`dimensions/4 + 4` байта). Только `cosineDistance`.
* `Quantized('int8', dimensions)` — один код `Int8` на координату плюс норма вектора (`dimensions + 4` байта); самый крупный, но и наиболее точно передающий значения плоский код. Поддерживает `L2Distance` и `cosineDistance`.
* `Quantized('prefix', dimensions, leading_dimensions, 'int8'|'bf16')` — Matryoshka: сохраняет только первые `leading_dimensions` координат в формате `Int8` (с масштабом для каждого вектора) или `BFloat16`. Очень компактные коды для эмбеддингов, обученных с использованием Matryoshka Representation Learning. Поддерживает `L2Distance` и `cosineDistance`.
* `Quantized('product', dimensions, nbits, m)` — Product Quantization: кодовая книга для каждой части, обученная методом k-means; каждый вектор преобразуется в `m` кодов по `nbits` бит (поэтому `dimensions` должно быть кратно `m`). Самый компактный вариант и максимальная полнота на байт, но требует этапа обучения во время вставки. Поддерживает `L2Distance` и `cosineDistance`.

Для `rabitq` и `turboquant` значение `dimensions` должно быть кратно 8.

<div id="quantized-codecs-searching">
  #### Прозрачный поиск
</div>

Здесь не нужен специальный синтаксис запроса — используйте тот же запрос top-`k`, что и для [точного поиска](#exact-nearest-neighbor-search):

```sql theme={null}
WITH [/* reference vector of `dimensions` floats */] AS reference_vec
SELECT id
FROM vectors
ORDER BY cosineDistance(vec, reference_vec) ASC
LIMIT 10
SETTINGS vector_search_use_quantized_codes = 1;
```

При `vector_search_use_quantized_codes = 1` оптимизатор автоматически переписывает запрос в двухстадийный план: он сканирует квантизованные коды, чтобы сформировать shortlist, а затем заново оценивает shortlist по `vec` с полной точностью.
Этот параметр по умолчанию отключён, поэтому без него тот же запрос выполняется как обычное точное сканирование — кодек никогда не меняет результаты, а лишь даёт более быстрый путь, если вы явно его включите.
Используйте функцию расстояния, которую поддерживает выбранный метод: `cosineDistance` для всех методов, `L2Distance` дополнительно для `int8`, `prefix` и `product`.

<div id="quantized-codecs-settings">
  #### Настройки
</div>

* `allow_experimental_codecs` — должен быть включен, чтобы можно было объявить кодек `Quantized` (по умолчанию: `0`).
* `vector_search_use_quantized_codes` — включает двухстадийное преобразование с отбором shortlist и пересчётом оценок (по умолчанию: `0`). Если параметр выключен, поисковые запросы выполняют точное сканирование векторов с полной точностью.
* `vector_search_index_fetch_multiplier` — сколько кандидатов включать в shortlist относительно `LIMIT` запроса: при сканировании сохраняются лучшие `LIMIT × multiplier` кодов перед пересчётом оценок. Чем больше значение, тем выше полнота, но тем больше объём пересчёта оценок. Значение по умолчанию — `1` (без избыточной выборки), поэтому для хорошей полноты его обычно нужно увеличить, например до `10` или выше.

<div id="quantized-codecs-built-for-scale">
  #### Рассчитано на масштабирование
</div>

Этот кодек хорошо подходит для ClickHouse, потому что самая затратная часть — сканирование — как раз относится к тем задачам, которые движок ClickHouse умеет выполнять особенно хорошо:

* **Векторизовано.** Ядра сканирования написаны под SIMD, с выбором во время выполнения самых широких инструкций, которые поддерживает CPU: аппаратный `popcount` для методов sign-code (`rabitq`, `turboquant`) и широкие инструкции fused-multiply-add для остальных.
* **Параллельно по ядрам и частям.** Линейное сканирование легко распараллеливается, и ClickHouse именно так его и обрабатывает: расстояния вычисляются сразу во всех доступных потоках и по всем частям таблицы, и только финальное слияние top-`k` выполняется последовательно.
* **Распределённо.** В сегментированном кластере работа распределяется по машинам — каждый сегмент параллельно сканирует свою часть данных, а координатор объединяет shortlist'ы.
* **Столбцовый формат и удобная фильтрация.** Квантованные коды занимают отдельный столбец, сжимаются и читаются по тому же пути I/O, что и любой другой столбец, поэтому выборочное `WHERE` просто оставляет меньше кодов для сканирования.
* **Без отдельного этапа построения.** Коды создаются по мере записи векторов и объединяются конкатенацией — индекс не нужно строить, настраивать или перестраивать, поэтому таблица готова к поиску сразу после поступления данных.

Коды используются только для генерации кандидатов; окончательное точное ранжирование обеспечивает столбец с полной точностью, который сохраняется на месте.

<div id="approximate-nearest-neighbor-search-qbit">
  ### Квантованный бит (QBit)
</div>

Один из распространённых способов ускорить точный векторный поиск — использовать [тип данных с плавающей точкой](/docs/ru/reference/data-types/float) с меньшей точностью.
Например, если векторы хранятся как `Array(BFloat16)` вместо `Array(Float32)`, объём данных уменьшается вдвое, и время выполнения запросов, как ожидается, сокращается пропорционально.
Этот метод называется квантованием. Хотя он ускоряет вычисления, точность результатов может снижаться, несмотря на полный перебор всех векторов.

При традиционном квантовании мы теряем точность и во время поиска, и при хранении данных. В примере выше мы бы хранили `BFloat16` вместо `Float32`, а значит, позже уже не смогли бы выполнить более точный поиск, даже если бы захотели. Один из альтернативных подходов — хранить две копии данных: квантованную и с полной точностью. Хотя это работает, такой подход требует избыточного хранения. Рассмотрим сценарий, в котором исходные данные имеют формат `Float64`, а мы хотим выполнять поиск с разной точностью (16 бит, 32 бита или полные 64 бита). В этом случае пришлось бы хранить три отдельные копии данных.

ClickHouse предлагает тип данных Quantized Bit (`QBit`), который решает эти проблемы за счёт следующего:

1. Хранения исходных данных с полной точностью.
2. Возможности задавать точность квантования во время выполнения запроса.

Это достигается за счёт хранения данных в формате с группировкой по битам (то есть все i-е биты всех векторов хранятся вместе), что позволяет считывать данные только с запрошенной точностью. Вы получаете выигрыш в скорости за счёт уменьшения I/O и объёма вычислений благодаря квантованию, при этом все исходные данные остаются доступными при необходимости. При выборе максимальной точности поиск становится точным.

Чтобы объявить столбец типа `QBit`, используйте следующий синтаксис:

```sql theme={null}
column_name QBit(element_type, dimension[, stride])
```

Где:

* `element_type` – тип каждого элемента вектора. Поддерживаемые типы: `Int8`, `BFloat16`, `Float32` и `Float64`
* `dimension` – количество элементов в каждом векторе
* `stride` – необязательно. Делитель `dimension`, который разбивает размерности на `dimension / stride` смежных групп, хранящихся в отдельных потоках, так что при поиске только по первым размерностям считывается меньше потоков (полезно для эмбеддинг-векторов Matryoshka). По умолчанию используется `dimension`; в этом случае тип побайтно идентичен `QBit` без `stride`. Подробности см. на [странице типа данных `QBit`](/docs/ru/reference/data-types/qbit).

<div id="qbit-create">
  #### Создание таблицы `QBit` и добавление в неё данных
</div>

```sql theme={null}
CREATE TABLE fruit_animal (
    word String,
    vec QBit(Float64, 5)
) ENGINE = MergeTree
ORDER BY word;

INSERT INTO fruit_animal VALUES
    ('apple', [-0.99105519, 1.28887844, -0.43526649, -0.98520696, 0.66154391]),
    ('banana', [-0.69372815, 0.25587061, -0.88226235, -2.54593015, 0.05300475]),
    ('orange', [0.93338752, 2.06571317, -0.54612565, -1.51625717, 0.69775337]),
    ('dog', [0.72138876, 1.55757105, 2.10953259, -0.33961248, -0.62217325]),
    ('cat', [-0.56611276, 0.52267331, 1.27839863, -0.59809804, -1.26721048]),
    ('horse', [-0.61435682, 0.48542571, 1.21091247, -0.62530446, -1.33082533]);
```

<div id="qbit-search">
  #### Векторный поиск с `QBit`
</div>

Найдём ближайших соседей для вектора, представляющего слово 'lemon', с помощью расстояния L2. Третий параметр функции расстояния задаёт точность в битах: чем выше значение, тем точнее результат, но тем больше вычислений требуется.

Все доступные функции расстояния для `QBit` приведены [здесь](/docs/ru/reference/data-types/qbit#vector-search-functions).

**Поиск с полной точностью (64 бита):**

```sql theme={null}
SELECT
    word,
    L2DistanceTransposed(vec, [-0.88693672, 1.31532824, -0.51182908, -0.99652702, 0.59907770], 64) AS distance
FROM fruit_animal
ORDER BY distance;
```

```text theme={null}
   ┌─word───┬────────────distance─┐
1. │ apple  │ 0.14639757188169716 │
2. │ banana │   1.998961369007679 │
3. │ orange │   2.039041552613732 │
4. │ cat    │   2.752802631487914 │
5. │ horse  │  2.7555776805484813 │
6. │ dog    │   3.382295083120104 │
   └────────┴─────────────────────┘
```

**Поиск с пониженной точностью:**

```sql theme={null}
SELECT
    word,
    L2DistanceTransposed(vec, [-0.88693672, 1.31532824, -0.51182908, -0.99652702, 0.59907770], 12) AS distance
FROM fruit_animal
ORDER BY distance;
```

```text theme={null}
   ┌─word───┬───────────distance─┐
1. │ apple  │  0.757668703053566 │
2. │ orange │ 1.5499475034938677 │
3. │ banana │ 1.6168396735102937 │
4. │ cat    │  2.429752230904804 │
5. │ horse  │  2.524650475528617 │
6. │ dog    │   3.17766975527459 │
   └────────┴────────────────────┘
```

Обратите внимание, что при 12-битном квантовании мы получаем достаточно точную оценку расстояний при более быстром выполнении запроса. Относительный порядок в целом сохраняется, и 'apple' по-прежнему остается самым близким совпадением.

<div id="qbit-performance">
  #### Особенности производительности
</div>

Преимущество `QBit` с точки зрения производительности связано со снижением числа операций I/O: при меньшей точности из хранилища нужно считывать меньше данных. Кроме того, если `QBit` содержит данные `Float32` и параметр точности равен 16 или меньше, дополнительный выигрыш достигается за счет сокращения объема вычислений. Параметр точности напрямую задает компромисс между точностью и скоростью:

* **Более высокая точность** (ближе к исходной разрядности данных): более точные результаты, но запросы выполняются медленнее
* **Более низкая точность**: более быстрые запросы с приближенными результатами, меньшее использование памяти

<div id="references">
  ### Справочные материалы
</div>

Блоги:

* [Векторный поиск с ClickHouse — Часть 1](https://clickhouse.com/blog/vector-search-clickhouse-p1)
* [Векторный поиск с ClickHouse — Часть 2](https://clickhouse.com/blog/vector-search-clickhouse-p2)
* [Мы создали движок векторного поиска, который позволяет выбирать точность на этапе выполнения запроса](https://clickhouse.com/blog/qbit-vector-search)
