HNSW Vector Similarity и QBit
Векторный поиск — растущий use case для RAG-систем, рекомендаций и семантического поиска. ClickHouse не является vector-native базой данных, но предоставляет ANN-индексы (Approximate Nearest Neighbor) для случаев, когда данные уже хранятся в ClickHouse и нет необходимости в отдельном векторном хранилище.
HNSW Vector Similarity Index (GA с 25.8)
HNSW vector_similarity index стал GA-фичей в ClickHouse 25.8 (vectorSimilarityIndex stable) и доступен в 26.3 LTS как production-ready возможность. Иерархические навигационные малые миры (Hierarchical Navigable Small World) — граф-структура для приближённого поиска ближайших соседей. Предыдущие экспериментальные индексы Annoy и usearch удалены в 25.6 — если у вас были такие индексы на старых версиях, их необходимо DROP INDEX перед апгрейдом.
Начиная с 25.8 настройка allow_experimental_vector_similarity_index больше не требуется (индекс GA). 25.8 также добавил index-only reading, fetch multiplier и binary quantization (b1). Quantization-режимы: bf16 (по умолчанию), i8 (int8), b1 (бинарная).
Создание таблицы с HNSW-индексом:
CREATE TABLE embeddings (
id UInt32,
title String,
vector Array(Float32),
CONSTRAINT dim CHECK length(vector) = 384
) ENGINE = MergeTree()
ORDER BY id;
Добавление индекса через ALTER TABLE:
-- Синтаксис: vector_similarity(algorithm, distance, dim, quantization, M, ef_construction)
ALTER TABLE embeddings
ADD INDEX vec_idx vector
TYPE vector_similarity('hnsw', 'cosineDistance', 384, 'bf16', 64, 512);
Параметры индекса:
| Параметр | Значение | Описание |
|---|---|---|
algorithm | 'hnsw' | Алгоритм построения графа |
distance_function | 'cosineDistance' | Функция расстояния |
dim | 384 | Размерность векторов (должна совпадать для всех строк) |
quantization | 'bf16' | Квантизация (bf16 = bfloat16, экономия памяти) |
M | 64 | Число рёбер в HNSW-графе (больше = точнее, больше памяти) |
ef_construction | 512 | Размер очереди при построении (больше = точнее, медленнее build) |
ADD INDEX vs MATERIALIZE INDEX
ADD INDEX только объявляет определение индекса. Для существующих данных необходимо выполнить ALTER TABLE embeddings MATERIALIZE INDEX vec_idx SETTINGS mutations_sync = 2. Новые INSERT индексируются автоматически.
-- Шаг 1: объявить определение индекса
ALTER TABLE embeddings
ADD INDEX vec_idx vector
TYPE vector_similarity('hnsw', 'cosineDistance', 384, 'bf16', 64, 512);
-- Шаг 2: материализовать для существующих данных
ALTER TABLE embeddings
MATERIALIZE INDEX vec_idx
SETTINGS mutations_sync = 2;
-- mutations_sync=2 -- ждать завершения мутации на всех репликах
Поиск ближайших соседей
ClickHouse автоматически использует HNSW при запросе с ORDER BY distance_function(...) LIMIT K:
-- Найти 10 наиболее похожих документов
SELECT id, title
FROM embeddings
ORDER BY cosineDistance(vector, [0.1, 0.2, 0.3, 0.4])
LIMIT 10;
Доступные функции расстояния:
| Функция | Описание | Использование |
|---|---|---|
cosineDistance | 1 - cos(angle) | Текстовые эмбеддинги, семантическое сходство |
L2Distance | Евклидово расстояние | Изображения, audio-эмбеддинги |
L2SquaredDistance | L2^2 без sqrt | Оптимизация вычислений (монотонно эквивалентно L2) |
HNSW + FINAL: несовместимость
HNSW indexes несовместимы с FINAL qualifier и ReplacingMergeTree. При использовании SELECT ... FINAL ClickHouse вернёт результат, но HNSW-индекс будет проигнорирован — будет выполнен полный сканирующий поиск. Для vector storage используйте обычный MergeTree без FINAL.
-- Неправильно: HNSW индекс не используется при FINAL
SELECT id, title
FROM dedup_embeddings FINAL
ORDER BY cosineDistance(vector, query_vec)
LIMIT 10;
-- Правильно: MergeTree без FINAL, ручная дедупликация при необходимости
SELECT id, title
FROM plain_embeddings
ORDER BY cosineDistance(vector, query_vec)
LIMIT 10;
QBit Binary Quantization
QBit — тип данных для бит-срезового хранения векторов с tunable precision. Введён в 25.10, переведён в beta в 26.1, объявлен production-ready в 26.2 и стандартно доступен в 26.3 LTS.
Ключевая идея QBit — decomposed bit-sliced storage. Каждое число (BFloat16, Float32 или Float64) разбивается на отдельные бит-плоскости (bit planes), и при запросе пользователь указывает, сколько старших бит брать. Без QBit квантизация фиксируется во время INSERT; с QBit — выбирается на лету в момент SELECT. Re-ingest при смене precision не требуется.
-- В 26.3 LTS флаг включения по умолчанию активен; на более ранних версиях:
SET allow_experimental_qbit_type = 1;
CREATE TABLE items (
id UInt32,
embedding QBit(Float32, 128)
) ENGINE = MergeTree()
ORDER BY id;
Tunable precision: B бит на размерность
Параметр B (bits per dimension) выбирается в момент запроса, а не при создании таблицы. Чем меньше B — тем меньше I/O и быстрее вычисление расстояния, но тем ниже recall.
-- Поиск с B=2 бита на размерность (агрессивная квантизация, ~16x экономия памяти)
SELECT id, L2DistanceTransposed(embedding, [0.1, 0.2, ...], 2) AS dist
FROM items
ORDER BY dist
LIMIT 10;
-- Тот же запрос с B=8 (выше recall, больше I/O)
SELECT id, L2DistanceTransposed(embedding, [0.1, 0.2, ...], 8) AS dist
FROM items
ORDER BY dist
LIMIT 10;
-- B=32 для Float32 -- эквивалент полной точности
SELECT id, L2DistanceTransposed(embedding, [0.1, 0.2, ...], 32) AS dist
FROM items
ORDER BY dist
LIMIT 10;
Memory и recall trade-off
Для Float32 (32 бита на dimension) использование B=2 эффективно сокращает читаемый объём примерно в 16 раз — читаются только 2 старших бит-плоскости из 32. На практике это означает:
| B (бит/dim) | Чтение vs Float32 | Recall@10 (типично) | Use case |
|---|---|---|---|
| 1 | ~32x меньше | 0.6-0.75 | Грубое candidate generation |
| 2 | ~16x меньше | 0.75-0.85 | Recall-rough screening |
| 4 | ~8x меньше | 0.90-0.95 | Production query (баланс) |
| 8 | ~4x меньше | 0.97-0.99 | Высокое качество |
| 32 | 1x (full) | 1.0 (точное) | Reranking topK после QBit-screening |
Hybrid pattern: QBit screening + точный rerank
Типичный production-паттерн — двухэтапный поиск: QBit с малым B для быстрого получения top-K кандидатов, затем точное расстояние для финального ранжирования:
WITH candidates AS (
-- Шаг 1: дешёвое candidate generation через QBit B=2
SELECT id, embedding
FROM items
ORDER BY L2DistanceTransposed(embedding, {query:Array(Float32)}, 2)
LIMIT 200
)
-- Шаг 2: точное ранжирование на 200 кандидатах
SELECT id
FROM candidates
ORDER BY L2DistanceTransposed(embedding, {query:Array(Float32)}, 32)
LIMIT 10;
QBit поддерживает BFloat16, Float32 и Float64 как базовые типы. Для embedded-моделей, которые традиционно используют Float32, QBit(Float32, 768) — стандартный выбор для 768-мерных векторов BERT/E5.
Сравнение подходов
| Подход | Точность | Скорость поиска | Доп. память | Статус |
|---|---|---|---|---|
| Полный скан + cosineDistance | Точная | Медленная (O(N)) | Нет | GA |
HNSW index (vector_similarity) | Приближённая | Быстрая (O(log N)) | Значительная | GA (с 25.8) |
QBit (QBit(Float32, N), B=2-8) | Tunable (B на запрос) | Быстрая | Минимальная (~16x при B=2) | GA (с 26.2) |
| Annoy / usearch | — | — | — | Удалён в 25.6 (заменён на vector_similarity) |
Production альтернативы
ClickHouse HNSW (GA с 25.8) подходит для production-сценариев, когда данные уже хранятся в ClickHouse и нет потребности в отдельном инфраструктурном компоненте. Если требуется специализированная vector DB (например, для очень больших корпусов или сложных hybrid-search паттернов), рассмотрите:
- Milvus — открытая vector DB, Kubernetes-native
- Qdrant — высокопроизводительная vector DB на Rust
- pgvector — расширение PostgreSQL для векторного поиска
Ключевые выводы
- HNSW
vector_similarityindex — GA с ClickHouse 25.8 (доступен в 26.3 LTS). Настройкаallow_experimental_vector_similarity_indexбольше не требуется. ADD INDEXтолько регистрирует определение. Для индексации существующих строк необходимMATERIALIZE INDEX ... SETTINGS mutations_sync = 2.- Доступные distance functions:
cosineDistance(текст),L2Distance(изображения),L2SquaredDistance(оптимизация). - HNSW несовместим с
FINALи ReplacingMergeTree — приSELECT ... FINALиндекс игнорируется. - QBit — production-ready тип данных (GA в 26.2, доступен в 26.3 LTS) с tunable precision: параметр
B(bits per dimension) выбирается в момент запроса, а не приINSERT. B=2 даёт ~16x экономию I/O при recall ~0.75-0.85; B=4 — production-баланс. Hybrid-паттерн: QBit-screening для top-K кандидатов + точное ранжирование (B=32) для финального LIMIT. - Annoy и usearch индексы удалены в 25.6 — мигрировать на
vector_similarityчерезDROP INDEX+ADD INDEX ... TYPE vector_similarity(...)перед апгрейдом.