Roman Kryvolapov Engineering Blog

Векторні бази даних

Машинне навчання і особливо нейромережі дозволяють перетворювати "неструктуровані дані" на вектори фіксованої довжини (найчастіше float32), які зберігають семантику вихідного об'єкта. Наприклад, два схожі тексти матимуть близькі вектори (мала евклідова дистанція або висока косинусна близькість).

Векторні бази даних (vector databases):
це спеціалізовані сховища, призначені для ефективного пошуку та зіставлення векторів (зазвичай — ембедингів), які представляють об'єкти на кшталт тексту, зображень, аудіо чи відео в числовому вигляді.

Що зазвичай зберігається у векторній базі

КомпонентЩо цеПриклад
IDУнікальний ідентифікатор запису"doc-001" або 123
ВекторЧисловий список, що представляє об'єкт[0.12, -0.56, 0.44, …, -0.03] (зазвичай float32)
ДокументВихідний текст або файл (опційно)"Як справи?"
МетаданіДод. поля для фільтрації, тегів, контексту{"language": "ru", "user": "petya", "tags": ["faq"]}

Популярні векторні бази

НазваРозробникІндексуванняМетрикиПлюсиМінуси
MilvusZillizIVF_FLAT, IVF_SQ8, IVF_PQ, HNSW, ANNOY, FlatL2, IP, Cosine, Jaccard, HammingМасштабованість (мільярди точок)
Багато індексів
gRPC/REST
Потребує Docker або Standalone
Складніше розгорнути
QdrantQdrant (на Rust)HNSW (модифікований), FlatCosine, Dot, Euclidean (L2)Швидкий, Rust-рушій
Легке встановлення
Фільтрація за метаданими
Поки менше індексів
Немає вбудованої кластеризації
WeaviateSemi.technologiesHNSW + Text (Hybrid Search)Cosine, Dot, EuclideanГібридний пошук (BM25 + векторний)
GraphQL API
Автоінжекція даних
Потребує більше пам'яті
GraphQL не завжди зручний
ChromaChroma orgFlat (точний), HNSW (у roadmap/частково)CosineДуже просте встановлення
Ідеальний для RAG і локального запуску
Тільки Flat (поки)
Немає фільтрації за метаданими (частково є)
FAISSFacebook/MetaFlat, IVF, PQ, OPQ, HNSW, LSHL2, Dot, Cosine (через нормалізацію)Дуже гнучкий
GPU-підтримка
Найкраща продуктивність на CPU/GPU
Це бібліотека, не сервер
Потрібне ручне налаштування і кодування
OpenSearchAmazonHNSW, Faiss backend, ANN native pluginL2, Dot, CosineГібридний пошук (BM25 + ANN)
Інтеграція з текстом
Elasticsearch-сумісний
Складне налаштування ANN
Високі вимоги до пам'яті

Перетворення даних на вектори (Embedding)

Наприклад, текст "Як справи?" може бути перетворений на вектор із 384 значень

[0.12, -0.56, 0.44, ..., -0.03]

Для цього використовуються спеціалізовані embedding моделі, такі як:

НазваЩо кодує і як працюєПереваги
all-MiniLM-L6-v2Легка і швидка модель на базі Transformer
Кодує фрази, питання, абзаци
Компактна (~80MB)
Підтримка в sentence-transformers
Працює «з коробки»
text-embedding-ada-002
(OpenAI)
Комерційна модель від OpenAI
Потребує API-ключа
Кодує будь-які тексти
Висока якість ембедингів
Підтримка різних мов
Чудово підходить для RAG
bge-small-enСучасна модель від BAAI
Підтримує шаблони: "query:...", "passage:..."
Висока точність
Підтримка багатомовності (у M3E)
Чудова для Qdrant, LangChain
e5-base / e5-largeУніверсальні моделі від FlagAI
Підходять для пошуку, кластеризації, QA
Найкращі показники на MTEB
Підтримка багатомовних завдань
Працюють без fine-tune
Instructor-XLКодує текст з урахуванням завдання
Використовує інструкції у стилі: "Represent the ... for ..."
Підвищена точність
Підходить для task-aware embedding
Чудово для RAG/FAQ
mpnet-base-v2Від Microsoft
Контекстно-чутлива модель
Добра для схожих фраз
Хороший баланс точності і швидкості
Підходить для paraphrase і general search
LaBSEВід Google
Багатомовна модель
Найкраще з короткими реченнями
Підтримка 100+ мов
Чудовий вибір для крос-мовного пошуку

Типові розміри векторів (довжини ембедингів) різних моделей:

МодельДовжина вектора
all-MiniLM-L6-v2384
text-embedding-ada-002 (OpenAI)1536
bge-small-en384
bge-base-en768
bge-large-en1024
e5-small-v2384
e5-base-v2768
e5-large-v21024
mpnet-base-v2768
LaBSE768
Instructor-XL768 або 1024

Індексування векторів

Вектори, що відповідають об'єктам, індексуються, щоб потім можна було швидко за вхідним вектором шукати максимально близькі зі збережених у базі.
Способи індексації векторів:

Види індексів:

НазваЯк працюєПеревагиНедоліки
FlatПеребирає всі вектори вручнуНайточніший пошук
Проста реалізація
Ідеально для налагодження і невеликих наборів
Дуже повільно за великого обсягу
Потребує багато обчислень
Не масштабується
HNSWПошук за мережею схожих векторів (починаючи з "центрів")Дуже швидкий
Висока точність
Підходить для великих баз
Потребує багато пам'яті
Довга побудова індексу
Складний у параметризації
IVFДілить вектори на групи (кластери), шукає тільки в нихШвидше, ніж Flat
Гнучке налаштування (nprobe)
Добре масштабується
Може пропустити схожі вектори
Потребує попереднього навчання
PQЗамінює частини вектора короткими кодамиСильно економить пам'ять
Швидкий пошук за таблицею
Ідеальний для великих наборів
Втрата точності
Потрібне навчання (codebook)
Не для завдань з високою точністю
OPQПокращена версія PQ — спочатку "виправляє" векторВища точність, ніж у PQ
Добре працює у FAISS, Milvus
Комбінується з IVF
Складніший у навчанні
Все ще наближений метод
AnnoyБудує багато випадкових дерев, шукає за нимиПростий у використанні
Мало залежить від ресурсів
Підходить для CPU і мобільних
Менш точний, ніж HNSW
Довга побудова індексу
Не можна оновити після побудови

Коли користувач вводить запит, він перетворюється на вектор, і база виконує пошук найближчих сусідів (KNN) за обраною метрикою.

Види метрик:

Назва метрикиЯк працюєПеревагиНедоліки
Cosine SimilarityПорівнюємо кут між векторами. Що ближчий кут до 0°, то більша схожість.Враховує тільки напрямок
Добре працює з текстами і ембедингами
Не залежить від довжини вектора
Не враховує масштаб (довжину)
Не підходить, якщо довжина вектора важлива
Euclidean (L2)Міряємо «лінійну» відстань між точками. Ближче — значить схоже.Проста й інтуїтивно зрозуміла
Підходить для координат, зображень
Не нормує вектори (масштаб впливає)
Не завжди добре для текстів
Inner Product (Dot Product)Додаємо відповідні координати. Що більша сума, то вища схожість.Дуже швидко рахується
Добре працює з ненормалізованими векторами
Чутливий до довжини вектора
Можуть бути важко інтерпретовані значення
Manhattan (L1)Сума модулів різниць за кожною координатою — як по клітинках на сітці.Стійкий до викидів
Краще працює з розрідженими векторами
Рідше використовується
Гірше працює зі щільними векторами
Hamming DistanceРахуємо кількість бітів, у яких відрізняються два бінарні вектори.Дуже швидкий для бінарних даних
Підходить для fingerprint і хешів
Працює тільки з бінарними векторами
Не застосовний до float
Jaccard SimilarityВідношення перетину до об'єднання множин або бінарних векторів.Ідеальний для тегів і бінарних ознак
Зрозуміла метрика
Тільки для бінарних векторів
Не працює з float-векторами
TanimotoУзагальнена Jaccard-метрика, застосовна і до float-векторів.Підходить для хімічних структур, fingerprint
Працює і з бінарними, і з дійсними
Рідко використовується
Обмежена підтримка в бібліотеках

Приклад роботи з векторною базою

У прикладі CRUD операцій використовуватимемо Python, базу Milvus, embedding модель e5‑base

from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection
from pymilvus.model.dense import SentenceTransformerEmbeddingFunction
# 1. Підключення до Milvus (за замовчуванням localhost:19530)
connections.connect("default", host="localhost", port="19530")
# 2. Ініціалізація функції ембедингу з моделлю e5-base-v2
# Ця модель потребує:
# - Префікс "passage: " для документів
# - Префікс "query: " для пошукових запитів
ef = SentenceTransformerEmbeddingFunction("intfloat/e5-base-v2")
# 3. Визначення схеми колекції:
# - "id" — цілочисельний ідентифікатор (первинний ключ)
# - "text" — вихідний текст документа (рядок)
# - "emb" — ембединг-вектор розмірності 768
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=False),
FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=512),
FieldSchema(name="emb", dtype=DataType.FLOAT_VECTOR, dim=768)
]
schema = CollectionSchema(fields, description="Колекція з ембедингами від e5-base-v2")
# 4. Створення колекції в Milvus із заданою схемою
collection = Collection("e5_collection", schema)
# 5. Підготовка і вставка документів
# Важливо: перед подачею в модель потрібно додати префікс "passage: "
raw_docs = ["Hello world", "Milvus vector database", "Semantic search with e5 model"]
docs = [f"passage: {d}" for d in raw_docs] # додаємо префікс
ids = [1, 2, 3]
# Обчислюємо ембединги для документів використовуючи e5‑base
embs = ef.encode_documents(docs)
# Вставляємо в колекцію:
# - ідентифікатори
# - вихідні (чисті) тексти без префіксів
# - ембединги
collection.insert([ids, raw_docs, embs])
# 6. Створення індексу за полем "emb" для пришвидшення пошуку
collection.create_index(
field_name="emb",
index_params={
# тип індексу
"index_type": "IVF_FLAT",
# параметр розбиття на кластери
"params": {"nlist": 128},
# метрика відстані (евклідова відстань)
"metric_type": "L2"
}
)
# 7. Завантаження колекції в оперативну пам'ять
# Без цього пошук працювати не буде
collection.load()
# 8. Пошуковий запит
# Аналогічно — використовуємо префікс "query: " перед текстом запиту
query_docs = ["query: vector database"]
q_emb = ef.encode_queries(query_docs)
# Виконуємо семантичний пошук за ембедингами
results = collection.search(
# ембединг пошукового запиту
data=q_emb,
# поле, за яким здійснюється пошук
anns_field="emb",
# параметри пошуку
param={"metric_type": "L2", "params": {"nprobe": 10}},
# кількість найближчих сусідів
limit=2,
# додаткові поля, які потрібно повернути
output_fields=["text"]
)
# 9. Виводимо результати пошуку
for i, hits in enumerate(results):
print(f"Результати для запиту: '{query_docs[i]}'")
if not hits:
print("Нічого не знайдено")
continue
for rank, hit in enumerate(hits, start=1):
print(f" {rank}:")
print(f" ID: {hit.id}")
print(f" Текст: {hit.entity.get('text')}")
print(f" Відстань: {hit.distance:.4f}")
# Результати для запиту: 'query: vector database'
# 1:
# ID: 2
# Текст: Milvus vector database
# Відстань: 2.8374
# 2:
# ID: 3
# Текст: Semantic search with e5 model
# Відстань: 5.4931
# 10. Видалення документа за ID
# У цьому випадку видаляється документ з id = 1
collection.delete(expr="id in [1]")
# 11. Видалення всієї колекції (якщо більше не потрібна)
collection.drop()

Copyright: Roman Kryvolapov