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

5.2 KiB
Raw Blame History

Ответ Опуса — фоновая классификация при 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-таблица в БД:

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"