YDB векторный поиск в RAG: архитектура Нейроюриста для миллионов документов | AiManual
AiManual Logo Ai / Manual.
11 Мар 2026 Гайд

Архитектура векторного поиска Нейроюриста: как YDB обрабатывает миллионы документов в RAG

Глубокий разбор архитектуры векторного поиска Нейроюриста от Яндекса. Как YDB обрабатывает миллионы юридических документов с фильтрацией, настройка HNSW индексо

Проблема: как искать иголку в стоге юридических документов?

Представьте: 3.2 миллиона юридических документов. Судебные решения, договоры, нормативные акты. Каждый день добавляются тысячи новых. К вам приходит юрист и спрашивает: "Найдите все решения по делам о банкротстве за 2025 год, где сумма иска превышала 100 миллионов рублей, и которые касаются строительных компаний".

Обычный RAG с этим не справляется. Почему? Потому что вам нужно одновременно:

  • Искать по семантическому смыслу ("банкротство", "строительные компании")
  • Фильтровать по точным метаданным (год: 2025, сумма > 100 млн)
  • Делать это за секунды, а не минуты
  • Не терять точность при масштабировании

Традиционные подходы ломаются. Векторная БД ищет по смыслу, но плохо фильтрует. Реляционная БД отлично фильтрует, но не ищет по смыслу. Соединять их - получаете ад из декартовых произведений и запросов, которые выполняются вечность.

Вот типичная ошибка: сначала делаете SQL-фильтрацию, потом векторный поиск по результатам. На 3.2 млн документов с фильтром, который оставляет 50 тыс., векторный поиск все равно работает со всеми 50 тыс. векторами. Медленно. Очень медленно.

Почему обычный RAG с фильтрацией ломается на миллионах документов?

Потому что архитектура не масштабируется. Возьмем популярный подход: храним векторы в Pinecone/Weaviate/Qdrant, метаданные - в PostgreSQL. Запрос приходит, вы:

  1. Фильтруете метаданные в PostgreSQL
  2. Получаете список ID документов
  3. Делаете векторный поиск только по этим ID

Звучит логично? На практике - нет. Потому что векторные БД оптимизированы для поиска по всем векторам. Поиск по подмножеству часто работает МЕДЛЕННЕЕ, чем по всему набору. Особенно если у вас составные фильтры с диапазонами.

Именно с этой проблемой столкнулась команда Нейроюриста. Их решение - YDB с нативной поддержкой векторных индексов. Не гибридная система, а единая база, которая умеет и фильтровать, и искать по векторам одновременно.

Решение: YDB и его векторные индексы

YDB (Yandex Database) - распределенная SQL-база с поддержкой векторных индексов. На 2026 год актуальная версия - YDB 2.4, которая включает серьезные улучшения для векторного поиска:

  • HNSW индексы с поддержкой фильтрации во время поиска
  • Автоматическое управление памятью для векторных индексов
  • Поддержка эмбеддингов размерностью до 4096 (актуально для новых моделей типа text-embedding-3-large)
  • Распределенное выполнение векторных запросов

Ключевая фишка: YDB позволяет делать запросы типа:

SELECT document_id, document_text
FROM legal_documents
WHERE year = 2025
  AND claim_amount > 100000000
  AND industry = 'construction'
ORDER BY vector_distance(embedding, ?) ASC
LIMIT 10

И этот запрос выполняется эффективно. Не фильтрация потом поиск, не поиск потом фильтрация. А единый план выполнения, который учитывает и фильтры, и векторную близость.

💡
Если интересно, как устроен векторный поиск без специализированных БД, посмотрите мою статью про RAG без векторной БД. Но для продакшена на миллионах документов такой подход не подойдет.

Архитектура векторного поиска Нейроюриста

Давайте разберем по косточкам, как устроена система. Не теория, а конкретная архитектура, которая работает в продакшене.

1Подготовка документов и создание эмбеддингов

Первое - выбор модели. В 2026 году ландшафт embedding-моделей изменился. OpenAI text-embedding-3-large дает 3072-мерные векторы (можно урезать до 256, 512, 1024). Но Нейроюрист использует собственную модель Yandex Embeddings v3, оптимизированную для юридических текстов на русском языке.

Размерность: 1024. Почему не больше? Потому что с ростом размерности:

  • Увеличивается объем хранилища (в 2 раза на каждый скачок размерности)
  • Замедляется поиск (расстояния считаются дольше)
  • HNSW индекс требует больше памяти

Для юридических документов 1024-мерных эмбеддингов достаточно. Проверено на выборке из 500 тыс. документов: качество поиска (nDCG@10) отличается от 2048-мерных всего на 0.03.

Паблишинг документов происходит так:

# Упрощенный код пайплайна индексации
def index_document(document_text, metadata):
    # Генерация эмбеддинга (Yandex Embeddings API v3)
    embedding = yandex_embeddings.encode(document_text)
    
    # Подготовка метаданных
    doc_id = generate_uuid()
    
    # Вставка в YDB
    with ydb_pool.acquire() as connection:
        connection.execute(
            """
            UPSERT INTO legal_documents 
            (doc_id, text, embedding, year, court_type, 
             claim_amount, industry, region)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """,
            doc_id, document_text, embedding,
            metadata['year'], metadata['court_type'],
            metadata['claim_amount'], metadata['industry'],
            metadata['region']
        )

2Настройка векторного индекса HNSW в YDB

Вот где начинается магия. Создание индекса в YDB 2.4:

CREATE TABLE legal_documents (
    doc_id Utf8,
    text Utf8,
    embedding List,
    year Int32,
    court_type Utf8,
    claim_amount Double,
    industry Utf8,
    region Utf8,
    PRIMARY KEY (doc_id)
) WITH (
    AUTO_PARTITIONING_BY_SIZE = ENABLED,
    AUTO_PARTITIONING_PARTITION_SIZE_MB = 2048
);

-- Векторный индекс HNSW
ALTER TABLE legal_documents ADD INDEX vector_idx 
    GLOBAL ON (embedding) 
    HNSW WITH (
        DISTANCE_CALCULATION = 'Cosine',
        M = 32,          -- Количество соседей на уровне
        EF_CONSTRUCTION = 200,  -- Параметр для построения
        EF_SEARCH = 100   -- Параметр для поиска
    );

-- Обычные индексы для фильтрации
ALTER TABLE legal_documents ADD INDEX year_idx GLOBAL ON (year);
ALTER TABLE legal_documents ADD INDEX industry_idx GLOBAL ON (industry);

Параметры HNSW критически важны:

ПараметрЗначениеЧто делаетЧто будет если изменить
M32Связность графаБольше = точнее, но медленнее поиск
EF_CONSTRUCTION200Качество построенияБольше = лучше индекс, но дольше построение
EF_SEARCH100Точность поискаБольше = точнее, но медленнее

Для 3.2 млн документов индекс занимает около 120 ГБ памяти. Это много? Да. Но YDB распределяет индекс по кластеру. И что важнее - память сейчас дешевле, чем время юристов.

3Интеграция с RAG-пайплайном

Поисковый запрос обрабатывается так:

async def rag_search(query: str, filters: dict):
    # Шаг 1: Эмбеддинг запроса
    query_embedding = await embed_query(query)
    
    # Шаг 2: Поиск с фильтрацией в YDB
    documents = await ydb_search(
        query_embedding=query_embedding,
        filters=filters,
        limit=20
    )
    
    # Шаг 3: Ранжирование и ре-ранжирование
    if len(documents) > 10:
        # Используем кросс-энкодер для точного ранжирования
        documents = rerank_with_cross_encoder(documents, query)
    
    # Шаг 4: Контекст для LLM
    context = "\n\n".join([d['text'] for d in documents[:5]])
    
    # Шаг 5: Генерация ответа
    response = await llm_generate(
        model="yandexgpt-3",
        prompt=build_prompt(query, context)
    )
    
    return {
        "answer": response,
        "sources": documents[:5],
        "search_metrics": {...}
    }

def ydb_search(query_embedding, filters, limit):
    # Динамическое построение WHERE-части
    where_clauses = []
    params = [query_embedding]
    
    if "year" in filters:
        where_clauses.append("year = ?")
        params.append(filters["year"])
    
    if "min_amount" in filters:
        where_clauses.append("claim_amount >= ?")
        params.append(filters["min_amount"])
    
    # ... другие фильтры
    
    where_sql = " AND ".join(where_clauses) if where_clauses else "1=1"
    
    sql = f"""
        SELECT doc_id, text, year, court_type, 
               claim_amount, industry,
               COSINE_DISTANCE(embedding, ?) as distance
        FROM legal_documents
        WHERE {where_sql}
        ORDER BY distance ASC
        LIMIT ?
    """
    
    params.append(limit)
    return execute_sql(sql, params)

Обратите внимание: фильтры применяются ВО ВРЕМЯ векторного поиска. Не до, не после. YDB использует алгоритм, который сочетает обход HNSW-графа с проверкой условий WHERE.

4Фильтрация по метаданным без потери скорости

Самое сложное - когда фильтры сильно сокращают выборку. Например, "2025 год, Московский арбитражный суд, сумма иска > 1 млрд". Таких документов может быть всего 50 из 3.2 млн.

Наивный подход: сначала фильтрация, потом векторный поиск. Но YDB умнее. Он использует статистику по индексам, чтобы решить:

  • Если фильтр очень селективный (оставляет < 0.1% документов) - сначала фильтрация
  • Если фильтр неселективный - совмещенный поиск
  • Если вообще нет фильтров - чистый векторный поиск

План запроса можно посмотреть через EXPLAIN. И да, иногда приходится подсказывать оптимизатору через хинты.

Про битву между SQL и векторным поиском я писал в статье "Когда SQL и векторный поиск дерутся за ваши данные". В YDB эта битва закончилась перемирием.

Бенчмарки: что получилось у Яндекса

Цифры на 2026 год для кластера YDB из 12 нод (CPU: Intel Sapphire Rapids, RAM: 256 ГБ на ноду, SSD NVMe):

МетрикаБез фильтровС 1 фильтромС 3 фильтрамиТрадиционный подход (2 БД)
Среднее время поиска (p95)47 мс52 мс68 мс210 мс
Пропускная способность (запросов/сек)12501150890380
Точность (nDCG@10)0.870.860.850.84
Загрузка CPU42%45%51%78%

Разница в 3-4 раза по скорости. Почему так?

  1. Нет network hops между БД
  2. Единый план выполнения
  3. Оптимизированная работа с памятью
  4. Распределение нагрузки по нодам

Но есть нюанс: при очень селективных фильтрах (оставляющих < 0.01% документов) разница меньше. Потому что в традиционном подходе PostgreSQL быстро фильтрует маленькую выборку.

Ошибки, которые все делают (и как их избежать)

Ошибка 1: Слепая вера в дефолтные параметры HNSW

Берете M=16, ef_construction=200, ef_search=100 как "стандартные". Для 3 млн документов это слишком мало. Индекс будет быстрым, но неточным. А для юридических документов точность критична.

Как правильно: Тестируйте на своей data. Возьмите 1000 запросов с ground truth. Замерьте recall@k для разных параметров. На 3+ млн документов стоит начинать с M=32, ef_construction=300, ef_search=150.

Ошибка 2: Не мониторить деградацию индекса

HNSW индекс не обновляется автоматически при upsert. Новые векторы добавляются, но структура графа не перестраивается. Со временем поисковая точность падает. Я видел падение recall@10 с 0.85 до 0.72 за 3 месяца при активной записи.

Как правильно: Регулярно (раз в неделю) замеряйте качество на контрольной выборке. Если падает больше чем на 0.03 - перестраивайте индекс. В YDB 2.4 есть фоновая реорганизация индексов, но она экспериментальная.

Ошибка 3: Игнорировать распределение фильтров

Если 80% запросов содержат фильтр "court_type = 'arbitration'", а 20% - нет, то нужно соответствующим образом настроить индексы. И возможно партиционирование.

Как правильно: Анализируйте логи поиска. Стройте heatmap частотности фильтров. Для часто используемых комбинаций создавайте composite индексы или материализованные представления.

О деградации RAG при росте базы я подробно писал в отдельной статье. Проблема реальная, и YDB не делает вас от нее иммунными.

Ошибка 4: Экономить на памяти

HNSW индекс для 3 млн векторов размерностью 1024 занимает ~120 ГБ. "Давайте поставим на ноды по 128 ГБ RAM, будет впритык". Не делайте так. При нагрузке индекс будет вытесняться в swap, и поиск замедлится в 100 раз.

Как правильно: Память = размер индекса * 2.5. Для 120 ГБ индекса нужно 300 ГБ RAM на кластер. Да, это дорого. Но дешевле, чем потерянные клиенты из-за медленного поиска.

FAQ по векторному поиску в YDB

Q: YDB - это только для Яндекс Облака? Можно ли поставить on-prem?

Можно. YDB открыта под лицензией Apache 2.0. Но готовьтесь: развертывание on-prem кластера из 10+ нод - это минимум 2 недели работы опытного SRE. Или можно использовать YDB в Яндекс Облаке - managed-сервис, где за вас все настраивают.

Q: Какие альтернативы YDB для такого сценария?

На 2026 год:

  • PostgreSQL + pgvector + pg_embedding. Хорошо для < 1 млн документов
  • Elasticsearch с векторным поиском. Сильно проигрывает в точности
  • ClickHouse + специальные расширения. Быстро, но сложно с обновлениями
  • Специализированные векторные БД (Weaviate, Qdrant) + отдельная SQL БД. Архитектурно сложнее

Q: Как мигрировать с существующего стека (например, PostgreSQL + Pinecone)?

Поэтапно:

  1. Начинаете параллельную запись в обе системы
  2. Переводите на чтение non-critical трафик
  3. Сравниваете метрики качества и скорости
  4. Переводите весь трафик
  5. Отключаете старую систему через 2 недели

Миграция 3 млн документов занимает 12-18 часов при правильной настройке batch-вставок.

Q: Что с безопасностью? Юридические документы часто конфиденциальны

YDB поддерживает:

  • Шифрование на rest (включено по умолчанию в managed-версии)
  • RBAC с интеграцией с Yandex Cloud IAM
  • Audit log всех операций
  • VPC изоляцию

Для особо параноидальных: можно развернуть air-gapped кластер без выхода в интернет.

Q: Какой бюджет нужен для такой системы?

Для масштаба Нейроюриста (3.2 млн документов, 1000 запросов в минуту):

  • YDB Managed: ~$8500/месяц
  • Yandex Embeddings API: ~$1200/месяц (по $0.00038 за 1К токенов)
  • YandexGPT API для генерации: ~$5000/месяц
  • Инфраструктура (мониторинг, бэкапы): ~$1000/месяц

Итого: ~$15-17К в месяц. Дорого? Да. Но один успешный судебный процесс, который выиграли благодаря системе, окупает год эксплуатации.

Что дальше?

Архитектура Нейроюриста - не конечная точка. На горизонте:

  • Квантованные индексы в YDB (обещают в 2.5 версии) - сокращение памяти в 4 раза
  • Learned индексы - когда нейросеть предсказывает расположение векторов
  • Мультимодальный поиск - не только текст, но и сканы документов с таблицами, графиками

Но самый интересный тренд - отказ от универсальных embedding-моделей. В Нейроюрите уже экспериментируют с моделью, которая обучается на feedback loop: какие документы юристы реально выбирают после поиска. Через 6 месяцев такая модель обгоняет универсальную на 0.15 по nDCG.

Мой совет: если вы строите RAG на миллионах документов с фильтрацией - смотрите в сторону единой базы типа YDB. Гибель гибридных архитектур неизбежна. Просто потому, что сложность их поддержки растет экспоненциально с масштабом.

А если у вас пока тысячи документов - не переусложняйте. Начните с PostgreSQL и pgvector. Когда упретесь в limitations - мигрируете. Главное - проектируйте систему так, чтобы миграция была возможна без переписывания всего кода.

P.S. Если хотите глубже понять математические ограничения векторного поиска, почитайте мою статью про математический потолок RAG. Там нет магии, только hard math.

Подписаться на канал