Точный и приближённый векторный поиск
Задача нахождения N ближайших точек в многомерном (векторном) пространстве для заданной точки известна как поиск ближайших соседей или, кратко, векторный поиск. Существуют два общих подхода к решению задачи векторного поиска:
- Точный векторный поиск вычисляет расстояние между заданной точкой и всеми точками в векторном пространстве. Это обеспечивает максимально возможную точность, то есть возвращаемые точки гарантированно являются фактическими ближайшими соседями. Поскольку векторное пространство просматривается полностью, точный векторный поиск может быть слишком медленным для практического использования.
- Приближённый векторный поиск относится к группе методов (например, специальные структуры данных, такие как графы и случайные леса), которые вычисляют результаты намного быстрее, чем точный векторный поиск. Точность результата, как правило, «достаточно хороша» для практического применения. Многие приближённые методы предоставляют параметры для настройки компромисса между точностью результата и временем поиска.
Векторный поиск (точный или приближённый) может быть записан на SQL следующим образом:
Точки в векторном пространстве хранятся в столбце vectors типа массива, например Array(Float64), Array(Float32) или Array(BFloat16).
Эталонный вектор — это константный массив, задаваемый в виде общего табличного выражения.
<DistanceFunction> вычисляет расстояние между эталонной точкой и всеми сохранёнными точками.
Для этого может быть использована любая из доступных функций расстояния.
<N> задаёт, сколько соседей нужно вернуть.
Точный поиск по векторам
Точный поиск по векторам можно выполнить с использованием приведённого выше запроса SELECT без изменений. Время выполнения таких запросов, как правило, пропорционально количеству сохранённых векторов и их размерности, то есть количеству элементов массива. Кроме того, поскольку ClickHouse выполняет полный перебор всех векторов, время выполнения таких запросов также зависит от количества потоков, используемых запросом (см. настройку max_threads).
Пример
возвращает
Приблизительный векторный поиск
Индексы сходства векторов
ClickHouse предоставляет специальный индекс сходства векторов («vector similarity») для выполнения приблизительного векторного поиска.
Индексы сходства векторов доступны в ClickHouse версиях 25.8 и новее. Если вы столкнётесь с проблемами, пожалуйста, создайте issue в репозитории ClickHouse.
Создание индекса сходства векторов
Индекс сходства векторов можно создать на новой таблице следующим образом:
В качестве альтернативы можно добавить индекс векторного сходства к уже существующей таблице:
Индексы векторного сходства — это особый вид пропускающих индексов (см. здесь и здесь).
Соответственно, приведенный выше оператор ALTER TABLE приводит к тому, что индекс строится только для новых данных, которые будут вставляться в таблицу.
Чтобы построить индекс и для уже существующих данных, его необходимо материализовать:
Функция <distance_function> должна быть
L2Distance, евклидовым расстоянием, представляющим длину отрезка между двумя точками в евклидовом пространстве, илиcosineDistance, косинусным расстоянием, представляющим угол между двумя ненулевыми векторами.
Для нормализованных данных обычно лучше всего подходит L2Distance, в противном случае рекомендуется cosineDistance, чтобы компенсировать масштаб.
<dimensions> задаёт размер массива (количество элементов) в базовом столбце.
Если при создании индекса ClickHouse обнаружит массив с другим размером, индекс не будет создан, а будет возвращена ошибка.
Необязательный параметр GRANULARITY <N> относится к размеру гранул индекса (см. здесь).
Значение по умолчанию — 100 миллионов — должно хорошо подходить для большинства вариантов использования, но его также можно настроить.
Мы рекомендуем выполнять настройку только продвинутым пользователям, которые понимают последствия своих действий (см. ниже).
Индексы векторного сходства являются обобщёнными в том смысле, что могут использовать различные методы приблизительного поиска.
Фактически используемый метод задаётся параметром <type>.
На данный момент доступен только метод HNSW (академическая статья) — популярная и современная техника приблизительного поиска по векторам на основе иерархических графов близости.
Если в качестве типа используется HNSW, пользователи могут дополнительно указать специфичные для HNSW параметры:
Доступны следующие параметры, специфичные для HNSW:
<quantization>управляет квантизацией векторов в графе близости. Возможные значения:f64,f32,f16,bf16,i8илиb1. Значение по умолчанию —bf16. Обратите внимание, что этот параметр не влияет на представление векторов во внутреннем столбце.<hnsw_max_connections_per_layer>управляет количеством соседей у каждой вершины графа, также известным как гиперпараметр HNSWM. Значение по умолчанию —32. Значение0означает использование значения по умолчанию.<hnsw_candidate_list_size_for_construction>управляет размером динамического списка кандидатов во время построения графа HNSW, также известным как гиперпараметр HNSWef_construction. Значение по умолчанию —128. Значение0означает использование значения по умолчанию.
Значения по умолчанию всех параметров, специфичных для HNSW, достаточно хорошо подходят для большинства сценариев использования. Поэтому мы не рекомендуем изменять эти параметры.
Дополнительно действуют следующие ограничения:
- Индексы векторного сходства могут быть построены только по столбцам типов Array(Float32), Array(Float64) или Array(BFloat16). Массивы допускающих
NULLи чисел с плавающей запятой с низкой кардинальностью, такие какArray(Nullable(Float32))иArray(LowCardinality(Float32)), не поддерживаются. - Индексы векторного сходства должны строиться по отдельным столбцам.
- Индексы векторного сходства могут быть построены по вычисляемым выражениям (например,
INDEX index_name arraySort(vectors) TYPE vector_similarity([...])), но такие индексы не могут быть использованы для последующего приближённого поиска ближайших соседей. - Для индексов векторного сходства требуется, чтобы все массивы в базовом столбце содержали
<dimension>элементов — это проверяется при создании индекса. Чтобы как можно раньше обнаружить нарушения этого требования, пользователи могут добавить ограничение для векторного столбца, например,CONSTRAINT same_length CHECK length(vectors) = 256. - Аналогично, значения массивов в базовом столбце не должны быть пустыми (
[]) и не должны иметь пустое значение по умолчанию (также[]).
Оценка потребления хранилища и памяти
Вектор, сгенерированный для использования с типичной моделью ИИ (например, большой языковой моделью, LLMs), состоит из сотен или тысяч значений с плавающей запятой. Таким образом, одно векторное значение может потреблять несколько килобайт памяти. Пользователи, которые хотят оценить объём хранилища, необходимый для базового векторного столбца в таблице, а также объём оперативной памяти, необходимый для индекса векторного сходства, могут использовать две приведённые ниже формулы:
Потребление хранилища векторным столбцом в таблице (без сжатия):
Пример для датасета 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 отдельных гранулах. Эти 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 будет выполнять постфильтрацию для этой части.
На практике второй вариант встречается довольно редко.
Дополнительные фильтры могут оцениваться с использованием skipping index
Если дополнительные условия фильтрации могут быть оценены с использованием skipping indexes (minmax index, set index и т. д.), ClickHouse выполняет постфильтрацию. В таких случаях индекс векторного сходства вычисляется первым, так как ожидается, что он отбросит наибольшее количество строк по сравнению с другими skipping indexes.
Для более тонкого управления постфильтрацией и предварительной фильтрацией можно использовать два параметра:
Параметр vector_search_filter_strategy (по умолчанию: auto, который реализует приведённые выше эвристики) может быть установлен в значение prefilter.
Это полезно для принудительной предварительной фильтрации в случаях, когда дополнительные условия фильтрации обладают крайне высокой селективностью.
Например, следующий запрос может выиграть от предварительной фильтрации:
Предположим, что только очень небольшое количество книг стоит меньше 2 долларов. В этом случае постфильтрация может вернуть ноль строк, потому что все 10 лучших совпадений, возвращённых векторным индексом, могут иметь цену выше 2 долларов.
Принудительно включив префильтрацию (добавьте SETTINGS vector_search_filter_strategy = 'prefilter' к запросу), ClickHouse сначала находит все книги с ценой менее 2 долларов, а затем выполняет переборный (brute-force) векторный поиск по найденным книгам.
В качестве альтернативного подхода к решению указанной выше проблемы можно задать настройку 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 запрошенных строк.
Пересчёт оценок (rescoring)
Skip-индексы в ClickHouse обычно фильтруют данные на уровне гранул, то есть запрос к skip-индексу (внутренне) возвращает список потенциально подходящих гранул, что сокращает объём читаемых данных при последующем сканировании. Это хорошо работает для skip-индексов в целом, но в случае индексов векторного сходства создаётся «несоответствие гранулярности». Подробнее: индекс векторного сходства определяет номера строк N наиболее похожих векторов для заданного опорного вектора, но затем ему нужно сопоставить эти номера строк с номерами гранул. ClickHouse затем загрузит эти гранулы с диска и повторит вычисление расстояний для всех векторов в этих гранулах. Этот шаг называется пересчётом оценок (rescoring), и хотя теоретически он может повысить точность — помните, что индекс векторного сходства возвращает только приближённый результат, — очевидно, что с точки зрения производительности он не оптимален.
Поэтому 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.data_skipping_indices) и задать размер кэша соответствующим образом.
Текущий размер кэша индекса векторного сходства отображается в system.metrics:
Информацию о попаданиях и промахах кэша для запроса с заданным идентификатором можно получить из system.query_log:
Для production-сценариев мы рекомендуем выбирать размер кэша таким образом, чтобы все векторные индексы постоянно помещались в память.
Настройка квантования
Квантование — это метод уменьшения объёма памяти, занимаемой векторами, и вычислительных затрат на построение и обход векторных индексов. Векторные индексы ClickHouse поддерживают следующие варианты квантования:
| Quantization | Name | Storage per dimension |
|---|---|---|
| f32 | Single precision | 4 bytes |
| f16 | Half precision | 2 bytes |
| bf16 (default) | Half precision (brain float) | 2 bytes |
| i8 | Quarter precision | 1 byte |
| b1 | Binary | 1 bit |
Квантование снижает точность векторного поиска по сравнению с поиском по исходным значениям с полной точностью с плавающей запятой (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.
В приведённом выше коде драйвер ClickHouse для Python подставляет вектор встраивания, преобразуя его в человекочитаемую строку, и затем целиком отправляет запрос SELECT в виде строки.
Предположим, что вектор встраивания состоит из 1536 значений с плавающей запятой одинарной точности, тогда длина отправляемой строки достигает 20 кБ.
Это приводит к высокому использованию CPU на токенизацию, разбор и выполнение тысяч преобразований строки в число с плавающей запятой.
Кроме того, требуется значительный объём места в файле журнала сервера ClickHouse, что также вызывает разрастание system.query_log.
Обратите внимание, что большинство LLM‑моделей возвращают вектор встраивания в виде списка или массива NumPy из нативных чисел с плавающей запятой. Поэтому мы рекомендуем 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 загрузит эти гранулы и определит действительно лучшие строки, выполнив расчёт расстояний полным перебором (brute-force) по всем строкам гранул.
При небольшом значении GRANULARITY каждый из подиндексов возвращает до LIMIT N гранул.
В результате требуется загрузить и дополнительно отфильтровать больше гранул.
Обратите внимание, что точность поиска в обоих случаях одинаково высока, различается только производительность обработки.
Обычно рекомендуется использовать большое значение GRANULARITY для индексов векторного сходства и переходить к меньшим значениям GRANULARITY только в случае проблем, например чрезмерного потребления памяти структурами векторного сходства.
Если GRANULARITY для индексов векторного сходства не задан, значение по умолчанию — 100 миллионов.
Пример
возвращает
Дополнительные наборы данных, использующие приблизительный векторный поиск:
Квантованный бит (QBit)
Один из распространённых подходов к ускорению точного векторного поиска — использовать менее точный тип данных float.
Например, если векторы хранятся как Array(BFloat16) вместо Array(Float32), размер данных уменьшается вдвое, и ожидается, что время выполнения запросов сократится пропорционально.
Этот метод называется квантизацией. Хотя он ускоряет вычисления, точность результатов может снижаться, даже при полном переборе всех векторов.
При традиционной квантизации точность теряется как во время поиска, так и при хранении данных. В приведённом выше примере мы бы хранили BFloat16 вместо Float32, что означает невозможность выполнения более точного поиска в будущем, даже если это потребуется. Один из альтернативных подходов — хранить две копии данных: квантованную и с полной точностью. Хотя это работает, такой подход требует избыточного объёма хранилища. Рассмотрим сценарий, когда исходные данные имеют тип Float64, и нам нужно выполнять поиск с различной точностью (16-битной, 32-битной или полной 64-битной). В этом случае нам пришлось бы хранить три отдельные копии данных.
ClickHouse предлагает тип данных Quantized Bit (QBit), который устраняет эти ограничения за счёт:
- Хранения исходных данных с полной точностью.
- Возможности указания точности квантизации на этапе выполнения запроса.
Это достигается за счёт хранения данных в формате с побитовой группировкой (то есть все i-е биты всех векторов хранятся вместе), что позволяет выполнять чтение только с запрошенным уровнем точности. Вы получаете выигрыш в скорости за счёт сокращения объёма операций ввода-вывода и вычислений благодаря квантизации, при этом все исходные данные остаются доступными при необходимости. При выборе максимальной точности поиск становится точным.
Тип данных QBit и связанные с ним функции вычисления расстояния в настоящее время являются экспериментальными. Чтобы их включить, выполните SET allow_experimental_qbit_type = 1.
Если вы столкнулись с проблемами, пожалуйста, создайте issue в репозитории ClickHouse.
Чтобы объявить столбец типа QBit, используйте следующий синтаксис:
Где:
element_type– тип каждого элемента вектора. Поддерживаемые типы:BFloat16,Float32иFloat64dimension– количество элементов в каждом векторе
Создание таблицы QBit и добавление данных
Векторный поиск с QBit
Найдём ближайших соседей к вектору, соответствующему слову «lemon», используя L2-расстояние. Третий параметр в функции расстояния задаёт точность в битах: более высокие значения обеспечивают большую точность, но требуют больше вычислительных ресурсов.
Все доступные функции расстояния для QBit можно найти здесь.
Поиск с полной точностью (64-битной):
Поиск с уменьшенной точностью:
Обратите внимание, что с 12-битной квантизацией мы получаем хорошее приближение расстояний при более быстром выполнении запросов. Относительный порядок в целом сохраняется: 'apple' по‑прежнему является ближайшим совпадением.
В текущей реализации ускорение достигается за счёт уменьшения I/O, так как мы читаем меньше данных. Если исходные данные были «широкими», например Float64, выбор меньшей точности всё равно приведёт к вычислению расстояния по данным той же ширины — только с меньшей точностью.
Соображения по производительности
Производительность QBit повышается за счёт сокращения операций I/O, поскольку при использовании меньшей точности из хранилища нужно читать меньше данных. Кроме того, когда QBit содержит данные типа Float32 и параметр точности равен 16 или меньше, появляется дополнительное преимущество за счёт уменьшения объёма вычислений. Параметр точности напрямую управляет компромиссом между точностью и скоростью:
- Более высокая точность (ближе к исходной разрядности данных): более точные результаты, более медленные запросы
- Низкая точность: более быстрые запросы с приближёнными результатами, уменьшенное использование памяти
Ссылки
Блоги: