RAG для 2-4 млн документов: локальная архитектура, OCR, безопасность | AiManual
AiManual Logo Ai / Manual.
16 Янв 2026 Гайд

Локальный RAG для 4 миллионов PDF: как не сломать сервер и не потерять данные

Пошаговое руководство по созданию локального RAG-поисковика для миллионов PDF с OCR. Архитектура, инструменты, безопасность данных и оптимизация производительно

Почему 4 миллиона документов — это не просто "чуть больше"

Представьте: у вас есть архив. Не просто папка с файлами, а настоящий цифровой склад — 2-4 миллиона PDF, сканов, договоров, отчётов. Каждый день к нему обращаются десятки людей, и каждый раз они тратят часы на поиск нужной информации. Классический поиск по тексту уже не работает — он не понимает смысла, не находит синонимы, пропускает сканированные документы.

Решение очевидно — RAG (Retrieval-Augmented Generation). Но все облачные решения отпадают сразу: конфиденциальность данных, стоимость API-вызовов для такого объёма, зависимость от интернета. Нужно локальное решение. И вот здесь начинается настоящая инженерия, а не просто "установи библиотеку и запусти".

Главная ошибка новичков: пытаться обработать 4 миллиона документов так же, как 4 тысячи. Разница не линейная, а экспоненциальная. Проблемы с памятью, дисковым I/O, временем индексации возникают неожиданно и ломают всю систему.

Архитектура, которая не упадёт под нагрузкой

Давайте сразу отбросим учебные примеры. В них всё работает в памяти, документы — чистый текст, а поиск — по 100 записям. Наша реальность другая.

1 Слоистая архитектура: разделяй и властвуй

Одна монолитная система для 4 миллионов документов — гарантия падения. Нужно разделить ответственность:

  • Ingestion Pipeline — загрузка и предобработка документов. Работает асинхронно, не блокирует поиск.
  • Vector Store — хранилище эмбиддингов. Отдельный сервис с собственной оптимизацией.
  • Search API — принимает запросы, комбинирует результаты, возвращает ответы.
  • LLM Service — генерация ответов на основе найденных фрагментов.

Каждый слой можно масштабировать независимо. Если индексация тормозит — добавляем воркеров в Ingestion Pipeline. Если поиск медленный — оптимизируем Vector Store.

💡
Используйте message queue (RabbitMQ или Kafka) между слоями. Это даёт буфер при пиковых нагрузках и позволяет перезапускать отдельные компоненты без потери данных.

2 Выбор инструментов: не модные, а рабочие

Здесь много соблазнов взять "самое популярное на GitHub". Не делайте этого. Для 4 миллионов документов нужны инструменты, которые доказали свою устойчивость в production.

Задача Инструмент Почему именно он
OCR для сканов Tesseract 5 + pre-processing Локальный, бесплатный, поддерживает 100+ языков. Ключ — правильная предобработка изображений.
Векторная БД Qdrant или Weaviate Поддерживают фильтрацию по метаданным, горизонтальное масштабирование, эффективное хранение.
Текстовые эмбеддинги BGE-M3 или E5-large-v2 Мультиязычные, хорошее качество, работают на CPU при необходимости.
Локальная LLM Qwen2.5-7B-Instruct или Llama 3.1 8B Баланс качества и требований к ресурсам. Запускаются на GPU с 8-12 ГБ памяти.

Забудьте про FAISS для такого объёма. Он отлично работает для in-memory поиска по нескольким миллионам векторов, но не умеет в персистентное хранение, репликацию, фильтрацию. Вам нужна настоящая база данных.

OCR, который не превратит сканы в абракадабру

Сканированные документы — особая боль. Старые сканы с пятнами, плохим разрешением, наклонным текстом. Стандартный Tesseract на сырых изображениях даст 30% точности. Нам нужно 90%+.

# Не делайте так:
text = pytesseract.image_to_string(image)

# Делайте так:
def enhance_image_for_ocr(image):
    # 1. Конвертация в grayscale
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
    # 2. Удаление шума
    denoised = cv2.fastNlMeansDenoising(gray)
    
    # 3. Повышение контраста (CLAHE)
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
    enhanced = clahe.apply(denoised)
    
    # 4. Бинаризация (adaptive threshold)
    binary = cv2.adaptiveThreshold(enhanced, 255, 
                                   cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                   cv2.THRESH_BINARY, 11, 2)
    
    # 5. Исправление наклона (deskew)
    # ... код для определения угла и поворота
    
    return binary

# Только потом OCR
processed = enhance_image_for_ocr(image)
text = pytesseract.image_to_string(processed, lang='rus+eng')

Этот пайплайн увеличивает точность распознавания в 2-3 раза. Но он медленный. Для 4 миллионов документов нужно распараллеливание.

Используйте GPU-ускорение для OpenCV операций. CUDA-версия OpenCV ускоряет предобработку изображений в 10-50 раз. Для масштабирования добавьте очередь задач и воркер-пул.

Безопасность: когда данные не должны уйти даже случайно

Локальное развёртывание — не гарантия безопасности. Данные могут "утечь" через:

  • Модели, которые загружают что-то в интернет "для улучшения"
  • Логи, которые пишутся в открытые файлы
  • Временные файлы, остающиеся на диске
  • Сетевые порты, открытые для всех в локальной сети

3 Жёсткие меры, которые работают

# Dockerfile с безопасной базой
FROM ubuntu:22.04

# 1. Отключаем телеметрию везде, где можно
ENV HF_HUB_DISABLE_TELEMETRY=1
ENV TRANSFORMERS_OFFLINE=1
ENV LLAMA_NO_METRICS=1
ENV ANONYMIZED_TELEMETRY=false

# 2. Используем только локальные модели
COPY models/ /app/models/

# 3. Запрещаем исходящие соединения (кроме необходимых)
# В docker-compose или Kubernetes NetworkPolicy

# 4. Шифрование данных на диске
VOLUME /encrypted_data

# 5. Запуск от непривилегированного пользователя
RUN useradd -m -u 1000 raguser
USER raguser

Дополнительные меры:

  • Шифрование векторов: храните эмбеддинги зашифрованными. При поиске — расшифровывайте в памяти. Добавляет 5-10% к времени поиска, но защищает от кражи базы.
  • Audit log: кто, когда, какой запрос искал. Пишется в отдельную, защищённую БД.
  • Network isolation: система в отдельном VLAN, доступ только через VPN или jump-host.

Оптимизация производительности: где искать резервы

С 4 миллионами документов каждая миллисекунда имеет значение.

4 Хитрости, которые дают 10x ускорение

  1. Иерархический поиск: сначала быстрый coarse search по уменьшенным векторам (например, 128 вместо 768 измерений), потом точный search только по кандидатам.
  2. Кэширование запросов: 80% запросов повторяются. Redis с TTL 1 час снижает нагрузку на векторную БД в разы.
  3. Пакетная обработка: вместо embedding по одному документу — батчами по 32-128. Экономит вызовы GPU/CPU.
  4. Квантование моделей: INT8 квантование эмбеддинг-модели почти не теряет качество, но ускоряет inference в 2 раза и уменьшает память.
# Пример иерархического поиска в Qdrant
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance

# Создаём две коллекции: coarse (128 dim) и fine (768 dim)
client.create_collection(
    collection_name="coarse_vectors",
    vectors_config=VectorParams(size=128, distance=Distance.COSINE)
)

client.create_collection(
    collection_name="fine_vectors",
    vectors_config=VectorParams(size=768, distance=Distance.COSINE)
)

# Поиск: сначала coarse, потом fine
def hierarchical_search(query_embedding, coarse_embedding):
    # 1. Быстрый поиск по coarse (top 1000)
    coarse_results = client.search(
        collection_name="coarse_vectors",
        query_vector=coarse_embedding,
        limit=1000
    )
    
    # 2. Точный поиск только по кандидатам (top 100)
    candidate_ids = [r.id for r in coarse_results]
    fine_results = client.search(
        collection_name="fine_vectors",
        query_vector=query_embedding,
        query_filter=Filter(must=[
            FieldCondition(key="id", match=Match(any=candidate_ids))
        ]),
        limit=100
    )
    return fine_results

Этот подход сокращает время поиска с 200 мс до 50 мс при 4 миллионах векторов.

Развёртывание: от тестового стенда к production

Не пытайтесь сразу запустить всё на production-серверах. Это путь к катастрофе.

  1. Этап 1: MVP на подмножестве данных — возьмите 10 000 документов, отработайте весь пайплайн. Убедитесь, что OCR работает, поиск находит, LLM отвечает адекватно.
  2. Этап 2: Нагрузочное тестирование — добавьте ещё 100 000 документов. Проверьте, как ведёт себя система при параллельных запросах, сколько памяти ест, не падает ли при длительной индексации.
  3. Этап 3: Полномасштабная индексация — запустите на всех 4 миллионах. Разбейте на батчи, мониторьте прогресс, будьте готовы остановиться и пофиксить проблемы.
  4. Этап 4: Production с мониторингом — добавьте Prometheus метрики, алерты на рост времени ответа, ошибки OCR, заполнение диска.
💡
Используйте incremental indexing. Новые документы должны добавляться без полной переиндексации. В Qdrant/Weaviate это есть из коробки. Для эмбеддингов — кэшируйте уже обработанные документы, чтобы не считать заново при обновлениях.

Что делать, когда всё равно медленно

Даже после всех оптимизаций поиск по 4 миллионам документов — ресурсоёмкая операция. Если нужно быстрее:

  • Гибридный поиск: комбинируйте семантический поиск с классическим BM25. Как в нашем руководстве по гибридному поиску, это даёт +48% точности и можно предфильтровать кандидатов быстрым BM25.
  • Метаданные для фильтрации: если пользователи ищут документы за определённый период или от определённого отдела — добавьте фильтрацию по метаданным ДО семантического поиска. Это сокращает пространство поиска в разы.
  • Аппаратное ускорение: GPU для инференса эмбеддингов, NVMe диски для векторной БД, много ядер для параллельного OCR. Но сначала прочитайте про сравнение железа для локальных LLM — не все GPU одинаково полезны.

Чего ждать в будущем

Через год текущее решение будет выглядеть архаично. Уже сейчас появляются:

  • Специализированные векторные процессоры — в 10-100 раз быстрее GPU для поиска.
  • Кросс-модальные модели — один эмбеддинг для текста, таблиц и изображений в документе.
  • Умное chunking — разбиение документов не по фиксированному размеру, а по смысловым границам.

Но самая большая проблема, с которой столкнётесь вы — не технологии, а данные. Старые сканы с грязным текстом, PDF с картинками вместо текста, документы в 10 разных кодировках. Решение этих проблем — 80% работы. Остальные 20% — выбор и настройка инструментов.

Начните с малого. Возьмите 1000 документов, сделайте работающий прототип. Потом масштабируйте. И помните: идеального решения нет. Есть решение, которое работает достаточно хорошо для ваших конкретных документов и запросов.