Files
contracts/History/opus-classify-async-answer.md
T

101 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ответ Опуса — фоновая классификация при 50+ файлах
Ответ на `History/opus-classify-async-question.md` от 25.06.2026.
---
## Диагноз: что рвётся на самом деле
**nginx НЕ виноват.** `classify-batch` отдаёт `202` мгновенно. `batch-progress` — короткие независимые запросы, укладываются в 30с. Классификация идёт `classify_worker → api.aillm.ru` напрямую, минуя nginx.
Реальное узкое место — **симулятор сдаётся на 4-й минуте** (120 итераций × 2с), а 69 файлов при 4 воркерах не успевают: 69 / 4 ≈ 18 волн × 10-15с/LLM-вызов = 3-5 минут > лимита симулятора.
---
## Ответы
### Q1. `subprocess.Popen` для прода — норм?
Для масштаба сотни-тысячи файлов регулярно `Popen`-на-запрос **неадекватен**:
- Нет супервизии: упал worker — никто не узнал (stdout/stderr в DEVNULL)
- Нет авто-рестарта
- Гонки при параллельных батчах с одним `batch_id`
**Целевая архитектура:** постоянный worker-сервис под отдельным systemd-юнитом + очередь задач на основе БД.
Почему БД-очередь, а не Redis:
- Work items уже в БД (`documents.classify_status='pending'`)
- Устойчиво к рестартам ВМ — resumable
- Redis не установлен в проде
- Для одной ВМ Celery/RQ — оверкилл
Дополнительно при тысячах файлов:
- retry/backoff на 429/5xx от api.aillm.ru
- rate-limit к LLM API
- Аккуратное повышение параллелизма (не 4 воркера, а 10-15)
### Q2. nginx при долгих запросах
nginx НЕ узкое место. classify-batch=202, batch-progress<30s, классификация идёт worker→LLM напрямую. Реальное узкое: throughput (4 воркера) + лимит симулятора. Прогресс durable в БД, UI не зависит от дедлайна.
### Q3. Два classify подряд — два Popen
- **Разные `batch_id`** — безопасно. Каждый процесс работает только над своими документами.
- **Один и тот же `batch_id` дважды** — гонка! Оба сделают `reset_classify_status` + повторные LLM-вызовы → двойные траты токенов, неконсистентные счётчики.
**Решение:** НЕ убивать предыдущий процесс (опасно — может испортить БД). Guard через проверку: если для `batch_id` уже есть running-процесс → вернуть `{"ok": false, "error": "already running"}`. Реализация: либо файл-лок (`/tmp/classify_<batch_id>.lock`), либо запись в БД.
### Q4. Мониторинг/логирование
DEVNULL недопустим на масштабе — падения невидимы.
**Минимум сейчас:** лог-файл per batch `/home/naeel/contracts/logs/classify_<batch_id>.log` (Python logging) вместо DEVNULL.
**Целевое:** job-таблица в БД:
```sql
CREATE TABLE classify_jobs (
batch_id UUID PRIMARY KEY,
started_at TIMESTAMP,
finished_at TIMESTAMP,
total INT, done INT, failed INT,
error_text TEXT,
pid INT
);
```
Даёт: честный прогресс, видимость падений, per-doc retry, аудит.
---
## Целевая архитектура (будущее)
```
systemd: contracts.service (HTTP)
systemd: classify-worker.service (фоновая классификация)
flow:
HTTP → 202 + запись в classify_jobs (status='queued')
classify-worker (постоянно):
SELECT batch_id FROM classify_jobs WHERE status='queued' LIMIT 1
→ status='running'
→ classify_batch(batch_id)
→ status='done' + метрики
→ следующий батч
Прогресс: documents.classify_status (pending/classified/failed)
Поллинг: GET /api/batch-progress?batch=X → count by status
```
Преимущества:
- Устойчиво к рестартам (состояние в БД)
- Один процесс обрабатывает батчи последовательно — нет гонок
- systemd мониторит и рестартует при падении
- Логи systemd/journald
---
## Минимум сейчас (без переписывания архитектуры)
1. **Лог-файл вместо DEVNULL:** `stdout=open(log_path, 'w')`
2. **Лимит симулятора:** увеличить `MAX_WAIT` до 600с (10 мин) для bulk-тестов
3. **Guard от двойного classify:** файл-лок `/tmp/classify_<batch_id>.lock` — если существует, вернуть "already running"