--- type: tech-risks work_item_id: ET-008 title: "Технические риски — ET-008: GPS-треки с публичных платформ" version: 1 status: approved created_at: 2026-06-01 authors: - "agent:architect" --- # Технические риски — ET-008 Технические риски этапа разработки и эксплуатации. Бизнес-риски — в BRD §6 (пересечение есть, здесь акцент на технические митигации). Шкала: вероятность (Н/С/В) × влияние (Н/С/В). ## R-1 — Парсер источника ломается при изменении HTML - **Описание:** ADR-010/011 источники (`enduro_russia`, `ttrails`) скрейпят HTML-страницы. Платформа может в любой момент изменить разметку (новый шаблон, JS-rendering) → парсер перестаёт извлекать треки. - **Вероятность / Влияние:** В / С. - **Митигация:** - Каждый source в отдельном модуле (`src/api/gps_tracks/sources/.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-.sqlite data/gps_tracks.sqlite` + cache-clear API. RTO ≈ 1–2 минуты. - Полный 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.gpsTracksLayer` ≠ `window.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): ```js 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, не дефект анализа).