Files
enduro-trails/docs/work-items/ET-008/10-tech-risks.md
claude-bot d33f360a2f
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 6s
CI / build (push) Successful in 2s
architect(ET-008): ADRs, infra/data requirements, tech risks
2026-06-01 12:15:05 +00:00

21 KiB
Raw Blame History

type, work_item_id, title, version, status, created_at, authors
type work_item_id title version status created_at authors
tech-risks ET-008 Технические риски — ET-008: GPS-треки с публичных платформ 1 approved 2026-06-01
agent:architect

Технические риски — ET-008

Технические риски этапа разработки и эксплуатации. Бизнес-риски — в BRD §6 (пересечение есть, здесь акцент на технические митигации). Шкала: вероятность (Н/С/В) × влияние (Н/С/В).

R-1 — Парсер источника ломается при изменении HTML

  • Описание: ADR-010/011 источники (enduro_russia, ttrails) скрейпят HTML-страницы. Платформа может в любой момент изменить разметку (новый шаблон, JS-rendering) → парсер перестаёт извлекать треки.
  • Вероятность / Влияние: В / С.
  • Митигация:
    • Каждый source в отдельном модуле (src/api/gps_tracks/sources/<name>.py); падение одного не валит других (ADR-007 §I-A).
    • Pipeline пишет status=error в pipeline_runs; оператор видит через /api/gps-tracks/health.
    • Параметризированные тесты с фикстурами HTML-снапшота — при первом упавшем прогоне разработчик обновляет фикстуру и парсер за 1 итерацию.
    • При двух неудачных прогонах подряд — алерт (07-infra-requirements.md §10.1). На MVP — ручная проверка.
    • Конфиг gps_sources.yaml::enabled: false — мгновенное отключение источника без deploy.

R-2 — Ложные коллизии дедупа

  • Описание: ADR-006 алгоритм bbox+length+date bucket детерминированно мерджит треки с похожими параметрами. На треках без created_at (от источников без даты) — гарантированный merge всех таких треков в одном bbox/length. На дата-датасете — возможны коллизии для популярных маршрутов (двое разных гонщиков проехали тот же 30-км круг в один день).
  • Вероятность / Влияние: С / С.
  • Митигация:
    • BRD §5 фиксирует допустимую метрику «< 5% дублей»; QA-скрипт scripts/dedup_audit.py проверяет на выборке 100 треков (04-test-plan.yaml).
    • При провале метрики — план отступления ADR-006 §8 (сузить length-bucket, добавить activity в ключ).
    • Если меняется формула dedup_key — полный rebuild БД (rm + python -m scripts.gps_collect); регенерация ≤ 6 часов.
    • Документация в 08-data-requirements.md §3.2 для оператора.

R-3 — Pipeline повреждает БД

  • Описание: Бaг в Python-коде upsert (ADR-006 §6) при ON CONFLICT может оставить БД в несогласованном состоянии (битый JSON в sources_json, частично записанная transaction). SQLite + WAL обычно atomic per-statement, но composite upsert может рассогласоваться.
  • Вероятность / Влияние: Н / В.
  • Митигация:
    • Все upsert операции — внутри SQLite BEGIN IMMEDIATE / COMMIT (atomic transaction).
    • Ежедневный backup data/gps_tracks.sqlite (07-infra-requirements.md §4.4).
    • При повреждении: cp backups/gps_tracks-<date>.sqlite data/gps_tracks.sqlite + cache-clear API. RTO ≈ 12 минуты.
    • Полный rebuild: rm gps_tracks.sqlite && docker compose --profile batch run --rm gps-collector — ≤ 6 часов.
    • Изоляция в отдельной БД (ADR-005 D-A) гарантирует, что повреждение не затронет centralfederal.sqlite (OSM-данные).

R-4 — Размер БД превышает 2 ГБ

  • Описание: REQ-NF-03 предел data/gps_tracks.sqlite — 2 ГБ. На MVP-объёме (5000 треков ≈ 105 МБ) запас 20×. Но при расширении на РФ или при отсутствии работающего GC размер может вырасти линейно.
  • Вероятность / Влияние: Н / С.
  • Митигация:
    • Health-эндпоинт отдаёт db_size_mb — оператор видит.
    • Месячный GC --gc удаляет треки старше 5 лет (07-infra-requirements.md §7.1).
    • При устойчивом росте > 2 ГБ — миграция на PostGIS (отдельный work item; контракт API стабилен, см. ADR-005 §«Технический долг»).
    • Алерт db_size_mb > 2000 — пока ручная проверка (post-MVP — автоматический).

R-5 — IP mva154 банится источником

  • Описание: Скрейпер с фиксированного IP может попасть в чёрный список платформы (особенно при ошибках rate-limit). Pipeline начинает возвращать 429/403 на все запросы → source не пополняется.
  • Вероятность / Влияние: С / С.
  • Митигация:
    • Rate-limit в gps_sources.yaml per-source (1 сек для OSM, 5 сек для скрейп-источников).
    • Корректный User-Agent с контактом — платформа может связаться, прежде чем банить.
    • Backoff на 429 (TRZ §6.3) — exponential до 3 попыток.
    • pipeline_runs.errors_json фиксирует HTTP-коды → оператор видит.
    • При бане — приостановить source (enabled: false), связаться с платформой, при необходимости отключить полностью.
    • Прокси через сторонний IP — не закладывается (нарушает дух прозрачности).

R-6 — Pipeline жрёт ресурсы и деградирует API во время прогона

  • Описание: На время прогона gps-collector контейнер активен, скачивает GPX, парсит, пишет в БД. Если ресурсы не ограничены — httpx + shapely могут уйти в GC-storm; SQLite write lock конкурирует с API readers.
  • Вероятность / Влияние: Н / С.
  • Митигация:
    • Docker cpus: "1.0", mem_limit: 512m (07-infra-requirements.md §9.2). Pipeline не вытесняет API даже на одно-CPU-сервере.
    • WAL-mode позволяет API читать БД во время записи pipeline'а (ADR-005 W-A).
    • Cron в 03:00 UTC = 06:00 MSK — низкий traffic.
    • Async-генератор parser.collect() — pipeline pulls треки по одному, не накапливает в памяти больше одного (ADR-007 §4).

R-7 — Дублирование tile-утилит между main.py и gps_tracks/mvt.py

  • Описание: ADR-005 §8 принимает дублирование tile_to_bbox / wkb_to_coords / simplify_coords (≈ 100 строк) ради избежания риска регрессии существующего слоя trails. Любая правка формулы упрощения требует синхронной правки в двух местах.
  • Вероятность / Влияние: С / Н.
  • Митигация:
    • Комментарий в обоих файлах # ET-008/ADR-005-§8: дубль из main.py; при добавлении третьего MVT-источника — вынести в src/api/tiles_util.py.
    • Code review-чеклист: при правке simplify_coords в одном файле — проверить второй.
    • При появлении третьего MVT-источника — обязательный рефакторинг (отдельный work item).

R-8 — GeoJSON-эндпоинт превышает SLA на плотных bbox

  • Описание: REQ-NF-02 предел 300 мс p95 на bbox с ≤ 500 треков. На реальной географии возможны bbox в плотных регионах (например, Подмосковье на z=12) где total_in_bbox > 2000. SQL даже с R-tree может проигрывать при ORDER BY + post-filter source.
  • Вероятность / Влияние: С / Н.
  • Митигация:
    • Cutoff limit=500 обрезает результат на уровне SQL.
    • Cutoff zoom 12 — на z=11 уходим в MVT-кэш, нагрузки на GeoJSON-endpoint нет.
    • R-tree обеспечивает O(log n) bbox-prefetch.
    • Дополнительный индекс по length_m DESC для ORDER BY (длинные треки приоритетнее) — фиксируется в коде; SQLite сделает sort быстро на 500 строках.
    • Если SLA не выполняется — server-side кэширование GeoJSON-ответов по (bbox_quantized, activity, source) (post-MVP).

R-9 — Лицензионный ADR не enforced

  • Описание: ADR-007 §6 требует, чтобы pipeline отказывался загружать source-parser без accepted-ADR. Если разработчик обходит проверку (например, забывает добавить license_adr: поле в gps_sources.yaml) — pipeline пойдёт скрейпить без юридического подтверждения. BRD §4 явно требует «зелёного света».
  • Вероятность / Влияние: Н / В.
  • Митигация:
    • Pydantic-валидация gps_sources.yaml — поле license_adr обязательное, отсутствие → exception при старте pipeline.
    • Дополнительная проверка в runtime: license_adr должен указывать на существующий файл; YAML frontmatter status: accepted. Иначе source skip с status: skipped_license.
    • Code review-чеклист в 12-review.md: при добавлении source в gps_sources.yaml обязательна ссылка на accepted-ADR.
    • QA-кейс: tests/api/test_gps_tracks_licensing_guard.py — поднимает pipeline с proposed-ADR, проверяет что source пропускается.

R-10 — Cache-clear endpoint доступен извне

  • Описание: POST /api/gps-tracks/cache/clear сбрасывает LRU. Если эндпоинт доступен через /enduro/ — атакующий может вызывать его в цикле, обнуляя кэш и заставляя сервер постоянно перегенерировать тайлы (DoS).
  • Вероятность / Влияние: Н / С.
  • Митигация:
    • 07-infra-requirements.md §3.1: nginx-правило location = /enduro/api/gps-tracks/cache/clear { allow 172.16/12; deny all; }.
    • Pipeline ↔ API дёргает endpoint напрямую через docker-сеть, минуя nginx → работает.
    • При появлении CSP-заголовка — connect-src 'self' блокирует внешние POST'ы из браузера (но это уже есть).

R-11 — Pipeline зависает (вечная проблема скрейперов)

  • Описание: Парсер одного источника попадает в бесконечный pagination loop или висит на медленном HTTP. Cron-job не завершается, следующий cron-тик попадает на ту же задачу.
  • Вероятность / Влияние: Н / С.
  • Митигация:
    • httpx.AsyncClient(timeout=30) — таймаут на каждый запрос.
    • Per-source максимум треков на прогон (max_tracks_per_run в gps_sources.yaml, default 5000) — стопорит pagination loop.
    • Cron-окно (3 дня между прогонами) > потенциального hang-окна; overlapping runs — два docker container'а, ресурсы изолированы; следующий cron не блокируется первым.
    • Опционально: timeout 21600 docker compose ... в cron — kill после 6 часов (REQ-NF-02). На MVP — не обязательно, но рекомендовано.

R-12 — Несогласованность UI/style при setStyle()

  • Описание: При переключении тёмной темы / спутника map.setStyle() сбрасывает все runtime-добавленные source/layer. rebuildMapOverlays() пересоздаёт; если порядок вызовов нарушен — слой публичных треков может оказаться поверх маршрута или ниже спутника.
  • Вероятность / Влияние: Н / С.
  • Митигация:
    • restorePublicTracksState() вызывается в rebuildMapOverlays() после restoreTrailsState(), до restorePoiState() и маршрута/GPX (TRZ REQ-F-19).
    • AC-12 «Переживание setStyle()» проверяет: чекбокс работает после смены темы.
    • Идемпотентные if (!map.getSource(id)) map.addSource(...) — паттерн из ADR-004 R-6.

R-13 — Конфликт с ET-006 (личные GPX)

  • Описание: ET-006 хранит личные GPX треки в window.gpxTracks и отображает как gpx-layer-*. Если ET-008 случайно использует тот же layer-id или event-handler — взаимная коллизия.
  • Вероятность / Влияние: Н / С.
  • Митигация:
    • Префикс gps-tracks-* для всех новых id (source, layer, halo) — конфликт исключён.
    • window.gpsTracksLayerwindow.gpxTracks (TRZ §4.4).
    • Z-order: gps-tracks-layer-* < gpx-layer-* (личные приоритетнее, как уточняется в TRZ §7.1).
    • AC-10 «Совместимость с ET-006» проверяет совместное отображение.

R-14 — Конфликт с ET-007 (спутник + halo)

  • Описание: ET-007 уже реализовал паттерн halo для trails на спутнике через applyTrailHaloVisibility() (ADR-004 §9). ET-008 добавляет два новых halo (gps-tracks-halo-mvt-satellite, gps-tracks-halo-geo-satellite) и расширяет applyBaseLayer().
  • Вероятность / Влияние: Н / С.
  • Митигация:
    • Новые halo-слои добавляются в оба style.json / style-dark.json с visibility: none — по тому же паттерну ET-007.
    • applyBaseLayer() (ET-007) расширяется одним блоком (см. TRZ §7.2):
      const gpsHaloOn = (currentBase === 'satellite' && layerState.publicTracks);
      setLayoutProperty('gps-tracks-halo-mvt-satellite', 'visibility', gpsHaloOn && activeMode === 'mvt' ? 'visible' : 'none');
      setLayoutProperty('gps-tracks-halo-geo-satellite', 'visibility', gpsHaloOn && activeMode === 'geo' ? 'visible' : 'none');
      
    • AC-11 «Halo на спутнике» проверяет.

R-15 — Pipeline не находит зависимости (defusedxml, pyyaml)

  • Описание: При смене образа без полного rebuild — gps-collector стартует с старым requirements.txt → ImportError.
  • Вероятность / Влияние: Н / Н.
  • Митигация:
    • Deploy-runbook (§7) явно требует docker compose build перед запуском нового pipeline.
    • CI-job собирает образ при каждом push → новые зависимости видны на CI, а не в production.

R-16 — Атрибуция теряется при включении/выключении источников

  • Описание: BRD-метрика «атрибуция каждого активного источника видна». При динамическом изменении набора enabled-источников (например, оператор временно выключил ttrails в gps_sources.yaml) клиент может продолжать показывать атрибуцию, потому что в БД уже есть треки с sources_json содержащим ttrails.
  • Вероятность / Влияние: Н / Н.
  • Митигация:
    • Атрибуция формируется на клиенте из /api/gps-tracks/health.tracks_by_source (только source с tracks_count > 0). Если в БД остались ttrails записи — атрибуция корректно отображает.
    • Если source удалён + треки удалены — tracks_by_source его не содержит → атрибуция корректно скрывается.
    • AC-15 проверяет.

Сводная таблица

ID Риск Вер. Влияние Класс Статус
R-1 Парсер ломается при смене HTML В С Высокий принят + per-source изоляция + алерт
R-2 Ложные коллизии dedup С С Средний принят + метрика BRD + план отступления
R-3 Pipeline повреждает БД Н В Средний atomic tx + ежедневный backup + rebuild за 6 ч
R-4 Размер БД > 2 ГБ Н С Низкий GC + health + миграция на PostGIS
R-5 IP mva154 банится С С Средний rate-limit + UA + backoff + отключение источника
R-6 Pipeline деградирует API Н С Низкий cgroup limits + WAL + ночное окно
R-7 Дублирование tile-утилит С Н Низкий принят + комментарии в коде + review-чеклист
R-8 GeoJSON SLA на плотных bbox С Н Низкий limit + zoom-cutoff + R-tree
R-9 Licensing-ADR не enforced Н В Высокий runtime-guard + Pydantic-валидация + тест
R-10 Cache-clear доступен извне Н С Низкий nginx allow/deny
R-11 Pipeline зависает Н С Низкий httpx timeout + max_tracks_per_run + (опц.) timeout cron
R-12 UI несогласован после setStyle Н С Низкий паттерн ADR-004 + AC-12
R-13 Конфликт с ET-006 (GPX) Н С Низкий префикс + параллельные модели + AC-10
R-14 Конфликт с ET-007 (halo) Н С Низкий новые halo по тому же паттерну + AC-11
R-15 Зависимости pipeline Н Н Низкий CI-build + runbook
R-16 Атрибуция теряется Н Н Низкий health-derived rendering

Высокие классы:

  • R-1 — операционный, ожидаемый для скрейп-источников; митигация — per-source изоляция и быстрое отключение через конфиг.
  • R-9 — критический для legal compliance; митигация многослойная (Pydantic + runtime check + тест).

Блокирующих рисков нет. R-1 и R-9 требуют внимания разработки и code review, но не блокируют merge.

Эскалация

  • arch:major-change — выставлен на ADR-005 (новая БД) и ADR-007 (новый сервис + cron). Требует архитектурного approve перед merge.
  • back-to:analysis — не требуется. ТЗ полное, BRD ясный, ADR-010 и ADR-011 явно блокирующие до закрытия licensing review (это операционный pre-requisite, не дефект анализа).