14. Векторные базы данных

HNSW, метрики расстояния и интеграция с Go

Векторные базы данных: Поиск смысла в многомерном пространстве

Традиционные базы данных, такие как 6. PostgreSQL или 1. Архитектура MySQL, оптимизированы для работы со структурированными данными: числами, строками и датами. Они отлично справляются с запросом WHERE price < 100, но пасуют перед задачей «найди мне товары, похожие на этот по стилю».

С расцветом Large Language Models (LLM) и нейросетей возникла необходимость хранить и быстро искать эмбеддинги (embeddings) — высокоразмерные векторы чисел, которые представляют собой сжатый «смысл» объекта (текста, картинки или аудио).


1. Что такое векторный поиск?

Векторная база данных хранит не сами объекты, а их представления в виде векторов в многомерном пространстве (обычно от 384 до 1536+ измерений).

flowchart LR
    Text["Текст / Картинка / Аудио"] --> Model["Нейросеть (Embedding Model)"]
    Model --> Vector["Вектор чисел: [0.12, -0.45, ..., 0.89] (1536 dim)"]
    Vector --> VDB["Векторная СУБД (Индексация HNSW)"]

    classDef default fill:#1e293b,stroke:#334155,color:#f8fafc;
    classDef emb fill:#312e81,stroke:#6366f1,color:#ffffff;
    class Vector,VDB emb;
  

Основная задача векторного поиска — найти K ближайших соседей (K-Nearest Neighbors, kNN) для целевого вектора. Близость определяется не совпадением символов, а математическим расстоянием между точками.

Основные метрики расстояния:

  1. L2 (Euclidean Distance): Обычное геометрическое расстояние. Чем меньше, тем лучше. Чувствительно к величине векторов.
  2. Cosine Similarity: Измеряет косинус угла между векторами. Фокусируется на направлении, а не на длине. Идеально для текстов.
  3. Inner Product (Dot Product): Скалярное произведение. Часто используется, если векторы нормализованы.

2. Mechanical Sympathy: Почему нельзя просто использовать циклы?

Если у вас 10 миллионов векторов по 1536 измерений, то простой перебор (Flat Search) потребует $10^7 \times 1536$ операций умножения и сложения для каждого поискового запроса.


3. Алгоритмы индексации: Как работает магия

Чтобы поиск был быстрым, используются алгоритмы Approximate Nearest Neighbors (ANN). Мы жертвуем 1% точности ради ускорения в 1000 раз.

IVFFlat (Inverted File Index)

Пространство делится на кластеры (ячейки Вороного). При поиске мы сначала находим ближайшие центроиды кластеров, а затем ищем только внутри этих кластеров.

HNSW (Hierarchical Navigable Small World)

На текущий момент это стандарт индустрии. Он строит граф, где узлы — это векторы.

  • Иерархия: На верхних слоях графа узлов мало, и «прыжки» между ними длинные (как магистрали между городами).
  • Спуск: Мы находим ближайшую точку на верхнем слое, спускаемся на слой ниже и уточняем поиск (как улицы внутри города).
  • Сложность: Поиск в HNSW имеет логарифмическую сложность $O(\log N)$.
flowchart TD
    Start["Запрос: Вектор V"] --> Layer2["Слой 2 (Магистрали между кластерами)"]
    Layer2 -->|"Найти ближайший узел"| NodeA["Узел A"]
    NodeA --> Layer1["Слой 1 (Улицы внутри района)"]
    Layer1 -->|"Уточнение позиции"| NodeB["Узел B"]
    NodeB --> Layer0["Слой 0 (Дома / Все векторы)"]
    Layer0 -->|"Точный расчет kNN"| Result["Ближайшие соседи"]

    classDef default fill:#1e293b,stroke:#334155,color:#f8fafc;
    classDef step fill:#312e81,stroke:#6366f1,color:#ffffff;
    class Layer2,Layer1,Layer0 step;
  

4. Архитектура векторных СУБД

Существует два подхода к реализации:

  1. Vector-native (Qdrant, Milvus, Weaviate, Pinecone): Написаны с нуля (часто на Rust или Go) для максимальной производительности векторных операций.
  2. Vector-plugins (pgvector для Postgres, Elasticsearch): Добавляют поддержку векторов в знакомые системы. Удобно, но часто медленнее и хуже масштабируется при миллиардах векторов.

5. Практика в Go: Работа с pgvector

Go отлично подходит для работы с векторными БД благодаря своей производительности и типизации. Рассмотрим пример использования pgvector через sqlx.

Go
package main

import (
    "context"
    "log"

    "github.com/jmoiron/sqlx"
    _ "github.com/lib/pq"
    "github.com/pgvector/pgvector-go"
)

type Document struct {
    ID        int             `db:"id"`
    Content   string          `db:"content"`
    Embedding pgvector.Vector `db:"embedding"` // Вектор в Go
}

func GetSimilarDocs(db *sqlx.DB, queryEmbedding []float32) ([]Document, error) {
    // Используем оператор <-> для L2 расстояния или <=> для Cosine
    query := `SELECT id, content FROM items 
              ORDER BY embedding <=> $1 
              LIMIT 5`

    var docs []Document
    // Конвертируем []float32 в формат pgvector
    err := db.Select(&docs, query, pgvector.NewVector(queryEmbedding))
    if err != nil {
        return nil, err
    }
    return docs, nil
}

Итог

  1. Векторные БД решают задачу семантического поиска через поиск ближайших соседей (kNN).
  2. HNSW — самый эффективный алгоритм для быстрого поиска по графу.
  3. Mechanical Sympathy: Векторный поиск критически зависит от SIMD-инструкций и скорости работы с памятью.
  4. Сжатие (Квантование): Необходимый инструмент при работе с Big Data, чтобы не разориться на оперативной памяти.

Мы разобрали, как хранить и искать смыслы. Теперь пора применить это на практике и понять, как интегрировать векторный поиск в реальные AI-продукты: 15. Поиск по embedding и AI use cases