Перейти к основному содержанию
Задача поиска N ближайших точек в многомерном (векторном) пространстве для заданной точки известна как поиск ближайших соседей или, кратко, векторный поиск. Существуют два основных подхода к решению задачи векторного поиска:
  • Точный векторный поиск вычисляет расстояние между заданной точкой и всеми точками векторного пространства. Это обеспечивает максимально возможную точность, то есть возвращённые точки гарантированно являются истинными ближайшими соседями. Поскольку векторное пространство просматривается полностью, точный векторный поиск может быть слишком медленным для практического применения.
  • Приближённый векторный поиск — это группа методов (например, специальные структуры данных, такие как графы и случайные леса), которые позволяют получать результаты гораздо быстрее, чем точный векторный поиск. Точность результата обычно является “достаточно хорошей” для практического использования. Многие приближённые методы предоставляют параметры для настройки компромисса между точностью результата и временем поиска.
Векторный поиск (точный или приближённый) можно записать в SQL следующим образом:
Точки в векторном пространстве хранятся в столбце vectors с типом Array, например Array(Float64), Array(Float32) или Array(BFloat16). Опорный вектор — это константный массив, заданный в виде общего табличного выражения. <DistanceFunction> вычисляет расстояние между опорной точкой и всеми сохранёнными точками. Для этого можно использовать любую из доступных функций расстояния. <N> указывает, сколько соседей нужно вернуть. Точный векторный поиск можно выполнить, используя приведённый выше запрос SELECT без каких-либо изменений. Время выполнения таких запросов, как правило, пропорционально количеству сохранённых векторов и их размерности, то есть числу элементов массива. Кроме того, поскольку ClickHouse выполняет полный перебор всех векторов, время выполнения также зависит от числа потоков, используемых запросом (см. настройку max_threads).

Пример

возвращает

Индексы векторного сходства

ClickHouse предоставляет специальный индекс векторного сходства для выполнения приближённого векторного поиска.
Индексы векторного сходства доступны в ClickHouse версии 25.8 и выше. Если у вас возникнут проблемы, пожалуйста, создайте issue в репозитории ClickHouse.

Создание индекса векторного сходства

Индекс векторного сходства можно создать для новой таблицы следующим образом:
Или, чтобы добавить индекс векторного сходства в существующую таблицу:
Индексы векторного сходства — это особый тип индексов пропуска данных (см. здесь и здесь). Соответственно, приведённый выше оператор ALTER TABLE строит индекс только для новых данных, которые будут вставлены в таблицу в будущем. Чтобы построить индекс и для существующих данных, его нужно материализовать:
Функция <distance_function> должна принимать одно из следующих значений:
  • L2Distanceевклидово расстояние, то есть длину отрезка между двумя точками в евклидовом пространстве,
  • cosineDistanceкосинусное расстояние, то есть угол между двумя ненулевыми векторами, или
  • dotProductскалярное произведение (внутреннее произведение), то есть сумму попарных произведений элементов двух векторов. Для нормализованных данных эквивалентно cosineDistance.
Для нормализованных данных L2Distance обычно является оптимальным выбором; в противном случае рекомендуется cosineDistance, чтобы компенсировать различия в масштабе.
Для функций расстояния L2Distance и cosineDistance меньшее значение означает более высокое сходство, тогда как для dotProduct большее значение означает более высокое сходство. Поэтому векторные индексы с L2Distance и cosineDistance могут использоваться только в запросах SELECT [...] ORDER BY [...] ASC (ASC — значение по умолчанию для ORDER BY), тогда как векторные индексы, построенные для dotProduct, могут использоваться только в запросах SELECT [...] ORDER BY [...] DESC.
<dimensions> задаёт мощность массива (число элементов) в исходном столбце. Если ClickHouse обнаружит массив с другой мощностью во время создания индекса, индекс будет отброшен и возвращена ошибка. Необязательный параметр GRANULARITY <N> задаёт размер гранул индекса (см. здесь). В отличие от обычных индексов пропуска данных, для которых гранулярность индекса по умолчанию равна 1, индексы векторного сходства по умолчанию используют гранулярность 100 миллионов. Это значение позволяет гарантировать, что даже для больших частей будет внутренне построено лишь небольшое число индексов. Мы рекомендуем изменять гранулярность индекса только опытным пользователям, которые понимают последствия своих действий (см. ниже). Индексы векторного сходства являются универсальными в том смысле, что поддерживают разные методы приближённого поиска. Фактически используемый метод задаётся параметром <type>. На данный момент доступен только метод HNSW (научная статья) — популярная современная техника приближённого векторного поиска, основанная на иерархических графах близости. Если в качестве типа используется HNSW, пользователь при желании может указать дополнительные параметры, специфичные для HNSW:
Доступны следующие параметры 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), Array(Float64) или Array(BFloat16). Массивы nullable- и low-cardinality-значений с плавающей точкой, такие как Array(Nullable(Float32)) и Array(LowCardinality(Float32)), не допускаются.
  • Индексы векторного сходства должны создаваться для одиночных столбцов.
  • Индексы векторного сходства можно создавать для вычисляемых выражений (например, INDEX index_name arraySort(vectors) TYPE vector_similarity([...])), но такие индексы впоследствии нельзя использовать для приближённого поиска соседей.
  • Индексы векторного сходства требуют, чтобы все массивы в исходном столбце содержали по <dimension> элементов — это проверяется при создании индекса. Чтобы как можно раньше выявлять нарушения этого требования, пользователи могут добавить ограничение для векторного столбца, например CONSTRAINT same_length CHECK length(vectors) = 256.
  • Аналогично, значения массива в исходном столбце не должны быть пустыми ([]) или иметь значение по умолчанию (тоже []).
Оценка потребления хранилища и памяти Вектор, созданный для использования с типичной ИИ-моделью (например, большой языковой моделью, LLM), состоит из сотен или тысяч значений с плавающей точкой. Таким образом, одно значение вектора может занимать несколько килобайт памяти. Пользователи, которые хотят оценить объём хранилища, необходимый для исходного векторного столбца в таблице, а также объём оперативной памяти, необходимый для индекса векторного сходства, могут использовать две приведённые ниже формулы: Потребление хранилища векторным столбцом в таблице (в несжатом виде):
Пример для датасета DBpedia:
Индекс векторного сходства должен быть полностью загружен с диска в оперативную память для выполнения поиска. Аналогично, векторный индекс также полностью строится в памяти, а затем сохраняется на диск. Объём памяти, необходимый для загрузки векторного индекса:
Пример для датасета DBpedia:
Приведённая выше формула не учитывает дополнительную память, необходимую индексам векторного сходства для выделения служебных структур данных во время выполнения, таких как предварительно выделенные буферы и кэши.

Использование индекса векторного сходства

Чтобы использовать индексы векторного сходства, параметр compatibility должен быть равен '' (значение по умолчанию), либо '25.1' или выше.
Индексы векторного сходства поддерживают запросы SELECT следующего вида:
Оптимизатор запросов ClickHouse пытается сопоставить приведённый выше шаблон запроса и задействовать доступные индексы векторного сходства. Запрос может использовать индекс векторного сходства только в том случае, если функция расстояния в запросе SELECT совпадает с функцией расстояния, указанной при определении индекса. Опытные пользователи могут задать произвольное значение для параметра 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 со значением по умолчанию 100. Слишком большие значения LIMIT могут замедлить поиск и, как правило, указывают на ошибку в использовании. Чтобы проверить, использует ли запрос SELECT индекс векторного сходства, можно добавить EXPLAIN indexes = 1 перед запросом. В качестве примера выполним запрос
может вернуть
В данном примере 1 миллион векторов из датасета dbpedia, каждый с размерностью 1536, хранится в 575 гранулах, то есть по 1,7 тыс. строк на гранулу. Запрос ищет 10 ближайших соседей, и индекс векторного сходства находит их в 10 отдельных гранулах. Эти 10 гранул будут прочитаны при выполнении запроса. Индексы векторного сходства используются, если вывод содержит Skip, а также имя и тип векторного индекса (в примере — idx и vector_similarity). В данном случае индекс векторного сходства отсеял две из четырёх гранул, то есть 50% данных. Чем больше гранул удаётся отсеять, тем эффективнее используется индекс.
Чтобы принудительно задать использование индекса, можно выполнить запрос SELECT с настройкой force_data_skipping_indexes (укажите имя индекса в качестве значения настройки).
Постфильтрация и префильтрация При необходимости можно указать секцию WHERE с дополнительными условиями фильтрации для запроса SELECT. ClickHouse вычисляет эти условия фильтрации, используя стратегию постфильтрации или префильтрации. Если кратко, обе стратегии определяют порядок применения фильтров:
  • Постфильтрация означает, что сначала используется индекс векторного сходства, а затем ClickHouse применяет дополнительные фильтры, указанные в предложении WHERE.
  • Префильтрация означает, что порядок применения фильтра становится обратным.
Каждая стратегия имеет свои компромиссы:
  • У постфильтрации есть общая проблема: она может вернуть меньше строк, чем указано в секции LIMIT <N>. Это происходит, когда одна или несколько строк результата, возвращённых индексом векторного сходства, не проходят дополнительные фильтры.
  • Префильтрация в общем случае остаётся нерешённой задачей. Некоторые специализированные векторные базы данных поддерживают алгоритмы префильтрации, но большинство реляционных баз данных (включая ClickHouse) переходят к точному поиску ближайших соседей, то есть к полному перебору без индекса.
То, какая стратегия будет использоваться, зависит от условия фильтрации. Дополнительные фильтры входят в ключ партиционирования Если условие дополнительной фильтрации входит в ключ партиционирования, ClickHouse применит отсечение партиций. Например, таблица партиционирована по диапазонам столбца year, и выполняется следующий запрос:
ClickHouse отсечёт все партиции, кроме партиции за 2025 год. Дополнительные фильтры нельзя вычислить по индексам Если дополнительные условия фильтрации нельзя вычислить по индексам (индексу первичного ключа, индексу пропуска данных), ClickHouse применит постфильтрацию. Дополнительные фильтры можно вычислить с помощью индекса первичного ключа Если дополнительные условия фильтрации можно вычислить с помощью первичного ключа (то есть они образуют префикс первичного ключа) и
  • условие фильтрации отбрасывает хотя бы одну строку в пределах части, ClickHouse переключится на префильтрацию для “оставшихся” диапазонов внутри этой части,
  • условие фильтрации не отбрасывает ни одной строки в пределах части, ClickHouse выполнит постфильтрацию для этой части.
На практике второй случай встречается довольно редко. Дополнительные фильтры можно вычислить с помощью индекса пропуска данных Если дополнительные условия фильтрации можно вычислить с помощью индексов пропуска данных (индекса minmax, индекса set и т. д.), ClickHouse выполняет постфильтрацию. В таких случаях индекс векторного сходства вычисляется первым, поскольку ожидается, что он отсеет больше всего строк по сравнению с другими индексами пропуска данных. Для более точного выбора между постфильтрацией и префильтрацией можно использовать два параметра: Параметр vector_search_filter_strategy (по умолчанию: auto, реализующий описанные выше эвристики) можно установить в значение prefilter. Это полезно, если нужно принудительно включить префильтрацию в случаях, когда дополнительные условия фильтрации крайне избирательны. Например, следующий запрос может выиграть от префильтрации:
Если предположить, что лишь очень небольшое число книг стоит меньше 2 долларов, постфильтрация может вернуть ноль строк, поскольку все 10 лучших совпадений, возвращённых векторным индексом, могут иметь цену выше 2 долларов. Если принудительно включить префильтрацию (добавив SETTINGS vector_search_filter_strategy = 'prefilter' в запрос), ClickHouse сначала находит все книги с ценой ниже 2 долларов, а затем выполняет векторный поиск полным перебором по найденным книгам. В качестве альтернативного способа решить описанную выше проблему параметр vector_search_index_fetch_multiplier (по умолчанию: 1.0, максимум: 1000.0) можно установить в значение > 1.0 (например, 2.0). Число ближайших соседей, извлекаемых из векторного индекса, умножается на значение этого параметра, после чего к этим строкам применяется дополнительный фильтр, чтобы вернуть число строк, соответствующее LIMIT. Например, можно снова выполнить запрос, но с множителем 3.0:
ClickHouse получит 3.0 x 10 = 30 ближайших соседей из векторного индекса в каждой части, а затем применит дополнительные фильтры. Будут возвращены только десять ближайших соседей. Отметим, что параметр vector_search_index_fetch_multiplier может смягчить эту проблему, но в крайних случаях (при очень селективном условии WHERE) всё же возможно, что будет возвращено менее N запрошенных строк. Пересчёт оценок Индекс пропуска данных в ClickHouse обычно фильтрует на уровне гранул, то есть поиск в индексе пропуска данных (внутренне) возвращает список потенциально подходящих гранул, что уменьшает объём читаемых данных при последующем сканировании. В целом это хорошо работает для индексов пропуска данных, но в случае индексов векторного сходства возникает “несоответствие гранулярности”. Если говорить подробнее, индекс векторного сходства определяет номера строк N наиболее похожих векторов для заданного эталонного вектора, но затем ему нужно экстраполировать эти номера строк в номера гранул. Затем ClickHouse загрузит эти гранулы с диска и повторно вычислит расстояние для всех векторов в этих гранулах. Этот шаг называется пересчётом оценок, и хотя теоретически он может повысить точность — помните, что индекс векторного сходства возвращает лишь приблизительный результат, — с точки зрения производительности он явно не оптимален. Поэтому ClickHouse предоставляет оптимизацию, которая отключает пересчёт оценок и возвращает наиболее похожие векторы и расстояния до них напрямую из индекса. Эта оптимизация включена по умолчанию, см. настройку vector_search_with_rescoring. В общих чертах это работает так: ClickHouse делает наиболее похожие векторы и расстояния до них доступными в виде виртуального столбца _distances. Чтобы увидеть это, выполните запрос векторного поиска с EXPLAIN header = 1:
Запрос, выполняемый без пересчёта оценок (vector_search_with_rescoring = 0) и при включенных параллельных репликах, может всё же перейти к пересчёту оценок.

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

Настройка сжатия Практически во всех случаях векторы в исходном столбце являются плотными и плохо поддаются сжатию. В результате сжатие замедляет вставку и чтение данных из векторного столбца. Поэтому мы рекомендуем отключить сжатие. Для этого укажите CODEC(NONE) для векторного столбца следующим образом:
Настройка создания индекса Жизненный цикл индексов векторного сходства связан с жизненным циклом частей. Иными словами, всякий раз, когда создаётся новая часть с заданным индексом векторного сходства, этот индекс тоже создаётся. Обычно это происходит при вставке данных или во время слияний. К сожалению, для HNSW характерно длительное создание индекса, что может существенно замедлить вставки и слияния. В идеале индексы векторного сходства следует использовать только для неизменяемых или редко изменяемых данных. Чтобы ускорить создание индекса, можно использовать следующие методы: Во-первых, создание индекса можно распараллелить. Максимальное число потоков для создания индекса настраивается с помощью настройки сервера max_build_vector_similarity_index_thread_pool_size. Для оптимальной производительности значение этой настройки следует установить равным числу ядер CPU. Во-вторых, чтобы ускорить операторы INSERT, пользователи могут отключить создание индексов пропуска данных для новых частей при вставке с помощью настройки сеанса materialize_skip_indexes_on_insert. Для SELECT-запросов к таким частям будет использоваться точный поиск. Поскольку вставленные части обычно невелики по сравнению с общим размером таблицы, влияние на производительность, как ожидается, будет незначительным. В-третьих, чтобы ускорить слияния, пользователи могут отключить создание индексов пропуска данных для слитых частей с помощью настройки сеанса materialize_skip_indexes_on_merge. Это в сочетании с оператором ALTER TABLE […] MATERIALIZE INDEX […] даёт явный контроль над жизненным циклом индексов векторного сходства. Например, создание индекса можно отложить до тех пор, пока не будут приняты все данные, или до периода низкой нагрузки на систему, например до выходных. Настройка использования индекса Чтобы использовать индексы векторного сходства, SELECT-запросам нужно загрузить их в оперативную память. Чтобы один и тот же индекс векторного сходства не загружался в оперативную память повторно, ClickHouse предоставляет специальный кэш в оперативной памяти для таких индексов. Чем больше этот кэш, тем меньше будет лишних загрузок. Максимальный размер кэша настраивается с помощью настройки сервера vector_similarity_index_cache_size. По умолчанию размер кэша может достигать 5 ГБ. Следующие сообщения лога (system.text_log) указывают на то, что индекс векторного сходства загружается. Если такие сообщения повторяются для разных запросов векторного поиска, это указывает на то, что размер кэша слишком мал.
Кэш индекса векторного сходства хранит гранулы векторного индекса. Если размер отдельных гранул векторного индекса превышает размер кэша, они не будут кэшироваться. Поэтому обязательно вычислите размер векторного индекса (по формуле из “Оценка потребления хранилища и памяти” или system.data_skipping_indices) и соответствующим образом подберите размер кэша.
Еще раз подчеркнем: при анализе медленных запросов векторного поиска первым шагом должна быть проверка кэша векторного индекса и, при необходимости, увеличение его размера. Текущий размер кэша индекса векторного сходства приведен в system.metrics:
Количество попаданий в кэш и промахов кэша для запроса с определённым Query id можно получить из system.query_log:
Для использования в продакшне мы рекомендуем выделять достаточно большой кэш, чтобы все векторные индексы постоянно находились в памяти. Настройка квантования Квантование — это метод уменьшения объёма памяти, занимаемой векторами, а также вычислительных затрат на построение и обход векторных индексов. Векторные индексы ClickHouse поддерживают следующие варианты квантования: Квантование снижает точность векторного поиска по сравнению с поиском по исходным значениям с полной точностью (f32) и плавающей запятой. Однако на большинстве датасетов квантование half-precision brain float (bf16) даёт пренебрежимо малую потерю точности, поэтому индексы векторного сходства по умолчанию используют именно его. Квантование с четвертной точностью (i8) и двоичное квантование (b1) приводят к заметной потере точности при векторном поиске. Мы рекомендуем оба этих варианта только в том случае, если размер индекса векторного сходства значительно превышает доступный объём DRAM. В этом случае мы также рекомендуем включить пересчёт оценок (vector_search_index_fetch_multiplier, vector_search_with_rescoring), чтобы повысить точность. Двоичное квантование рекомендуется только 1) для нормализованных эмбеддингов (то есть длина вектора = 1; модели OpenAI обычно нормализованы) и 2) если в качестве функции расстояния используется косинусное расстояние. При построении и поиске в графе близости двоичное квантование внутренне использует расстояние Хэмминга. На этапе пересчёта оценок используются исходные векторы полной точности, хранящиеся в таблице, чтобы определить ближайших соседей по косинусному расстоянию. Настройка передачи данных Опорный вектор в запросе векторного поиска задаётся пользователем и обычно получается вызовом большой языковой модели (LLM). Типичный код Python, выполняющий векторный поиск в ClickHouse, может выглядеть так
Эмбеддинг-векторы (search_v в приведённом выше фрагменте) могут иметь очень большую размерность. Например, OpenAI предоставляет модели, которые генерируют эмбеддинг-векторы размерностью 1536 или даже 3072. В приведённом выше коде Python-драйвер ClickHouse подставляет эмбеддинг-вектор в виде человекочитаемой строки, а затем отправляет запрос SELECT целиком как строку. Если предположить, что эмбеддинг-вектор состоит из 1536 значений с плавающей точкой одинарной точности, длина отправляемой строки достигает 20 кБ. Это приводит к высокой загрузке процессора из-за токенизации, разбора и тысяч преобразований строк в числа с плавающей точкой. Кроме того, файл журнала сервера ClickHouse тоже занимает значительный объём, что также вызывает разрастание system.query_log. Обратите внимание, что большинство LLM-моделей возвращают эмбеддинг-вектор в виде списка или массива NumPy из native float. Поэтому мы рекомендуем Python-приложениям передавать параметр опорного вектора в бинарной форме, используя следующий стиль:
В этом примере опорный вектор отправляется в бинарном виде как есть и на сервере интерпретируется как массив чисел с плавающей точкой. Это экономит процессорное время на стороне сервера и позволяет избежать разрастания серверных журналов и system.query_log.

Администрирование и мониторинг

Размер индексов векторного сходства на диске можно узнать из system.data_skipping_indices:
Пример вывода:

Отличия от обычных индексов пропуска данных

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

Пример

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

Квантованный бит (QBit)

Один из распространённых способов ускорить точный векторный поиск — использовать тип данных с плавающей точкой с меньшей точностью. Например, если векторы хранятся как Array(BFloat16) вместо Array(Float32), объём данных уменьшается вдвое, и время выполнения запросов, как ожидается, сокращается пропорционально. Этот метод называется квантованием. Хотя он ускоряет вычисления, точность результатов может снижаться, несмотря на полный перебор всех векторов. При традиционном квантовании мы теряем точность и во время поиска, и при хранении данных. В примере выше мы бы хранили BFloat16 вместо Float32, а значит, позже уже не смогли бы выполнить более точный поиск, даже если бы захотели. Один из альтернативных подходов — хранить две копии данных: квантованную и с полной точностью. Хотя это работает, такой подход требует избыточного хранения. Рассмотрим сценарий, в котором исходные данные имеют формат Float64, а мы хотим выполнять поиск с разной точностью (16 бит, 32 бита или полные 64 бита). В этом случае пришлось бы хранить три отдельные копии данных. ClickHouse предлагает тип данных Quantized Bit (QBit), который решает эти проблемы за счёт следующего:
  1. Хранения исходных данных с полной точностью.
  2. Возможности задавать точность квантования во время выполнения запроса.
Это достигается за счёт хранения данных в формате с группировкой по битам (то есть все i-е биты всех векторов хранятся вместе), что позволяет считывать данные только с запрошенной точностью. Вы получаете выигрыш в скорости за счёт уменьшения I/O и объёма вычислений благодаря квантованию, при этом все исходные данные остаются доступными при необходимости. При выборе максимальной точности поиск становится точным. Чтобы объявить столбец типа QBit, используйте следующий синтаксис:
Где:
  • element_type – тип каждого элемента вектора. Поддерживаемые типы: BFloat16, Float32 и Float64
  • dimension – количество элементов в каждом векторе

Создание таблицы QBit и добавление в неё данных

Найдём ближайших соседей для вектора, представляющего слово ‘lemon’, с помощью расстояния L2. Третий параметр функции расстояния задаёт точность в битах: чем выше значение, тем точнее результат, но тем больше вычислений требуется. Все доступные функции расстояния для QBit приведены здесь. Поиск с полной точностью (64 бита):
Поиск с пониженной точностью:
Обратите внимание, что при 12-битном квантовании мы получаем достаточно точную оценку расстояний при более быстром выполнении запроса. Относительный порядок в целом сохраняется, и ‘apple’ по-прежнему остается самым близким совпадением.

Особенности производительности

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

Справочные материалы

Блоги:
Последнее изменение 2 июля 2026 г.