210 lines
21 KiB
Markdown
210 lines
21 KiB
Markdown
---
|
||
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/<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 ≈ 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, не дефект анализа).
|