Поза межами наївного RAG: пам'ять HNSW, гібридний пошук і довгий контекст
Чому векторні індекси в пам'яті впираються в стіну на великих обсягах, де ламається пошук за точним збігом і як BM25-гібриди, RRF та PostgreSQL вписуються у світ довгого контексту.
Контекстні вікна вже сягнули мільйона токенів і більше, і це непомітно змінює саму роль векторного пошуку. Той самий зсув показує, чому багатовимірні індекси погано масштабуються, і підказує, куди рухається корпоративний пошук.
Легкі роки RAG
Коли retrieval-augmented generation став стандартним патерном для корпоративних LLM-застосунків, рецепт виглядав просто:
- Нарізати корпус на фрагменти менші за 1000 токенів.
- Прогнати кожен фрагмент через embedding-модель на кшталт
text-embedding-3-large. - Зберігати щільні вектори в окремій векторній базі на графах Hierarchical Navigable Small World (HNSW).
- Під час запиту виконувати наближений пошук найближчих сусідів (ANN) і вставляти найкращі збіги в промпт.
За 10 000 документів і вікна у 4k це чудово працювало. Потім вікна виросли понад 1M токенів, і економіка змінилася. Якщо інженер може покласти в промпт усю кодову базу, юридичний архів чи річну звітність, аргументи на користь наївного векторного пошуку слабшають.
Масштаб додає ще одну проблему. Коли дані зростають від мільйонів до мільярдів векторів, виринають три приховані витрати чистого векторного пошуку: вичерпана RAM, якість індексу, що падає зі зростанням розмірності, і непередбачувана семантична повнота. Далі розберемо, чому векторні бази впираються в ці межі і що зараз роблять бойові системи.
Проблема пам'яті HNSW
Майже всі великі векторні рушії, зокрема Pinecone, Qdrant, Milvus, Weaviate і pgvector, спираються на графи HNSW. HNSW дає швидкий логарифмічний пошук, розкладаючи точки по шарах. Верхні шари тримають далекі зв'язки для швидких стрибків, нижні — щільні локальні зв'язки із сусідами.
Заковика в тому, що для швидкого обходу граф має жити в оперативній пам'яті.
Арифметика
Візьмімо 1536-вимірні ембедінги, які видають text-embedding-3-small або ada-002 від OpenAI, у вигляді 32-бітних float. Кожен вектор займає 1536 × 4 байти, тобто 6144 байти, близько 6 КБ.
На 100 мільйонах векторів:
- Самі вектори: 100 мільйонів × 6 КБ — приблизно 600 ГБ.
- Накладні витрати графа: зв'язки кожного вузла з 16–64 сусідами додають ще 20–50 відсотків.
- Разом пам'яті: десь від 750 ГБ до 1 ТБ швидкої RAM.
За хмарними цінами оренда терабайта пам'яті лише заради індексації текстових фрагментів швидко починає коштувати більше, ніж сам інференс LLM.
Product Quantization (PQ) і скалярне квантування (SQ8) стискають вектори з float32 в int8 або бінарний вигляд, але приносять другу проблему: падає повнота.
Прокляття розмірності та семантичний дрейф
Приблизно після 1000 вимірів у векторних просторах проявляються геометричні дивацтва, відомі як прокляття розмірності. Одне з них — концентрація відстаней. Зі зростанням розмірності відношення відстані до найближчої точки до відстані до найдальшої наближається до 1.
Для косинусної близькості майже кожен вектор починає здаватися однаково далеким від усіх інших. Додайте квантування, і межі між різними поняттями розмиваються ще більше.
Де ламається точний збіг
Векторний пошук імовірнісний за своєю природою. Він вимірює зміст, а не буквальні токени. У корпоративних системах, де важать буквальні збіги, це призводить до серйозних промахів:
- Артикули та ідентифікатори. Запит
"Part #AB-9941-X"може повернути"Part #AB-9942-X", бо обидва ембедінги потрапляють в один кластер. - Заперечення та логіка. Запит на кшталт
"Contracts without liability caps"часто повертає саме договори з обмеженням відповідальності, бо embedding-модель чіпляється за фразу «liability caps».
Довгий контекст проти чанків
З моделями, які від початку тримають великі вікна, баланс між попередньою індексацією та прямою увагою в контексті змістився. Розрізаючи документ на шматки по 512 токенів, ви рвете перехресні посилання, залежності таблиць і логічні умови, розтягнуті сторінкою. Коли велике вікно вміщує документи цілком, наївний чанкінг втрачає сенс, а векторний пошук із пошуку фрагментів перетворюється на маршрутизацію великих блоків документів.
Гібридний пошук і повернення BM25
Щоб обійти ці обмеження, дата-інженерія відходить від суто векторних сховищ до гібридних архітектур із трьох частин:
- Щільний пошук за векторами — ловить загальний намір і змістові запитання.
- Розріджений пошук через BM25 або SPLADE — ловить точні ключові слова, серійні номери та конкретні сутності.
- Reciprocal Rank Fusion (RRF) — зливає обидва списки результатів до того, як top-K кандидатів потраплять у модель-реранкер.
Бекенд-пайплайн може об'єднати щільні й розріджені оцінки через RRF на Python так:
from typing import List, Dict
def reciprocal_rank_fusion(
dense_results: List[str],
sparse_results: List[str],
k: int = 60
) -> List[Dict[str, float]]:
"""
Combines dense vector search results and sparse BM25 search results
using Reciprocal Rank Fusion (RRF).
"""
rrf_scores: Dict[str, float] = {}
# Score Dense Results
for rank, doc_id in enumerate(dense_results):
if doc_id not in rrf_scores:
rrf_scores[doc_id] = 0.0
rrf_scores[doc_id] += 1.0 / (k + (rank + 1))
# Score Sparse Results (BM25)
for rank, doc_id in enumerate(sparse_results):
if doc_id not in rrf_scores:
rrf_scores[doc_id] = 0.0
rrf_scores[doc_id] += 1.0 / (k + (rank + 1))
# Sort documents by descending fusion score
sorted_docs = sorted(
rrf_scores.items(), key=lambda item: item[1], reverse=True
)
return [{"doc_id": doc, "score": score} for doc, score in sorted_docs]
Чому реляційні бази повертають собі навантаження
Потреба в гібриді тягне навантаження назад до традиційних баз. PostgreSQL (з pgvector і pg_trgm), Elasticsearch і SingleStore дають змогу виконувати векторні запити поруч з операційними метаданими, реляційними джойнами та ACID-транзакціями, без окремої векторної бази, яку треба обслуговувати.
- PostgreSQL Hybrid Query: Vector Distance + Full Text Match + Relational Filter
SELECT
id,
title,
(ts_rank(text_search_vector, websearch_to_tsquery('english', 'liability clause')) * 0.5) +
((1 - (embedding <=> '[0.012, -0.043, …]')) * 0.5) AS hybrid_score
FROM enterprise_documents
WHERE tenant_id = 'org_99412'
AND status = 'ACTIVE'
ORDER BY hybrid_score DESC
LIMIT 10;
Висновки
Векторні бази нікуди не зникнуть. Скінчилася звичка бездумно запихати кожен чанк документа в HNSW-індекс у пам'яті. Чотири правила на 2026 рік:
- Не розв'язуйте задачі точного збігу самими векторами. Якщо користувачі шукають за SKU, ім'ям чи синтаксисом коду, від самого початку додавайте до векторів BM25 або інвертований індекс.
- Стежте за рахунком за пам'ять HNSW. Після приблизно 10M векторів придивіться до дискових індексів на кшталт DiskANN від Microsoft або до бінарного квантування, поки витрати не вийшли з-під контролю.
- Використовуйте вектори для маршрутизації, а не для читання. Замість крихітних чанків по 200 токенів обирайте три–п'ять цілих документів по 50k токенів і більше та віддавайте їх LLM із довгим контекстом.
- Консолідуйте. Якщо у вас не масштаб у мільярди векторів із SLA менше 10 мс, вашої нинішньої реляційної бази, наприклад PostgreSQL з
pgvector, найімовірніше, вистачить, і ви позбудетеся багів синхронізації розподіленої системи.