Collector daemon, FastAPI server (addresses, diff, collect, sources), SQLite storage with change journal, Docker Compose deployment, tests, documentation and project rules. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.3 KiB
План: разделение сборщика и API на два процесса
Context
Сейчас планировщик (APScheduler) живёт внутри процесса API: перезапуск или падение API останавливает сбор, а тяжёлый сбор делит процесс с обработкой запросов. Плюс ASN и FQDN можно менять только через CLI и правкой config.json.
Запрос состоит из двух частей, порядок выполнения важен, так как часть B опирается на часть A:
- Часть A - разделение на два процесса: планируется и выполняется после утверждения плана.
- Часть B - управление ASN и FQDN через API: только план. Реализация начнётся отдельной командой после части A.
Артефакты по правилам проекта: docs/plan-process-split.md и docs/plan-asn-fqdn-api.md (копии соответствующих частей плана, первым шагом), затем docs/summary-process-split.md (после части A) и обновление README.md.
Часть A. Разделение на два процесса (выполняется)
Архитектура
Два независимых сервиса без IPC, общение через файлы в каталоге проекта (уже атомарные и под flock):
- API (
uvicorn api_server:app): только читает данные и пишетconfig.json; планировщика в нём больше нет. - Сборщик-демон (
python collector_daemon.py, новый): держит расписание, запускает сбор, пишетstatus.json.
Изменение расписания через POST /schedule доходит до демона через config.json: демон раз в 30 секунд сверяет секцию schedule и перепланирует задания без перезапуска (задержка применения до 30 с, документируется).
Изменения
collector_daemon.py(новый)BlockingScheduler, заданияasn_jobиfqdn_jobизschedule(значения по умолчанию как сейчас:0 2 * * */0 3 * * *),max_instances=1,coalesce=True.- Задание
sync_scheduleкаждые 30 с: читаетload_full_config(), при изменении cron делаетreschedule_job; невалидный cron логируется и игнорируется (остаётся прежнее расписание). run_job(name, collector_cls)переезжает изapi_server.py(CIDRCollector/FQDNCollectorсоздаются заново на каждый запуск - свежий конфиг); статусыlast_run,last_error,next_runпишутся вstatus.jsonатомарно вместе сupdated_at(heartbeat каждые 30 с).- Единственный экземпляр: неблокирующий
flockнаcollector.daemon.lock; второй экземпляр завершается с понятной ошибкой. - Корректное завершение по
SIGTERM/SIGINT(scheduler.shutdown).
storage.py: добавитьtry_lock(path)(неблокирующая эксклюзивная блокировка для singleton).cidr_collector.py: константаSTATUS_FILEрядом с остальными путями (единая точка для API и тестов).api_server.py- Убрать
BackgroundScheduler,lifespan,job_state,run_*_job,JOBS,start_scheduler. POST /schedule: валидация cron черезCronTrigger.from_crontab(как сейчас), запись вconfig.jsonпод блокировкой; ответ уточняет, что применение демоном до 30 с.GET /health: читаетstatus.json;collector_alive = now - updated_at < 120 с;status = okтолько если демон жив и нетlast_error, иначеdegraded(нет файла = демон не запускался). HTTP-код остаётся 200.
- Убрать
- Развёртывание (
README.md,.gitignore,.dockerignore)- Второй сервис
ripe-collectorдля systemd и OpenRC (та же учётная записьripe,ExecStart=.../python collector_daemon.py,Restart=always); юниты API и сборщика независимы. - Раздел про cron остаётся как альтернатива ручного запуска (
cidr_collector.py run), но основной путь - демон. - Примечание об обновлении: после разделения одного
ripe-apiнедостаточно, безripe-collectorсбор не идёт;/healthпокажетdegraded. - Переписать раздел «Scheduler Logic» под новую схему; добавить
status.jsonв.gitignoreи.dockerignore.
- Второй сервис
Тесты (минимум, в контейнере)
sync_schedule: смена cron вconfig.jsonперепланирует задание; невалидный cron игнорируется (планировщик без запуска, проверка триггера задания)./health: свежийstatus.json->collector_alive: true, устаревший или отсутствующий ->falseиdegraded.
Проверка
docker build -f Dockerfile.test -t ripe-collector-test . && docker run --rm ripe-collector-test- все тесты (прежние 9 + новые) проходят.- Вручную на копии данных в scratchpad (реальные данные не трогаем): запустить демон с расписанием
*/1 * * * *и API в двух процессах; убедиться, что сбор идёт без API;POST /scheduleс токеном меняет расписание демона не позднее чем через 30 с (по логу и/health); остановка демона ->/healthчерез 2 минутыdegraded,collector_alive: false; второй запуск демона завершается с ошибкой singleton;kill -TERMостанавливает демон чисто. - Перезапуск API во время работы демона не прерывает сбор.
Критичные файлы
collector_daemon.py (новый), api_server.py, cidr_collector.py, storage.py, README.md, .gitignore, .dockerignore, tests/. Переиспользуем: load_full_config, save_json_atomic, file_lock, load_json, verify_token, merge_entry.
Порядок выполнения после утверждения
- Скопировать планы в
docs/. - Выполнить часть A (код, тесты, проверка, README,
docs/summary-process-split.md). - Часть B не реализуется, пока не будет отдельной команды.