Тихая катастрофа: как enable_auto_commit=True уничтожил RAG-базу без единой ошибки
Параметр enable_auto_commit=True в Kafka привёл к безвозвратной потере документов из RAG-пайплайна. Разбираю механику сбоя, объясняю почему стандартный try/except не спасает, и даю конкретные паттерны защиты.
Худший баг — тот, которого не видно
RAG-пайплайн работает. Логи чистые. Мониторинг молчит. Пользователи загружают документы — они появляются в хранилище.
А потом кто-то ищет документ, загруженный вчера. Его нет в индексе. И позавчерашнего нет. И за прошлую неделю — тоже.
Документы физически лежат в MinIO. Но в векторную базу они никогда не попали. Kafka честно отметил их как «обработанные». Qdrant их никогда не видел. Именно это произошло в open-source RAG-пайплайне Aegis — один конфигурационный параметр молча уничтожал данные на протяжении неизвестного времени.
Архитектура: где прячется проблема
Стандартный production RAG: Spring Boot gateway принимает файл в MinIO → Kafka доставляет событие Python-воркеру → ML worker генерирует эмбеддинги → Qdrant принимает векторы.
Проблема возникла на стыке воркера и Qdrant. Но сначала был первый баг, запустивший цепочку.
Первый баг: 10 МБ → 62 МБ за одну строку кода
Наивный сплит text.split(" ") при бинарном файле на 10 МБ создавал один гигантский «чанк» — в бинарных данных нет пробелов. При JSON-сериализации каждый нулевой байт превращается в экранированную Unicode-последовательность — 6 символов вместо 1. Множитель 6x: 10 МБ × 6 = 60 МБ + вектор и заголовки = 62 922 836 байт.
Лимит Qdrant REST API — 32 МБ. HTTP 400. Соединение разорвано.
Исправление: RecursiveCharacterTextSplitter из LangChain. Разрезает по абзацам → переносам строк → пробелам, сохраняя логические границы и гарантируя предсказуемый размер чанков.
Второй баг: настоящая катастрофа
Конфигурация Kafka-консьюмера содержала параметр по умолчанию: enable_auto_commit = True.
Что это значит: Kafka автоматически, по таймеру, сдвигает offset — отмечая сообщение как «обработанное» — независимо от того, завершилась ли обработка успешно.
Что происходило при каждом сбое:
- Kafka доставляет сообщение воркеру
- Воркер пытается сделать upsert в Qdrant → 400 Bad Request
- Блок except логирует ошибку
- Таймер auto-commit срабатывает и сдвигает offset
- Kafka считает документ обработанным, повторной доставки не будет — никогда
Документ существует в MinIO. В векторной базе его нет. Поиск его не найдёт. Никакой системе об этом не известно.
Почему try/except здесь не работает
Стандартный try/except ловит исключение, логирует его и продолжает работу. В контексте Kafka с auto-commit «продолжить работу» означает «потерять сообщение навсегда». Лог-запись существует, но в production с тысячами документов в день строка «Failed to process document abc123» утонет в шуме. Retry-механизма нет. Документ исчез.
Решение: Dead Letter Queue
При любой ошибке событие уходит в отдельный топик aegis.documents.failed вместе с оригинальным JSON-событием, полным стектрейсом и Correlation ID. Когда инфраструктура восстанавливается — DLQ проигрывается заново. Ноль потерь данных.
Пять паттернов защиты от деструктивных дефолтов
1. Отключайте auto-commit, управляйте offset вручную
enable_auto_commit=False + consumer.commit() только после успешной обработки. При исключении — send_to_dlq(message), без коммита.
2. DLQ для каждого консьюмера
Не бывает пайплайна, где ошибки невозможны. Каждый Kafka-консьюмер должен иметь DLQ — это требование, не опция.
3. Мониторьте расхождение хранилище vs индекс
Периодически сравнивайте количество документов в object store с проиндексированными в векторной базе. Расхождение > 0 — алерт.
4. Не доверяйте дефолтам брокеров
enable_auto_commit → False, acks → all, enable.idempotence → True. Дефолты Kafka оптимизированы для throughput, а не для надёжности.
5. Хаос-тестирование обязательно
Убейте базу данных во время записи. Оборвите сеть во время эмбеддинга. Если пайплайн теряет данные — он не готов к production.
Бонусные грабли из того же проекта
- Тег :latest в Docker — MinIO-бакет исчез после обновления, потому что mc latest обновился молча. Вывод: пинить версии всегда.
- Зависший Kafka-тред — CTRL+C не останавливал сервер: консьюмер в run_in_executor() игнорировал SIGINT. Нужен threading.Event() как kill switch.
- Запятая в имени файла — Windows curl падал на "Book, Version 2.pdf": запятая интерпретировалась как разделитель HTTP-заголовков.
Выводы
enable_auto_commit=True — не баг Kafka. Это дефолт ради простоты. В туториалах работает идеально. Production — не туториал.
Самый опасный баг — не тот, который кладёт систему. А тот, который работает тихо, пока кто-то не заметит, что половина документов исчезла.
Проверьте свои Kafka-консьюмеры прямо сейчас. Grep по enable_auto_commit. Если видите True или не видите параметр вообще (дефолт — True) — у вас потенциальная бомба замедленного действия.
- RAG
- Kafka
- Qdrant
- LangChain
- векторная база
- Dead Letter Queue
- отказоустойчивость
- продакшн