Архитектурные компромиссы¶
Три роли из одного package¶
Выбрано: crawler, indexer, api из одного image.
Плюс: мало дублирования, одна dependency graph, понятный запуск.
Цена: роли нельзя независимо версионировать. Для тестового и одного владельца это разумнее трёх репозиториев, каждый из которых торжественно содержит по четыре файла.
Общий PostgreSQL¶
Выбрано: одна БД и схема.
Почему: отдельные БД потребовали бы catalog API, синхронизацию и eventual consistency.
Цена: процессные роли связаны схемой. При росте первым кандидатом на выделение будет read model/search storage.
NATS только для индексации¶
Выбрано: один durable subject.
Почему: медленная индексация должна переживать перезапуск; Search не выигрывает от broker hop.
Цена: NATS используется узко, зато не натянут на обычный SQL read только ради слова в резюме.
Нет Redis¶
Выбрано: PostgreSQL fixed-window limiter с row lock.
Плюс: один state store.
Цена: boundary burst и меньшая масштабируемость. При высокой нагрузке разумная замена — Redis sliding window/token bucket.
Общий volume вместо S3/MinIO¶
Выбрано: Docker volume.
Плюс: нулевая дополнительная инфраструктура на одном host.
Цена: crawler/indexer нельзя разнести по разным hosts без BlobStorage adapter.
Exact semantic search¶
Выбрано: cosine scan без HNSW/IVFFlat.
Плюс: точная выдача и отсутствие index tuning на малом корпусе.
Цена: линейная стоимость. Approximate index добавляется после измерения latency/corpus size.
Нет hybrid FTS¶
Выбрано: только embeddings.
Плюс: минимально закрывает RAG.
Цена: номера закупок, CPV и артикулы могут искаться хуже. Следующий этап — PostgreSQL FTS + RRF с benchmark, а не ритуальное добавление Elasticsearch.
Fake AI в CI¶
Выбрано: deterministic hashing embeddings и детерминированный ответ.
Плюс: быстрые и воспроизводимые тесты без model pull.
Цена: качество реальной модели проверяется отдельным live smoke.
Нет OCR¶
Выбрано: PDF только с text layer.
Плюс: меньше тяжёлых зависимостей и ложной уверенности в распознанном тексте.
Цена: сканы скачиваются, но не индексируются.
Нет outbox/inbox¶
Выбрано: pending + republish и indexed_hash + redelivery.
Плюс: минимальная реализация закрывает конкретный event flow.
Цена: это не универсальная exactly-once схема. При появлении нескольких типов событий или внешних consumers нужен transactional outbox.
Статический UI¶
Выбрано: HTML/CSS/JS и fetch.
Плюс: нет npm build, CDN и лишнего state management.
Цена: при появлении нескольких экранов интерфейс следует перенести в Angular standalone/signals, который автор проекта уже знает. Сейчас framework решил бы проблему, которой пока нет.