Перейти к содержанию

Архитектурные компромиссы

Три роли из одного 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.

Выбрано: 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 решил бы проблему, которой пока нет.