Files
EvoBGP/docs/backend-improvement-plan.md
T
DenozordecandCursor 240d83fc61 docs(docs): describe durable jobs, telemetry, and bundle limits
Зафиксированы очередь job, семантика метрик, лимиты бандла и исходящих запросов, а также чеклист продакшена для пула соединений.

Co-authored-by: Cursor <[email protected]>
2026-11-02 17:24:18 +07:00

461 lines
86 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.
# EvoBGP · План развития backend
> **Дата:** 26 октября 2026 · **Основа анализа:** checkout `924f111`
> **Фокус:** достоверная статистика, надёжное применение BGP-конфигураций, устойчивые фоновые задачи, экономное использование VPS.
## 1. Главный вывод
EvoBGP уже имеет содержательное ядро: сбор префиксов из AS/CDN/доменов/ручных списков, материализованные снимки, агрегацию CIDR, ревизии, подписанные бандлы, управление BIRD, удалённые агенты, PostgreSQL-мониторинг и обслуживание БД. Основная задача следующей итерации — укрепить гарантии между этими частями и сделать наблюдаемое состояние достоверным.
**Рекомендуемый порядок:** исправление статусов и выполнения задач → единый сбор телеметрии → снижение фоновой нагрузки → новые операторские функции.
**Обязательное условие:** обновление control plane не должно требовать одновременного обновления удалённых спикеров. Уже развёрнутые старые агенты/CLI должны продолжать получать, проверять и применять совместимые бандлы. Новые возможности включаются по capabilities конкретной ноды; ограничения наблюдаемости показываются явно. Политика перехода и проверки — B08.
| Приоритет | Значение | Правило исполнения |
|---|---|---|
| **P0** | Неверное подтверждение сетевого изменения, небезопасное применение | Выполнить до расширения автоматического deploy |
| **P1** | Потеря/повтор задач, недостоверная статистика, ошибки изоляции и защиты | Ближайший инженерный этап |
| **P2** | Производительность, эксплуатация, полезные функции | После стабилизации зависимостей |
| **P3** | Расширенные возможности | По подтверждённой потребности |
### Границы исследования
Первоначальный анализ исходников, миграций, Compose и CI выполнен тремя субагентами **Astra**. Реализация ведётся субагентами **Sol / medium**, проверка — **Astra**; фактический прогресс отмечен ниже. Production, реальные RSS/CPU/IO, размер опубликованных образов и нагрузка операторов не измерялись. Docker CLI в локальной среде отсутствует. Поэтому проценты экономии и гарантированная вместимость VPS здесь не заявляются.
Найденные при анализе устаревшие описания очереди и Svelte UI в `docs/architecture.md` и `docs/overview.md` исправлены на этапе 0. План учитывает фактическую реализацию и подключённый **ReUI PRO**.
## 2. Что уже есть и что следует сохранить
### Статус реализации · 26.10.2026
**Этап 0 «Проверяемая основа» и первая очередь runtime-исправлений.** Реализация: Sol / medium; независимая проверка: Astra. Старый формат запроса node/sync и формат бандла сохраняются. Полный этап 0 остаётся открытым до стендовых проверок ниже.
- [x] Добавлен CI job `postgres-integration`: временная PostgreSQL 16, последовательное применение миграций и repository tests с `-race`; проверяется успех каждого из 23 обязательных тестов, skip/отсутствие теста не даёт зелёный результат.
- [x] Добавлены тесты повторного применения миграций, конкурентного SQL claim, сохранения строки job при повторном открытии pool и idempotency row; исправлен невалидный tenant fixture в существующем CRUD-тесте.
- [x] Добавлены воспроизведения двух дефектов live-status: stale Established и ложный healthy без poll. После исправления S01 tag `backend_repro` удалён; проверки включены в обязательный набор.
- [x] Зафиксирован локальный baseline совместимости: исторические исходники `v1.0.0`, минимальные node/sync/health wire contracts, независимая проверка подписи/хешей и замороженный signed bundle текущего builder. Подробности: [совместимость](backend-compatibility.md).
- [x] Добавлен read-only PowerShell collector ресурcных snapshots с whitelist полей и offline проверками multi-container stats, отсутствия утечки env и неполного результата: [инструкция](backend-baseline.md).
- [x] Уточнены architecture/overview: гибридная Registry + PostgreSQL очередь с ограничениями, реальный tenant-refresh endpoint и текущий React/ReUI UI.
- [x] Исправлен локальный Go lint runner: проверяет пакеты текущего модуля вместо посторонних вложенных worktree и возвращает ошибки vet/linter.
- [ ] Подтвердить новый integration job реальным запуском CI/PostgreSQL. Локальный запуск без DSN компилирует тесты, но DB-проверки пропускаются; это не интеграционный успех.
- [ ] Воспроизвести взаимодействие локального Registry и DB claim при ожидании дольше grace, повторном idempotency key и настоящем restart worker/jobs API. SQL-level тесты выше не доказывают эти гарантии.
- [ ] Инвентаризировать реально установленные версии/digests speakers и прогнать матрицу на старых binaries/images. Source-derived контракт и текущая fixture не заменяют эту матрицу.
- [ ] Снять effective Compose и реальные CPU/RAM/IO, выполнить workload/24h soak на целевом VPS; локально Docker отсутствует.
Результаты локальной валидации и ограничения среды приведены в [backend-baseline.md](backend-baseline.md).
**Первая очередь runtime: B01 / B02 / S01 / B08.** Отметки ниже относятся к реализованным частям, а не к закрытию пунктов целиком.
- [x] B01/B08: bare HTTP 2xx означает доставку без подтверждения (`unconfirmed`); applied обновляется только после явного ACK нужной опубликованной ревизии либо успешного локального apply. Формат запроса старому агенту сохранён.
- [x] B01: результат job содержит результаты по speakers и счётчики published/applied/unconfirmed/failed; ошибки доставки больше не превращаются в общий успешный apply.
- [x] S01: неопрошенное состояние и отсутствующие счётчики возвращаются как unknown/null; сохранённый Established не подменяет текущее наблюдение. Добавлены quality/source/timestamps и проверки назначенного speaker.
- [x] B02: реализованы атомарное переключение `bird.conf` внутри mounted ActiveDir, межпроцессная блокировка, проверяемые поколения и rollback с отдельным context. Astra проверила исправления inline/commented include bypass; переносимые тесты прошли.
- [x] B01: отмена останавливает публикацию следующим speakers, игнорирует поздний ACK и сохраняет частичный результат со статусом cancelled; добавлены регрессии отмены dispatch и legacy empty 204.
- [ ] B02: выполнить Linux fault/process-lock тесты и реальный `bird -p` CI; проверить configure/recovery с настоящим daemon, ENOSPC и аварийное завершение между фазами. Cross-compilation и fake birdc не закрывают эти проверки.
- [x] S01/B01 UI: качество и время наблюдения, last-known подсказки, unknown вместо ложного down/0, результаты неподтверждённого deploy. Исправлены знаменатель deployment, disabled peers и явный unhealthy; Astra проверила. Typecheck/lint/build — exit 0, Vitest — 9/9. Визуальный browser review ещё не выполнен; полный F01 этим не закрыт.
- [x] B01: добавлены persistent progress по speakers и атомарные lease/generation проверки публикации/ACK; `/jobs/{job_id}/report` читает сохранённые результаты после перезапуска, включая связанные recovery jobs. Проверены восстановление отчёта, приоритет собственного прогресса recovery и tenant isolation.
- [x] B01: bounded reconciliation сохраняет порядок исходных намерений, дедуплицирует recovery и ограничивает его тремя раундами. Health observation token отклоняет запоздалые результаты; timeout/отсутствующее поле не восстанавливает устаревший статус. Memory tests порядка/retry budget прошли; SQL-регрессии добавлены в CI.
- [ ] B01: выполнить PostgreSQL restart/fencing/health gate и стенд legacy agent. Двухминутная reservation после неоднозначного ответа 2.56.2 ограничивает повторы, но не доказывает остановку удалённой операции; exactly-once и состояние BIRD не заявляются.
- [x] B08 executables: обязательная legacy-версия **2.56.2**; её pinned source-built CLI прошёл pull/auth/signature против текущего CP, агент serve — текущий dispatch/ACK/health/protocols с подписанным bundle и fake BIRD. Добавлена CI-матрица 2.56.2 и дополнительного CLI 1.0.0; реальный BIRD этим не проверен.
- [ ] B08: реальные image digests 2.56.2, agent/BIRD apply/rollback и смешанный парк по-прежнему требуют стендовой проверки; source-built CLI не закрывает всю матрицу.
- [x] B05 transport/proxy: CDN URL проверяется при dial/redirect, DNS-ответ проверяется целиком, соединение выполняется с проверенным IP; CDN proxy отключён. Приватный node transport отделён. Forwarded-заголовки доверены только `EVOBGP_TRUSTED_PROXY_CIDRS`; auth limiter ограничен 4096 активными IP без вытеснения действующих счётчиков. Astra проверила; полный Go test и lint прошли.
- [x] B05 CDN body: ingest/preview отклоняют распакованный ответ больше 8 MiB вместо обрезания; ограничено чтение error-body при retry. Проверены точный предел/+1, gzip и реальный TLS redirect с контролируемым DNS на private target.
- [x] B05 CSV: превышение 8 MiB, 10000 строк данных или 64 полей в записи возвращает 413 до создания записей. Убрано тихое обрезание, ограничено усиление памяти CSV-парсером, отмена останавливает последующие записи. Тесты exact/+1, неизвестной длины, корректного префикса перед превышением и quoted/multiline полей прошли.
- [x] B05 bundle: ограничены compressed/expanded/logical bytes, payload, manifest, число файлов и пути; закрыты sparse tar, gzip trailing expansion, дубликаты/неподписанные файлы и выход через ссылки. HTTP retry читает не более 64 KiB error-body. Astra проверила; bundle/nodecli/httpclient/pipeline tests и повторные legacy 2.56.2 tests прошли. [Пределы и настройки](bundle-limits.md).
- [x] B05 HTTP: общий предел входного тела 8 MiB проверяется после auth до любых handler side effects; до 4 body-запросов одновременно с удержанием памяти до завершения handler. Добавлены локальные tenant/global concurrency и minute quotas preview/lookup, 429 + CORS-visible Retry-After. Refresh/retry сохраняют replay и shared queue quotas; legacy GET не занимают body slots. Astra review и focused HTTP tests прошли.
- [x] B05 внешние ответы: RIPE 32/4 MiB и DoH 2/1 MiB читаются полностью с проверкой +1, включая gzip; введены default deadlines без ослабления caller/profile deadline. После явного разрешения пользователя восстановлены внутренние DoH/RIPE через отдельный pool; CDN остаётся защищённым и переиспользует соединения. Bootstrap private-DoH и TLS connection-reuse tests прошли; [политика](outbound-policy-decision.md).
- [x] B05/B06 circuit cache: не более 1024 hostname, canonical keys, отказ до dial при заполнении, idle expiry закрытых записей и удаление Prometheus label set. Активные/open записи не вытесняются. Тесты capacity, aliases, lease/ref lifecycle и metric cleanup прошли.
- [ ] B05: выполнить symlink extraction regression в Linux CI (локально Windows пропускает из-за привилегий) и стендовые проверки нагрузки. Локальные квоты не доказывают экономию RSS или общую кластерную квоту; старые правила redirects/proxy для DoH сохранены по разрешённой политике.
- [x] S06 основа: добавлены явные inventory/stored метрики, сохранены старые имена и raw labels. При ошибке чтения БД выдаётся collection_success=0 без ложных нулевых агрегатов; добавлены alert и [описание семантики](metrics-semantics.md).
- [ ] S06: desired/published/confirmed applied/observed live группы и общий статус обязательных размещений ещё не завершены.
- [x] S02 реализация: persistent snapshots, bounded collector с lease/fencing, независимые BGP/health, local authority и atomic discoveries; GET не опрашивает ноды. Coalesced POST refresh подключён к UI. `expires_at`, ошибки GET и offline скрывают устаревшие значения, включая memoized графики. Astra не обнаружила блокеров; 30 web tests, typecheck/lint/build и scoped backend tests прошли. Протокол 2.56.2 сохранён, source-built binary smoke пройден.
- [x] S02 PostgreSQL: lease/fencing, discovery rollback и coalesced refresh исполнены на временном PostgreSQL 16.15; все 23 DB/repository tests пройдены без skip (состояние до manifest migration 000037).
- [ ] S02 release gate: Linux/race и реальный mixed-speaker/BIRD стенд остаются проверками B08.
- [x] B04 миграции: закреплённое соединение, advisory lock до создания schema_migrations, SQL+version в одной транзакции, bounded cleanup и отмена ожидания. Проверка SQL закрывает comment/dollar-identifier/строковые обходы transaction control; Astra повторно проверила.
- [x] B04 acquisition: отменяемое ожидание и явные ошибки перенесены в атомарный snapshot RMW; отдельные lock sessions заменены transaction advisory lock на том же соединении, что выполняет запись.
- [x] B04 scheduler scope: reference Compose использует trusted DB enqueue; HTTP-режим ограничен явным tenant. Проверены два tenants, idempotency, system-tenant exclusion и отмена до работы; Astra review и полный Go suite прошли. ListTenantIDs/ListModules ещё требуют context, поэтому общий deadline пока не ограничивает все DB reads.
- [x] B04 snapshot I/O/RMW: context и ошибки чтения/записи проходят в pipeline/lookup; snapshot read-modify-write и module hash атомарны на одном соединении. Memory concurrency/rollback и обработка partial reads проверены; полный Go test, vet/lint и независимый Astra review прошли.
- [ ] B04 manifest: фиксировать единую версию входов tenant-refresh и не допускать публикации результата по устаревшей конфигурации. PostgreSQL connection-loss/rollback/lock-contention tests прошли на изолированной БД; новые manifest-сценарии потребуют отдельного прогона.
- [x] B04 PostgreSQL до 000036: 23 обязательных DB/repository tests прошли на изолированном PostgreSQL 16.15 под Windows, без skips; race не запускался. [Протокол проверки](backend-baseline.md).
- [ ] B04 PostgreSQL 000037+: после manifest изменений повторить SQL runtime gate; отдельно выполнить Linux/race CI.
- [x] B03: единый durable enqueue/claim, lease/heartbeat/fencing, shared jobs API, квоты и graceful shutdown; replay исходного запроса предшествует динамическому подбору модулей и rate limit. Scoped Go tests прошли, Astra выявленные ошибки replay/seed/метрик устранены.
- [x] B03: при одинаковом CreatedAt выбор и список задач используют стабильные RequestSequence/ID. Добавлена регрессия с 32 одинаковыми timestamps; jobs/store прошли 20 повторов, независимая проверка Astra — 50 повторов regression. Это не обещание порядка завершения параллельных workers или snapshot-пагинации.
- [ ] B03: выполнить SQL/race/restart gate и закрыть разрыв между CRUD и enqueue transactional outbox. Заголовок enqueue-failed делает ошибку видимой, но не устраняет crash gap.
- [x] D02/D04 конфигурация: NATS вынесен в optional профиль, Prometheus получил named volume/retention, deploy worker переведён в static runtime, Go build context сужен, зеркало передаёт digest и обновляется явно без фиксированных пауз. Существующие Docker log caps сохранены. Static review Astra и YAML-проверки прошли; [перенос истории Prometheus](docker-resources.md) описан.
- [x] D02 CLI flags: агент принимает флаги до/после subcommand и отклоняет неизвестные/лишние аргументы watch/serve. В трёх Compose watch-командах флаги идут перед subcommand для совместимости с 2.56.2; argv-тесты и YAML-проверка прошли.
- [ ] D02/D04 runtime: Docker build/config engine, реальные замеры и перенос данных ещё не выполнены. Watch hash-gating, host log replay/rotation, согласование BIRD и SBOM остаются следующими частями Docker-этапа.
| Подсистема | Существующая реализация | Направление развития |
|---|---|---|
| Источники префиксов | `AS_PREFIXES`, `CDN_CIDRS`, `DOMAINS`, `IP_RANGES`; кэши, conditional HTTP, снимки | Контроль свежести, происхождения и качества данных |
| Pipeline | Batch-запись снимков, input hash, агрегация CIDR, ревизии и diff | Согласованный снимок tenant, отмена, лимиты ресурсов |
| BIRD / узлы | Parse-check, LKG, подписанные бандлы, pull/apply, dispatch | Подтверждённый applied, сериализация и надёжное восстановление |
| Jobs | Локальный Registry, ограничение активных workers, PostgreSQL audit/claim/reclaim | Одна авторитетная очередь, lease, идемпотентность, чтение после restart |
| Наблюдаемость | Prometheus, live BGP polling, PostgreSQL snapshots, runtime logs | Фоновый collector, история BGP, качество измерений |
| Доступ | API keys, роли, JWT permissions, tenant context | Проверка доверенных прокси, URL transport policy, интеграционные проверки изоляции |
| Эксплуатация | Maintenance policies, dry-run, pruning, аудит обслуживания | Предсказуемые бюджеты, backup/restore, контроль деградации |
| UI | React, shadcn/ui, ReUI PRO, Frame kit | Использовать существующие блоки для новых backend-возможностей |
Не нужно заново создавать очередь, CDN-кэш, revision diff, rollback, lookup, maintenance или мониторинг PostgreSQL: они уже существуют. Нужно усилить их контракт и завершить недостающие части.
## 3. План улучшения backend
### B01 · Достоверный жизненный цикл deploy — P0
**Основание:** `internal/jobs/worker.go:493` вызывает `SetLastAppliedRevision` до удалённого dispatch. Ошибка dispatch сохраняется в metadata, однако job завершается `Succeed` (`:545`, `:573`). Это позволяет считать удалённую ревизию применённой без подтверждения ноды.
**Работы:**
1. Разделить `desired_revision`, `published_revision`, `dispatched_revision`, `applied_revision`, `verified_revision`.
2. Ввести deployment и результаты по каждому speaker: `pending → dispatched → applying → applied → verified`, отдельно `failed`, `unknown`, `timed_out`.
3. Обновлять `applied_revision` только по явному аутентифицированному сообщению ноды о конкретной revision ID. Для новых агентов дополнительно проверять deployment ID/generation и хеш фактической конфигурации; хеш обязателен для уровня `verified`, но не становится новым обязательным полем старого протокола. Для legacy-агента сохранять уровень подтверждения `agent_reported`; без явного revision ID оставлять результат `unconfirmed`, продолжая совместимую доставку. Старое подтверждение не должно перезаписывать новое поколение deploy (см. B08).
4. Dispatch трактовать как доставку запроса. Для недоступной ноды сохранять desired state и повторять доставку с ограниченным backoff.
5. В результате массовой операции показывать частичный успех и ошибки по узлам. Не переопределять молча семантику существующего `202`: обновить OpenAPI и UI согласованно.
**Приёмка:** недоступная нода никогда не получает новый `applied`; потеря ответа и повторный dispatch не применяют один и тот же deployment дважды; после перезапуска CP корректно восстанавливает прогресс; 1 ошибка из 5 узлов явно видна в результате.
### B02 · Безопасное применение BIRD и восстановление — P0
**Основание:** `internal/birddeploy/apply.go:69–97` игнорирует ошибки создания LKG, меняет файлы по одному, а восстановление после ошибки использует исходный context. При отменённом context повторный configure также может не выполниться. Локальная функция не содержит блокировки конкурирующих apply.
**Работы:** сериализовать операции на один BIRD instance, включая несколько процессов; использовать отдельный staging на deployment; проверять полный набор файлов и реальные include paths; выполнять переключение согласованного дерева на одном filesystem; не продолжать без пригодного LKG; удалять исключённые из новой ревизии фрагменты; выполнять rollback с отдельным ограниченным recovery-context; сохранять отдельно исходную ошибку и результат восстановления.
**Приёмка:** тесты двух конкурентных apply, отмены, заполненного диска, ошибки parse/configure и падения между фазами; после каждого сценария BIRD использует целиком старую или целиком новую проверенную конфигурацию. Статус `restored` выдаётся только после подтверждённого восстановления.
### B03 · Довести PostgreSQL-очередь до надёжного контракта — P1
**Основание:** `internal/jobs/job.go:351–410`, `internal/jobs/durable.go:62–131`, `internal/repository/job_audit.go:23–70`. Enqueue запускает локального worker; сохранение в БД — best-effort. Повтор idempotency key создаёт новый UUID локально, а SQL conflict сохраняет старый ID строки. Ошибки SQL игнорируются. Reclaim ориентируется на возраст running, без полноценного протокола lease.
**Работы:**
1. Для PostgreSQL сделать `INSERT/claim` авторитетным источником выполнения; HTTP `202` возвращать после успешной фиксации задания. Memory-режим оставить явным режимом разработки.
2. Определить семантику idempotency: одинаковый ключ и payload возвращают одну операцию в пределах retention; другой payload с тем же ключом — конфликт; намеренный повтор — новый ключ.
3. Добавить `owner_id`, `lease_until`, heartbeat, номер попытки и fencing token. Claim выполнять только при наличии свободной worker capacity.
4. Реализовать ограниченные retries для временных ошибок, terminal failure и управляемый повтор оператором. Модель доставки — at-least-once с идемпотентными эффектами, без обещания exactly-once.
5. Ограничить размер ожидающей очереди и число заданий tenant. Текущий semaphore ограничивает активных workers, но не число ожидающих goroutine.
6. List/get/cancel/report читать из общего хранилища: сейчас `routes.go:870–944` обращается к локальному Registry.
7. При shutdown прекратить claim, завершить либо отпустить lease и закрыть БД после остановки workers.
**Приёмка:** две API-реплики, restart и повтор запроса дают один логический job; живой долгий worker не перехватывается; сбой записи в БД не даёт ложного `202`; job виден и отменяется через любую реплику; нагрузочный burst не вызывает неограниченный рост памяти.
`SKIP LOCKED` подходит для конкурирующих потребителей очереди, но сам по себе не решает lease и идемпотентность: [официальная документация PostgreSQL](https://www.postgresql.org/docs/18/sql-select.html#SQL-FOR-UPDATE-SHARE).
### B04 · Миграции, блокировки и tenant isolation — P1
**Основание:** `internal/db/apply.go:34–50` выполняет проверку версии, SQL и запись версии отдельно; миграции запускаются при открытии pool. В `internal/repository/module_snapshot.go:177` используется advisory lock с background-context. Standalone scheduler обходит tenants, но HTTP refresh отправляет с одним bearer (`internal/scheduler/run.go:70–116`).
**Работы:**
- Один владелец миграций: dedicated migrate step либо advisory lock на закреплённом соединении; транзакция на поддерживаемую миграцию, явный отдельный путь для non-transactional DDL; проверка совместимости schema при readiness.
- Передавать context через store/repository I/O; lock acquisition обязан иметь timeout и возвращать ошибку, а не продолжать без блокировки.
- Сделать tenant-refresh согласованным: сериализация render на tenant и фиксация версий входных снимков. Начать с транзакционного manifest входных версий, затем оптимизировать.
- Для scheduler выбрать явную модель: доверенный DB worker либо строго ограниченная service identity с tenant-scoped запросами. Не использовать пользовательский bearer как неявный доступ ко всем tenants.
- Проверить tenant scope для jobs, revisions, node acknowledgements, monitoring и maintenance. Глобальный мониторинг БД должен быть доступен только соответствующей административной роли.
**Приёмка:** параллельный старт API/workers не ломает миграции; неудачная миграция не записана как успешная; отмена операции прерывает ожидание lock; тесты минимум с двумя tenants доказывают отсутствие смешивания данных.
### B05 · Защита исходящих запросов и ограничение нагрузки — P1
**Основание:** CDN URL уже проверяется, но DNS precheck с кэшем (`internal/pipeline/cdn_url.go:96–132`) отделён от фактического соединения, а общий HTTP transport не проверяет redirect/dial (`internal/httpclient/httpclient.go:18–25`). `clientIP` доверяет X-Forwarded-For (`routes_audit.go:134`), а rate limiter не удаляет старые IP-ключи (`ratelimit.go:36–60`). Это подтверждённые пробелы защиты; эксплуатация в текущей сети не проверялась.
**Работы:** проверять целевые адреса при соединении и каждом redirect; определить поведение при HTTP proxy; разделить публичные CDN/DoH и разрешённые приватные адреса агентов; ограничить размеры скачивания/распаковки/CSV и длительность операций; добавить квоты дорогостоящих refresh/preview/lookup; доверять forwarded headers только от настроенных proxy; ограничить и очищать rate-limit cache.
**Приёмка:** тесты redirect на private address, смены DNS-ответа, IPv4-mapped IPv6, oversized/decompression payload; корректный доступ к явно разрешённому private agent; поддельный XFF не обходит лимит; cardinality IP не увеличивает память без границы.
### B06 · Ускорить lookup, API и pipeline на больших наборах — P2
**Основание:** `internal/lookup/lookup.go:193` перебирает модули и snapshots; для домена поиск повторяется по каждому resolved IP (`:173–185`). Ошибка DNS сейчас не отражена отдельно от отсутствия IP-совпадений. Размер выигрыша требует benchmark.
**Работы:**
- Добавить repository-операцию membership lookup с tenant predicate и индексом по сетевым префиксам; выбрать PostgreSQL `inet/cidr` и индекс по результатам EXPLAIN. Для memory backend — компактный префиксный индекс, если профиль оправдает сложность.
- Batch lookup всех resolved IP; лимиты/пагинация совпадений; `resolution_status` и используемый resolver; единая семантика с DoH pipeline.
- Для list endpoints — пагинация в SQL, а не после загрузки всей коллекции; summary DTO без тяжёлых snapshots и BIRD-конфига.
- Профилировать allocations, COPY/merge, render и bundle; уменьшать копирование больших срезов и повторную сериализацию по конкретным pprof/benchmark данным.
- Повтор внешних HTTP: учитывать 429/Retry-After, jitter и общий deadline; сохранять существующий circuit breaker, добавив наблюдаемость и ограничение его кэша.
- Не менять все store-интерфейсы за один раз: начать с горячих I/O путей и cancellable методов.
**Приёмка:** baseline и сравнение на 10k/100k/1M префиксов в подходящем профиле; одинаковый результат IPv4/IPv6 до и после; отсутствие full snapshot scan в DB lookup; API latency не растёт пропорционально числу открытых браузеров.
### B07 · Интеграционные проверки и эксплуатационная готовность — P1/P2
**Основание:** CI запускает `go test -race`, но в `.gitea/workflows/quality.yaml` нет отдельного PostgreSQL service/DSN для repository tests. Эти тесты вызывают `t.Skip` без `EVOBGP_TEST_DATABASE_URL` (`internal/repository/backend_test.go:17`). Таким образом зелёный общий Go job не доказывает работу PostgreSQL-сценариев.
**Работы:** добавить обязательный integration job с временной PostgreSQL и применением миграций; отдельные сценарии durable queue, cancel, two-tenant, restart, prune и concurrent migration; стенд CP + два агента + BIRD для apply/rollback. Добавить backup/restore runbook и периодическое восстановление в изолированное окружение: БД, signing key, node credentials, конфигурации и последние рабочие ревизии. Согласовать RPO/RTO по реальным требованиям.
OpenTelemetry сейчас является stub (`internal/observability/otel.go:11`). Сначала завершить структурированные логи и request/job/deployment correlation IDs; полноценный tracing включать опционально с sampling, когда он нужен для расследований.
**Приёмка:** CI явно подтверждает исполнение DB-тестов, а не skip; аварийный сценарий воспроизводим; восстановленный стенд проверяет подписи и применяет известную ревизию; все изменения проходят существующие Go/OpenAPI/BIRD quality gates.
### B08 · Обратная совместимость и постепенное обновление спикеров — P1, обязательный release gate
**Цель:** новый CP обслуживает смешанный парк старых и новых speakers. Отсутствие нового telemetry/ACK API не должно лишать старый speaker возможности работать по существующему pull/sync протоколу. Совместимость доставки и полнота подтверждения — отдельные характеристики.
**Основание в текущем коде:** `internal/nodedispatch/dispatch.go` уже читает `applied_revision_id` из sync-ответа, а `health.go` — `last_applied_revision_id` и `last_sync_at`. При этом sync-клиент подставляет запрошенную revision ID, если ответ её не содержит. Такая подстановка не является подтверждением ноды и должна быть устранена без отказа от legacy dispatch. Текущий формат бандла описан в `internal/bundle/pack.go` и проверяется в `verify.go`.
#### Контракт совместимости
| Поверхность | Что сохранить | Как вводить новое |
|---|---|---|
| Node API control plane | Существующие `/v1/speakers/{id}/revisions/latest`, `/bundle/{revision_id}`, получение signing public key и используемые механизмы auth | Новые обязательные поля/семантика — через согласованную capability или отдельную версию API; старые запросы остаются допустимыми |
| Agent API | Текущие sync/health запросы и известные поля ответа | Не отправлять новые поля старым strict-decoders; расширенный ACK и telemetry — только после определения возможностей |
| Signed bundle | Для legacy сохранить tar.gz, layout файлов, manifest, Ed25519/SHA-256 и проверку доверенного ключа | Новый формат выдавать только совместимому клиенту; legacy-вариант создавать и подписывать отдельно, не менять уже подписанные байты |
| BIRD-конфигурация | Синтаксис и capabilities реально установленной на speaker версии | Рендерить по профилю совместимости узла; новую директиву не отправлять неподдерживающему BIRD |
| Credentials / TLS | Действующие ключи доступа, сохранённый signing key и проверка TLS | Ротация через отдельный переход с периодом перекрытия там, где протокол его поддерживает; не обходить подпись/auth ради совместимости |
| UI / monitoring | Старые узлы остаются управляемыми и видимыми | Явные `legacy`, `unsupported`, `unconfirmed`; отсутствие новой метрики не означает неисправность BGP |
#### Определение возможностей и fallback
1. Перед изменением протокола инвентаризировать реальные версии/дигесты `evobgp-agent`, `evobgp-node`, BIRD и режимы работы: agent serve, pull по расписанию/CLI, иные используемые варианты. Зафиксировать поддерживаемый legacy baseline, а не предполагать, что все ноды равны текущему checkout.
2. Новые агенты объявляют protocol version и capabilities, например `deployment_ack`, `config_hash`, `telemetry_v2`, `bundle_v2`. Конкретные имена закрепить в OpenAPI. Возможности важнее сравнения строк версии.
3. Отсутствие capability metadata в корректном старом ответе либо документированный `404/405` capability endpoint означает legacy. Timeout, `401/403`, TLS error и некорректный ответ означают ошибку связи/доступа; они не должны автоматически запускать downgrade или обход проверки безопасности.
4. Для legacy использовать существующие request/response shapes и signed bundle. Если старый агент явно сообщает совпадающую revision ID, записывать `agent_reported`, но не утверждать проверку config hash. Голый `2xx` подтверждает только запрос; без подтверждения ревизии показывать `dispatched/unconfirmed`. Если старый health сообщает revision ID без времени наблюдения, показывать ограниченную свежесть, не выдавая его за ACK нового deployment.
5. Для нод без generation-aware ACK сериализовать deploy на speaker, не допускать неоднозначных перекрывающихся операций. Устаревший ответ не меняет текущий desired state; неоднозначность остаётся `unconfirmed` до новой проверки.
6. Старые pull-only узлы продолжают получать published revision без обязательного push/callback. Отсутствие ACK не вызывает бесконечный повтор apply: bounded retry относится к доставке, наблюдение — к reconciliation с backoff.
7. Новые health-gated rollout/auto-rollback допускают только узлы с достаточным уровнем подтверждения. Legacy-узел можно обновить прежним способом с явным ограничением автоматической верификации; его не блокируют глобально из-за отсутствующей новой функции.
#### Порядок перехода
- **Сначала CP:** добавить совместимые адаптеры, schema expansion и распознавание возможностей; новые флаги включать по speaker/cohort. Старый путь доставки остаётся рабочим.
- **Затем canary-агент:** проверить новый протокол на одной ноде, затем обновлять остальные поочерёдно. Смешанный парк — поддерживаемое рабочее состояние, а не только короткий момент установки.
- **Откат:** проверить новый CP со старым агентом и новый агент с предыдущим поддерживаемым CP. Сохранить старый digest, совместимый bundle generation и предыдущий протокол. Миграции БД — expand/contract: удаление/переименование полей откладывается за окно rollback; обратная совместимость схемы проверяется запуском предыдущего CP на обновлённой схеме.
- **Срок поддержки:** поддерживать инвентаризированные развёрнутые legacy-версии в рамках этого плана. Точный minimum supported version и окно deprecation зафиксировать после инвентаризации. Удаление legacy-пути — отдельное решение владельца проекта после миграции нод; не отключать его автоматически при первом новом релизе. Для опасного неподдерживаемого релиза — адресный план обновления, без бессрочного сохранения уязвимого поведения.
**Приёмка:** integration matrix использует реальные сохранённые старые binaries/images и golden bundles, а не только mocks текущего агента. Обязательные пары: новый CP + самый старый поддерживаемый агент/CLI; новый CP + текущий агент; предыдущий CP + новый агент; смешанный парк. Проверить pull/sync, подпись, auth, parse/apply/rollback, отсутствующие capabilities/ACK, reconnect после offline и поздний ответ. Обновление только CP не требует изменений на legacy-ноде и не вызывает BGP flap; недоступные новые функции явно обозначены. Проверка совместимости входит в CI/release gate каждого изменения node API, bundle, auth и BIRD render.
## 4. Сбор статистики: целевая модель
### S01 · Отделить текущее состояние от последнего известного — P1
**Основание:** `internal/httpapi/peers_live.go:275–283` не заменяет сохранённый session state, если live-проверка не нашла состояние. В `speakers_live.go:117–126` отсутствующий результат превращается в пустой view, а `:24–48` считает отсутствие ошибки успешным polling.
Для каждого измерения хранить `observed_at`, `received_at`, `last_success_at`, `source`, `quality`, `error_code`. Состояния качества: `ok`, `stale`, `unreachable`, `unknown`, `disabled`. Отдельно хранить последнее подтверждённое BGP state. Неизвестное значение — `null`/gap, а не ноль и не зелёный статус.
**Приёмка:** выключение агента переводит данные в stale/unreachable; удалённая сессия не остаётся Established; не настроенный collector не считается успешным; UI показывает возраст измерения и причину отсутствия данных.
### S02 · Перенести polling из чтения API в фоновый collector — P1
Исходно live-опрос был привязан к HTTP-чтению и короткому BGP cache, а health опрашивался в `collectSpeakerLiveStatus`. В реализации S02 эти пути удалены: GET читает persistent snapshots, collector выполняет ограниченные фоновые опросы.
**Целевая цепочка:**
```mermaid
flowchart LR
N[Agent / BIRD] --> C[Collector: jitter, limits, deadlines]
C --> L[Последнее состояние и качество]
C --> E[История событий]
C --> M[Prometheus metrics]
L --> API[Read API]
E --> API
API --> UI[ReUI PRO: dashboard и диагностика]
```
Начальный интервал — 15–30 секунд как проверяемая настройка, не гарантированный SLA. Добавить ограничение параллельных опросов, jitter, backoff для недоступных узлов и единственного владельца опроса в нескольких CP-репликах. Обычный GET отдаёт snapshot сразу. Кнопка «Обновить» ставит объединяемый запрос, не создаёт отдельный fan-out на каждого клиента.
**Приёмка:** при переходе с 1 на 20 UI-клиентов число poll к одному speaker практически неизменно; просроченные данные отмечаются явно; недоступный speaker не задерживает всю страницу; очередь collector ограничена.
### S03 · История BGP и событий — P2
Добавить историю переходов BGP state, длительности недоступности, flap count, времени последнего established, prefix counts по AFI/SAFI там, где их надёжно отдаёт BIRD. Отделить BGP FSM от состояния самого агента и транспорта управления.
Рекомендуемое разделение: PostgreSQL — события, deployment, аудит и ограниченные агрегаты; Prometheus — временные ряды с ограниченной cardinality. На минимальном VPS разрешить режим только событий и последнего snapshot. Не записывать каждый неизменившийся poll как полноценное событие.
Начальные retention-параметры для проверки: детальные события 30 дней, пятиминутные агрегаты 90 дней; это настраиваемые предложения, не требование к любому VPS. Оценивать объём по `число рядов × 86400 / интервал × дни`, затем измерять фактический размер с индексами/WAL. Partitioning вводить при подтверждённом объёме, не автоматически.
**Приёмка:** разрывы данных видны на графике; restart collector не создаёт ложный flap; повторные события дедуплицируются; очистка истории ограничена по времени/пакету; экспорт не блокирует ingest.
### S04 · Статистика качества источников и pipeline — P1/P2
Для каждого источника: время последней попытки и успеха, latency, HTTP status, ETag/cache hit, valid/invalid/duplicate prefix counts, возраст использованных данных, число последовательных ошибок. Для каждой ревизии — manifest источников и их версий, raw/aggregated counts, добавленные/удалённые префиксы, предупреждения о stale fallback.
Настроить пороги изменений: внезапный пустой ответ или массовое удаление префиксов переводят ревизию в `needs_review`; ручное исключение оставляет audit trail. Порог зависит от модуля, а не от одной глобальной константы.
**Приёмка:** оператор видит, почему данные не обновились; временная ошибка upstream не выдаётся за новый успешный ingest; опасное сокращение списка не публикуется автоматически без установленной политики.
### S05 · Правильная аналитика PostgreSQL и алерты — P2
Не смешивать накопительные статистики PostgreSQL с показателями за окно. Для rate/delta учитывать `stats_reset`, временной интервал и исчезновение query ID. В корреляции показывать только реально измеренные точки: текущий `internal/pgmonitor/correlation.go` объединяет историю jobs с одной текущей точкой cache hit, а `RecordCorrelationSnapshot` — заглушка.
Расширить существующие Prometheus alerts на потерю телеметрии, stale источники, очередь/возраст jobs, lease failure, deployment drift и ошибки БД. Предусмотреть окно обслуживания, задержку срабатывания, дедупликацию и восстановление. Правило «нет Established» отдельно от «не удалось опросить BIRD».
Не использовать job ID, revision ID, полный URL, домен или префикс как неограниченные labels. Это соответствует рекомендациям [Prometheus по cardinality](https://next.prometheus.io/docs/practices/naming/). Детали хранить в событиях и логах.
**Приёмка:** outage collector вызывает отдельный alert; графики не рисуют отсутствующие значения как нули; периодические snapshots не дублируются каждой API-репликой; количество рядов имеет проверяемый бюджет.
### S06 · Разделить смысл метрик — P1
`materialized_prefixes_max/sum` агрегируют сохранённые ревизии (`internal/repository/postgres.go:78–89`), а `bgp_peer_sessions` берёт сохранённый `meta_json.session_state` (`:100–116`). Локальная метрика Established не описывает все remote speakers. Это разные показатели: их нельзя объединять в один KPI «маршруты сейчас» или «сеть здорова».
Добавить явные группы **inventory / desired / published / confirmed applied / observed live**; для multi-speaker считать отдельно «есть Established хотя бы где-то» и «здоровы все обязательные размещения». Общий статус — healthy/degraded/down/unknown по задокументированной политике. При смене метрик сохранить переходный период и обновить dashboards/alerts. Ошибка DB-агрегации должна иметь collection error, а не выглядеть достоверным нулём.
**Приёмка:** добавление старой ревизии в историю не увеличивает KPI текущих маршрутов; outage одного обязательного speaker даёт degraded даже при Established на другом; fresh live и сохранённая конфигурация явно различаются в API.
## 5. Отдельный план улучшения и добавления функций
| ID / приоритет | Функция | Польза и объём MVP | Зависимости / критерий готовности |
|---|---|---|---|
| F01 · P1 | **Центр состояния сети** | Пиры, speakers, stale источники, deployment drift и очередь проблем на одном экране | S01–S02, B01; каждый статус имеет источник и возраст |
| F02 · P1 | **Паспорт ревизии** | Продолжение существующих preview/diff: происхождение данных, impact по узлам, warnings и подтверждения apply | B01, S04; можно объяснить каждое существенное изменение |
| F03 · P2 | **Управляемая раскатка** | Canary speaker, этапы rollout, лимит параллелизма, pause/resume, окно обслуживания | B01–B03/B08; ошибка canary останавливает продвижение; legacy-путь доступен отдельно от автоматической верификации |
| F04 · P2 | **История инцидентов и уведомления** | Flap, outage, stale, deploy failure; acknowledge, silence, webhook и один выбранный канал уведомлений | S03–S05; дедупликация, recovery notification и повтор доставки |
| F05 · P2 | **Объяснение принадлежности IP/домена** | Развитие existing lookup: источник → модуль → community → ревизия → desired/applied speaker | B06, F02; DNS failure отделён от «совпадений нет» |
| F06 · P2 | **Проверка источника до включения** | Развитие CDN preview: тип ответа, нормализация, ошибки строк, ожидаемый diff, тест расписания | S04, B05; тест не меняет опубликованную ревизию |
| F07 · P2 | **Backup и восстановление из панели** | Статус последних backup/restore drill, экспорт manifest, запуск разрешённой backup job | B03/B07; секреты не попадают в обычный экспорт UI |
| F08 · P2 | **Диагностический пакет** | Версии CP/agent/BIRD, job/deploy timeline, redacted logs, config hashes | B07; токены и приватные ключи исключены; лимит размера |
| F09 · P3 | **RPKI и защитные BGP-политики** | Warning-only проверка origin, max-prefix guard, затем опциональное применение политики | Отдельный дизайн и BIRD-тесты; отсутствие валидатора не меняет маршрутизацию молча |
| F10 · P3 | **Трафик по интерфейсам/потокам** | Если нужен именно трафик: SNMP/agent counters для bps/pps; NetFlow/IPFIX — отдельный сервис с sampling/retention | Отдельный бюджет и потребность; BGP counters нельзя выдавать за объём пользовательского трафика |
**ReUI PRO уже подключён.** Новые экраны строить на существующем `surface: frame` и `apps/web/src/components/reui-kit/`: `OpsDashboard` для F01, `ResourcePage` для событий/источников, `DetailPanel` для ревизии, `SettingsShell` для политик. Сохранять DataGrid/Filters и семантические Badge. Показывать stale/unknown/partial failure, а не только success/error. Переустановка UI-библиотеки или редизайн shell не нужны. Реализация UI должна следовать [контракту проекта](ui-design-contract.md) и обязательным typecheck/lint/build.
## 6. Отдельный план оптимизации Docker и ресурсов хоста
> **Меньший образ** сокращает загрузку, распаковку и место на диске. **Меньше RAM/CPU/IO** достигается также сокращением процессов, работы collectors, повторных configure, логирования и конкуренции. Эти результаты измеряются отдельно.
### Что уже оптимизировано
В `deploy/docker/gobinary/Dockerfile:26` уже используются multi-stage, `CGO_ENABLED=0`, `-trimpath`, `-s -w`; scheduler/ingest/render используют distroless targets. BuildKit registry cache и общая Go/web сборка присутствуют в `docker-bake.hcl`. `.dockerignore` исключает тяжёлые лишние каталоги. nginx уже работает с одним worker; Docker stdout rotation настроена. Повторное добавление этих механизмов не даст заявляемого нового выигрыша.
### D00 · Измеримый baseline — P1, до изменения лимитов
Зафиксировать image digests, effective Compose, vCPU/RAM, filesystem/cgroup, число tenants/modules/prefixes/speakers/peers. Собрать следующие сценарии на отдельном стенде:
| Сценарий | Что измерять | Зачем |
|---|---|---|
| Idle 15–30 минут | CPU, RSS/cgroup memory, SQL connections, число poll/configure, bytes logs | Найти постоянную лишнюю работу |
| Refresh → render → apply | Длительность, peak memory, allocations/GC, DB waits, BIRD parse/configure | Не оптимизировать idle ценой пикового OOM |
| Несколько refresh + UI | p95/p99 API, queue wait, CPU throttling, IO PSI | Проверить backpressure |
| Недоступные upstream/agents | Retries, goroutines, stale age, рост очереди | Убедиться в ограниченности деградации |
| Restart и 24h soak | Recovery, leaks, WAL/log growth, OOM/restarts, BGP flaps | Проверить эксплуатационный результат |
| Cold/warm image build и pull | Compressed bytes, unique/shared layers, build/pull/extract/start time | Отделить CI, сеть, диск и runtime |
Использовать effective `docker compose config`, `docker inspect` для фактических cgroup limits, `docker stats`, `docker system df -v`, cgroup memory/events/cpu.stat, PostgreSQL statistics и BIRD counters. `shm_size` — допустимая ёмкость, не автоматически занятая RAM. Shared layers не считать несколько раз.
### D01 · Исправить ресурсную модель профилей — P1
`docker-compose.microvps-full.yaml:9` обещает около 800 MiB суммы лимитов. С учётом наследуемого edge получается **1032M**: all 280 + PostgreSQL 256 + BIRD 96 + agent 48 + NATS 48 + web 48 + Prometheus 128 + Traefik 128. Это сумма верхних границ, **не измеренная RAM**, но заявленного резерва для ОС на 1 GiB VPS она не обеспечивает.
Production example и remote-speaker Compose не имеют аналогичных CPU/RAM caps. Подготовить согласованную матрицу ресурсов:
| Профиль | Предлагаемый состав | Политика |
|---|---|---|
| **microvps-core** — новый вариант | all + PostgreSQL + нужные BIRD/agent/edge; UI по потребности | Минимум фоновых процессов; внешний scrape либо ограниченный локальный мониторинг |
| **microvps-full** | Core + UI + локальная история/мониторинг | Допуск на 1 GiB только после workload-теста; если бюджет не сходится — честно поднять минимальные требования |
| **reference** | API и отдельные workers | Сохранить изоляцию процессов, но суммировать pools и concurrency |
| **remote-speaker** | BIRD + agent serve + защищённый endpoint | Отдельный бюджет на маршруты, reconfigure и reconnect; CP workload сюда не переносить |
Оставлять измеренный запас ОС/Docker и на пики BIRD/subprocesses. Не ужимать BIRD лимиты до появления flap/OOM. Согласовать значения с реальным cgroup, а не только YAML. Основа: [Docker resource constraints](https://docs.docker.com/engine/containers/resource_constraints/).
### D02 · Сократить лишние процессы и повторяющуюся работу — P1/P2
| Изменение | Основание | Ожидаемый эффект | Проверка |
|---|---|---|---|
| Вынести **NATS** в optional профиль | `internal/broker/connect.go:12–18` только логирует URL; Compose запускает сервис и зависимости | Удаление лишнего процесса и его RAM/IO в минимальном профиле | Внутренних потребителей нет; перед production проверить внешних клиентов; удалить зависимости/env согласованно |
| Change-driven или hash-gated **BIRD configure** | `cmd/evobgp-agent/main.go:86–94` безусловно configure в watch-цикле, обычно каждые 30s | Меньше работы при неизменной конфигурации | Реальный change применяется; restart/manual change покрываются reconciliation; учитывать, что remote serve — другой режим |
| Проверить CLI flags | `flag.Parse` до positional subcommand, Compose передаёт флаги после `watch` | Настройки interval/socket реально действуют | Тест argv для watch/serve; неверный flag не игнорируется молча |
| Ограничить **дублирование логов** | Production log collector делает `docker logs -f` в host-файлы; reconnect без cursor может повторять историю | Меньше disk IO и роста host logs | Cursor/rotation, dedup replay; Runtime Logs API продолжает работать |
| Использовать существующий cleanup осознанно | `internal/runtimelogs/policy.go:32–37` default disabled | Быстрый контроль накопления | Проверить политику/retention; cron threshold не выдавать за жёсткий byte cap |
### D03 · Настроить Go, PostgreSQL и мониторинг как общий бюджет — P2
- На текущем Go 1.24 провести A/B с `GOMAXPROCS` по CPU quota и `GOMEMLIMIT` с запасом для не-Go памяти и subprocesses. `GOMEMLIMIT` — мягкий лимит runtime, не полный RSS контейнера. Отдельно оценить обновление toolchain, где изменено container-aware поведение; не смешивать обновление версии с первыми correctness fixes. См. [Go GC guide](https://go.dev/doc/gc-guide) и [Go 1.25 runtime](https://go.dev/doc/go1.25#runtime).
- Настроить уже существующие `EVOBGP_DB_MAX_CONNS/MIN_CONNS` (`internal/db/open.go:28–36`) по роли. Сумма pools reference-профиля должна укладываться в PostgreSQL max_connections. `work_mem` оценивать с учётом числа одновременно выполняемых операций, а не как единственный буфер процесса.
- Ограничить общий job concurrency и отдельные бюджеты сетевого ingest/render/deploy/maintenance. Maintenance не должен вытеснять live collection и подтверждение deploy.
- Для Prometheus добавить явный named data volume, retention по времени и размеру с запасом для WAL; проверить recreate без потери истории. В текущем Compose mount `/prometheus` явно не закреплён. Для microvps проверить scrape 30–60s вместо 15s либо external scrape; оценить влияние на detection latency. Сокращение retention не означает пропорционального сокращения head RAM. См. [Prometheus storage](https://prometheus.io/docs/prometheus/latest/storage/).
### D04 · Уменьшить образы и ускорить сборку — P2
1. **Кандидат на облегчение deploy worker.** Target `evobgp-deploy` наследует birdc runtime (`docker-bake.hcl:238–240`), хотя `internal/deploy/run.go` выполняет наблюдение drift. Проверить startup/dependency paths и перевести в уже существующий static runtime, если BIRD tools действительно не вызываются. Считать unique-layer disk отдельно от размера одного image.
2. **Согласовать BIRD toolchain.** API/all/node используют сборку BIRD 2.14 на Debian, daemon/agent — Ubuntu package (`gobinary/Dockerfile:50–58`, `bird2/Dockerfile:4–12`). Сверить реальные версии/capabilities и parse-check на тех же конфигурациях. Затем унифицировать версию/источник и минимальные runtime libraries для новых образов, сохранив render/validation profile старых удалённых BIRD по B08. Обновление CP image не требует синхронного обновления всех speakers. Не переносить динамический BIRD в scratch без зависимостей.
3. **Сузить Go build inputs.** Текущая общая сборка видит root context. Проверить инвалидирование при web-only изменении; отделить Go sources/go.mod/go.sum/migrations и необходимые embed assets. Сохранить общую компиляцию targets, если benchmark не докажет пользу другого подхода. Проверить cold ephemeral builder: layer cache и cache mounts — разные механизмы. См. [Docker cache optimization](https://docs.docker.com/build/cache/optimize/).
4. **Обновляемые base digests.** Mirror script пропускает существующий tag и содержит пять фиксированных `sleep 8` (`mirror-base-images.sh:46–69`). Сделать контролируемый digest refresh и задержки только при retry/rate limit. Это улучшение CI/воспроизводимости, не экономия RAM приложения.
5. **Проверки поставки.** Фиксировать digest, проверять зависимости и базовые образы, генерировать SBOM при сборке; не включать tooling сборки в runtime. Не применять UPX и случайную смену libc ради красивого image-size числа без benchmark и совместимости.
### Приёмка Docker-этапа
До/после используют одинаковый workload и аппаратный профиль. Уменьшение compressed image size, unique disk, steady/peak memory, CPU и logs/day отражается раздельно. Обязательные ограничения: нет новых OOM/restarts и BGP flaps; наборы prefix/community совпадают; подпись, apply и rollback работают; p95/p99 и время refresh не ухудшены сверх согласованного допуска (начальная гипотеза — 5%, уточнить по шуму стенда).
Rollout: лаборатория → canary → остальные узлы; immutable tag/digest и предыдущий рабочий digest готовы. Пересоздавать только изменяемые сервисы с сохранением volumes. Экономия места не должна включать удаление рабочих данных или истории без retention-политики.
## 7. Очерёдность реализации и оценка объёма
Оценки ниже — предварительные **человеко-дни** одного опытного инженера, включая тесты и документацию. Это не календарное обещание. После первых воспроизведённых сценариев уточнить оценки; заложить резерв 25–35% на совместимость, миграции и fault injection. Работы могут идти параллельно только при разделении областей и соблюдении зависимостей.
| Этап | Результат | Задачи | Оценка |
|---|---|---|---|
| **0 · Проверяемая основа** | PostgreSQL integration CI, воспроизведения проблем, baseline ресурсов, инвентаризация legacy | B07, D00, начало B08, актуализация architecture | 3–5 дней |
| **1 · Доверие и защита** | Честные live/applied статусы, безопасный BIRD recovery, закрытие URL/proxy gaps | S01/S06, минимальный B01, B02, B05 | 8–14 дней |
| **2 · Устойчивое исполнение** | Общая durable очередь и jobs API, миграции/locks, multi-tenant scheduler | B03–B04, завершение B01 | 10–16 дней |
| **3 · Статистика и ресурсный бюджет** | Независимый collector, история, alerts, измеренная Docker-конфигурация | S02–S05, D01–D04, B06 | 10–18 дней |
| **4 · Операторские функции** | Dashboard, паспорт ревизии, rollout, incidents | F01–F04/F06, по очереди | 8–14 дней |
**Основной объём:** ориентировочно 39–67 человеко-дней до резерва. F05/F07/F08 и особенно F09/F10 оценивать отдельно после уточнения глубины; они не должны задерживать correctness и эксплуатационные исправления. Наличие ReUI PRO сокращает работу над типовыми экранами, но не заменяет backend-контракты и проверки отказов.
**Дополнение по совместимости B08:** предварительно ещё 4–8 человеко-дней на adapters, version matrix и тесты реальных legacy-артефактов; итого 43–75 человеко-дней до резерва. Уточнить после инвентаризации. B08 выполняется сквозным потоком этапов 0–3 и обязателен до выпуска изменённых протоколов B01/S02 или BIRD toolchain D04.
### Первая итерация: конкретный backlog
1. Добавить PostgreSQL integration job и regression tests: повтор idempotency key, queued > grace при двух workers, restart/history, stale Established, непроведённый poll.
2. Исправить модель live-status; добавить freshness/quality в OpenAPI и UI.
3. Прекратить преждевременную запись remote `applied`; показывать pending/unknown до ACK.
4. Сериализовать BIRD apply и сделать ошибки LKG/recovery явными.
5. Исправить transport-level CDN URL policy и trusted-proxy обработку.
6. Снять baseline effective Compose/RAM/CPU/IO, подтвердить бюджет microvps; подготовить optional NATS и bounded logs изменения.
7. Зафиксировать legacy baseline и golden bundles; добавить проверки «обновили только CP — старый speaker продолжает pull/sync/apply». Выполнить до выпуска изменений подтверждения apply и telemetry.
На первой итерации допустимо временно ограничить конкурентный deploy и число API-writers, пока lease/fencing не готовы. Это должно быть явным ограничением конфигурации и runbook, а не скрытой надеждой на один процесс.
### Зависимости, которые нельзя пропускать
```mermaid
flowchart TD
A[PG integration и воспроизведение отказов] --> B[Миграции и transactional enqueue]
B --> C[Lease / fencing / durable jobs API]
C --> D[Надёжный deployment workflow]
E[Сериализация BIRD и recovery] --> D
D --> F[Canary / автоматический rollout]
G[Честные статусы и timestamps] --> H[Фоновый collector]
H --> I[История / alerts / операторская аналитика]
J[Baseline ресурсов] --> K[Docker и runtime tuning]
H --> K
```
## 8. Измеримые результаты и правила готовности
| Направление | Контрольная проверка |
|---|---|
| Достоверность | Unknown/stale не отображаются как Established/healthy; applied всегда имеет подтверждение |
| Очередь | При DB outage запрос не принят ложно; restart восстанавливает задания; lease и эффект защищены от повторов |
| Сеть | Конкурентный apply и сбой rollback не скрываются; old/new поколения не смешиваются |
| Статистика | Число UI клиентов не определяет частоту polling; история имеет coverage/gaps и ограниченный retention |
| Производительность | Сохранён baseline; p95/p99, peak memory и GC сравниваются на фиксированном наборе данных |
| VPS | Effective limits проверены, достаточный запас памяти подтверждён пиковым workload и 24h soak |
| Доступ | Два tenant, несколько ролей, node ACK, redirect и forged forwarded headers покрыты тестами |
| Восстановление | Успешно выполнен restore drill со схемой БД, ключами и проверкой бандла |
| Контракт | OpenAPI обновлён до реализации; docs соответствуют реальному startup и режимам работы |
| Обратная совместимость | Новый CP обслуживает поддерживаемые старые speakers; смешанный парк проходит version matrix; отсутствие новых ACK/метрик не ломает доставку и не создаёт ложный verified |
Для Go-изменений — `gofmt`, `go vet ./...`, `scripts/lint-go.ps1`, `go test ./... -race -count=1`; для API — OpenAPI lint/codegen; для BIRD — scenarios и `bird -p`; для UI — обязательные typecheck/lint/build и релевантные тесты. Фактически выполненные проверки приведены в [backend-baseline.md](backend-baseline.md); наличие команды в плане не означает её успешный прогон.
## 9. Решения, которые стоит отложить до измерений
- Переход на микросервисы, Kafka/NATS/Redis как обязательную инфраструктуру: текущая PostgreSQL-очередь сначала должна получить корректные гарантии.
- Выделенный time-series кластер: сначала посчитать ряды, частоту и retention; для малого профиля PostgreSQL events + Prometheus достаточно как стартовая архитектура.
- Переписывание Go backend или замена PostgreSQL: аудит не выявил оснований для такого масштаба изменений.
- Автоматический rollback по одному неуспешному poll: сначала нужны quality/coverage, hysteresis и проверенный recovery.
- RPKI enforcement и сбор NetFlow: отдельный продуктовый этап с сетевым тестированием и самостоятельным ресурсным бюджетом.
## 10. Навигация по источникам проекта
| Область | Основные файлы |
|---|---|
| Контракт API / доступ | [openapi.yaml](openapi.yaml), [access.md](access.md), [permissions.go](../internal/httpapi/permissions.go) |
| Queue / persistence | [job.go](../internal/jobs/job.go), [durable.go](../internal/jobs/durable.go), [job_audit.go](../internal/repository/job_audit.go), [bootstrap.go](../internal/httpapi/bootstrap.go) |
| Deploy / recovery | [worker.go](../internal/jobs/worker.go), [apply.go](../internal/birddeploy/apply.go), [nodedispatch](../internal/nodedispatch/) |
| BGP live | [peers_live.go](../internal/httpapi/peers_live.go), [speakers_live.go](../internal/httpapi/speakers_live.go), [bgp_sessions.go](../internal/birdfmt/bgp_sessions.go) |
| Метрики / PostgreSQL | [metrics.go](../internal/observability/metrics.go), [pgmonitor](../internal/pgmonitor/), [alerts.yml](../deploy/prometheus/alerts.yml) |
| Источники / lookup | [pipeline](../internal/pipeline/), [lookup.go](../internal/lookup/lookup.go), [httpclient.go](../internal/httpclient/httpclient.go) |
| Docker / CI | [docker-bake.hcl](../deploy/docker/docker-bake.hcl), [Compose](../deploy/compose/docker-compose.yaml), [microvps override](../deploy/compose/docker-compose.microvps-full.yaml), [quality.yaml](../.gitea/workflows/quality.yaml) |
Номера строк в тексте относятся к исследованному checkout; при реализации ориентироваться также на имена функций. Приоритеты — инженерная оценка по последствиям возможного отказа, а не утверждение о произошедших production-инцидентах.