--- type: tech-risks work_item_id: ET-012 title: "Технические риски — ET-012: Снижение minzoom публичных треков до z5" version: 1 status: approved created_at: 2026-06-04 authors: - "agent:architect" --- # Технические риски — ET-012 Технические риски этапа снижения нижнего порога видимости слоя публичных GPS-треков с z=8 до z=5. Бизнес-риски — в BRD §5 (R-1..R-10). Шкала: вероятность (Н/С/В) × влияние (Н/С/В). ## R-T-1 — Размер MVT-тайла z=5 > 200 KB на реальных данных - **Описание:** На густонаселённых регионах (Москва, Урал) при росте БД до 5000+ треков фильтр `length_m ≥ 10000` + `limit=1500` может не сработать как страховка: 1500 треков × 200 байт после упрощения = ~300 KB до gzip, что близко к гейту M-8 (200 KB декомпрессировано на клиенте). - **Вероятность / Влияние:** С / С. - **Митигация:** - **Архитектурное решение (ADR-016 §T-2):** выбраны намеренно консервативные параметры (`min_length 10 км`, `limit 1500`) — это компромисс, а не «впритык». Запас 30-50% по M-8 при текущей БД (~500 треков ЦФО). - **Хук на снижение:** если PERF-Z5-01 или AC-10 покажут размер > 200 KB — снизить `limit` до 1000 в `build_gps_mvt`. Это правка одной константы, не требует архитектурного re-decide (см. ADR-016 §«Технический долг»). - **Тесты:** IT-Z5-01, IT-Z5-02 (REQ-F-11) — гейтируют размер на 50-200 треков; ручная проверка AC-10 — на реальной БД test-среды после деплоя. ## R-T-2 — DP-tolerance 4 км на z5 «убивает» геометрию треков 10-15 км - **Описание:** Трек длиной 12 км с реальной траекторией (зигзаги лесных дорог) после Douglas-Peucker с tolerance 0.04° (~2.6 км по долготе на 55° с.ш.) превращается в 2-3 точки → визуально «прямая линия от А до Б». Пользователь думает, что трек прямой, и недооценивает сложность. - **Вероятность / Влияние:** В / Н. - **Митигация:** - **Архитектурное решение (ADR-016 §T):** на z5 трек ≤ 5 км схлопывается в прямую — это **спецификация**, не баг (BRD §5 R-2). На z5 пиксель ≈ 5 км, поэтому даже идеально точный зигзаг не видно глазом. - **Спецификация поведения** для пользователя: «z5 — общий обзор сети; для деталей зумьте до z=10+». Это документировано в BRD §2.2 и TRZ §6. - **Тест:** TC-UI-12-Z5-Q (качественный) — оператор глазами проверяет, что на z5 видны минимум 3 разных «нити» в кадре (AC-08). ## R-T-3 — Линия `0.5 px` на z5 невидима на 1×-DPR мониторе - **Описание:** Если бы оставили `interpolate [..., 8, 1.0, ...]`, на z=5 MapLibre сэмплирует значение слева от первого стопа = 1.0, но после anti-aliasing на 1× мониторе линия «съедается» до ≤ 0.5px. - **Вероятность / Влияние:** С (без митигации — В) / Н. - **Митигация:** - **Архитектурное решение (ADR-016 §L-B / REQ-F-05):** явный стоп `5, 0.8` в `_gpsLayerDef.paint['line-width']`. 0.8 CSS-px = 1 физ.px на 1×-мониторе после округления GPU. Стоп `5, 1.8` в `_gpsHaloDef` (соотношение ~2.25×) — ореол не «съедает» линию. - **Тесты:** TC-UI-01-Z5 (Playwright), TC-UI-10-Z5-MOBILE (mobile viewport) — гейтируют видимость линии. ## R-T-4 — bbox-запрос на z5 тянет всю БД (R-tree fallback to full scan) - **Описание:** Один z=5 тайл накрывает ~1250×1250 км по экватору, ~700×1250 на 55° с.ш. При БД 5000 треков по ЦФО — все 5000 строк имеют bbox внутри тайла, R-tree-индекс возвращает все ROWID, и далее SQLite делает SCAN по 5000 строк для подгрузки полей. На CI-runner это ≤ 100 мс, на mva154 — оценочно ≤ 150 мс (HDD-storage). - **Вероятность / Влияние:** С / Н. - **Митигация:** - **Архитектурное решение (ADR-016 §B):** buffer 10% bbox **не меняем** в MVP — лишний 10%-запас погоды не делает при том, что основной фильтр — Python-фильтр по `length_m` после SELECT. - **PERF-Z5-01** (REQ-F-13) — гейт; при росте БД и деградации — добавляем индекс на `length_m DESC` отдельным минорным патчем (см. ADR-016 §«Технический долг»). - **Метрика M-6/M-7** — наблюдаем p95 в `uvicorn.access` после деплоя (см. `07-infra-requirements.md` §7.1). ## R-T-5 — LRU 1024 переполняется при walk-through-world - **Описание:** Если пользователь панорамирует карту на z=5 по всему миру, видит ~1024 уникальных тайла (z5 = 32×32). Серверный `_gps_tile_cache` ёмкостью 1024 при FIFO-вытеснении начинает выкидывать ранее запрошенные → повторный pan дёргает cold-build снова. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение (ADR-016 §C):** размер LRU 1024 **не меняем** в MVP. На практике пользователь работает с регионом (ЦФО + соседние области = ~20-30 тайлов z5). - **Метрика M-11** — гейт; если cache hit ratio < 80% — поднимаем до 2048 отдельным патчем. - **Альтернатива** (отложена): pre-render z=5 grid на диск при деплое (ADR-016 §P-B отклонён в MVP, но открыт для отдельного work-item). ## R-T-6 — Hint «Зум 8+» забыт в HTML → пользователь видит линии и подсказку «увеличь зум» - **Описание:** В `src/web/index.html` строка `Зум 8+`. Если в ходе реализации правка REQ-F-07 потеряется (например, мердж-конфликт), у пользователя на z<5 будет hint «Зум 8+», который противоречит фактическому порогу 5. - **Вероятность / Влияние:** С / Н. - **Митигация:** - **Архитектурное решение (REQ-F-07):** в HTML текст явно меняется на «Зум 5+». Логика показа в `_syncGpsLayersVisibility` автоматически использует `GPS_TRACKS_MIN_ZOOM` — порог переезжает автоматически. - **Тесты:** AC-05 (текст «Зум 5+»), TC-UI-04-HINT-OFF / TC-UI-05-HINT-ON (Playwright). - **Acceptance check** в `02-trz.md` REQ-F-01 `grep` — гарантирует, что других вхождений константы со старым значением нет. ## R-T-7 — Halo на спутнике на z5 «глушит» подложку - **Описание:** Если halo-line-width на z5 окажется слишком большим (например, по ошибке остался стоп `5, 4.0`), белый ореол на спутниковой подложке закрывает большую часть рельефа в кадре. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение (REQ-F-06 / ADR-016 §L-B):** halo z5 = 1.8 CSS-px; ограничено F-10 BRD `≤ 2 px`. Соотношение к line-width (1.8 / 0.8 ≈ 2.25) — стандартное для трэйл-линий. - **Тесты:** TC-UI-11-Z5-SAT (Playwright со спутниковой подложкой); AC-17 (halo-width ≤ 2 px, halo не «глушит» подложку). ## R-T-8 — Регрессия на z=8..11 из-за разделения tier z≤7 на z≤5/z=6/z=7 - **Описание:** В новой tier-таблице (ADR-016 §«Решение» п.2) ранее единый блок `z ≤ 7 → min_length=2000, limit=3000` разбит на `z≤5: min_length=10000, limit=1500 | z=6: min_length=5000, limit=2000 | z=7: min_length=2000, limit=3000`. Регрессия может проявиться, если при разбиении нечаянно поломан z=7 (например, ошибочный `elif z <= 7` вместо `elif z == 7`). - **Вероятность / Влияние:** С / С. - **Митигация:** - **Архитектурное решение (REQ-F-03):** код-сниппет в TRZ §3.3 точно указывает структуру `if z <= 5 / elif z == 6 / elif z == 7 / elif z <= 9 / ...`. - **Регрессионные тесты:** UT-Z7-01, UT-Z8-01, UT-Z12-01 (REQ-F-09), IT-REGRESS-Z8-01, IT-REGRESS-Z10-01 (REQ-F-12), AC-06. - **Code review** проверяет if-elif-цепочку построчно. ## R-T-9 — Cache poisoning: после deploy старые тайлы z8-z11 остались с прежней tier-логикой - **Описание:** `_gps_tile_cache` — in-memory FIFO; при перезапуске `app` он очищается автоматически. Но если оператор `docker compose restart app` не сделал, а только `docker compose up -d --no-deps app` пересобрал образ → новый процесс стартует с пустым кэшем, всё ок. Риск только при использовании `docker compose exec` или hot-reload (не наш случай в проде). - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение:** `docker compose up -d --no-deps app` в `07-infra-requirements.md` §6.2 шаг 2 — пересоздаёт контейнер, кэш пустой. - **Подстраховка:** `POST /api/gps-tracks/cache/clear` в шаге 4 (на случай race conditions). - **Браузерный кэш:** MapLibre LRU при reload очищается; `Cache-Control: max-age=300` ограничивает максимум 5 минут «застрявших» тайлов в браузерном кэше. ## R-T-10 — `_simplify_coords` падает с ValueError при пустом coords на z=5 - **Описание:** Существующий код: `if len(coords) < 3: return coords` — защита от пустых/коротких массивов. После добавления tier для z5 проверка остаётся. Но: `shapely.LineString(coords).simplify(0.04, ...)` при tolerance ≥ длины трека вернёт LineString из 2 точек (концы) или пустую коллекцию. Если результат пустой — fallback `return coords` возвращает оригинал. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение:** существующий fallback `return result if len(result) >= 2 else coords` (mvt.py:50) остаётся. Покрытие тестом UT-SIMP-Z5-02 (зигзаг 100 точек → 2 точки = валидный LineString). - **Дополнительный тест** (рекомендуется в pull request): `_simplify_coords([(37.0, 55.0), (37.001, 55.001)], 5)` → возвращает оригинал (2 точки). ## R-T-11 — Размер MVT z=5 = 0 байт на регионе без длинных треков - **Описание:** После фильтра `length_m ≥ 10000` в регионах с только короткими треками (например, лесопарки внутри города) тайл z=5 содержит 0 фич → возвращается `b""`. `_row_to_geojson_feature` / `build_gps_mvt` возвращают пустой protobuf, что MapLibre корректно интерпретирует как «фич нет». - **Вероятность / Влияние:** С / Н (это **ожидаемое поведение**). - **Митигация:** - **Архитектурное решение:** на z=5 в регионе без длинных треков — пусто. Это **специфицировано** в BRD §2.2 и AC-03 (требуется БД с ≥ 50 треков ≥ 10 км по ЦФО). - **Тест:** IT-Z5-03 (REQ-F-11) — тайл z=5 за пределами региона возвращает 200 с пустым телом. - **UX:** пользователь видит «пустую карту» на z=5, но hint не показывается (zoom ≥ 5); если пользователь зумит до z=8, появляются короткие треки. Естественная семантика. ## R-T-12 — Старый клиент (закэшированный в браузере) делает запросы только на z≥8 - **Описание:** Пользователь с открытой вкладкой неделю назад имеет закэшированный `gps_tracks.js` со старым `GPS_TRACKS_MIN_ZOOM = 8`. После деплоя при reload `gps_tracks.js` обновится (если есть `?v=...` versioning) или дотянется service-worker'ом. **Service worker — не настроен в MVP** (PH-9 не реализована). - **Вероятность / Влияние:** С / Н. - **Митигация:** - **Архитектурное решение:** `src/web/index.html` загружает `gps_tracks.js` напрямую (без SW). При reload браузер дёрнет последнюю версию (если nginx отдаёт нужные cache-headers). Если нет — пользователь сделает `Ctrl+F5` после очередного апа. - **Backwards compat:** старый клиент с `MIN_ZOOM=8` продолжает работать; он просто не запрашивает z=5..7. Никаких 4xx-ответов нет (REQ-F-15 — контракт не сломан). - **Митигация в долгую:** PWA / SW (PH-9, отдельный work-item) введёт правильную inval-стратегию. ## R-T-13 — DDoS на новый z=5 endpoint (бот ходит по 32×32 z5 grid) - **Описание:** Поскольку endpoint без auth и без rate-limit, скрипт-крулер может запросить все 1024 тайла z=5 за минуту → 1024 × ~200 мс build = ~3.5 минуты CPU на сервере. Не убийственно, но заметно. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение:** rate-limit **не вводим в MVP** (см. `07-infra-requirements.md` §3.2). LRU кэш съест второй проход — cold пройдёт один раз. - **Мониторинг:** в первую неделю после деплоя оператор смотрит `nginx access.log` на аномалии (см. `07-infra-requirements.md` §7.1). - **Эскалация:** если обнаружится паттерн — `slowapi`-middleware (отдельный DevOps-task). ## R-T-14 — Конфликт с halo при переключении spectator/satellite на z5 - **Описание:** При переключении подложки `applyBaseLayer()` (ET-007) должен корректно показать/скрыть halo для GPS-треков. На z=5 halo активен (`zoom ≥ GPS_TRACKS_MIN_ZOOM AND zoom < GPS_TRACKS_ZOOM_CUTOFF AND base === 'satellite'`). Если в `applyGpsHaloVisibility` есть hardcoded порог z≥8 — будет расхождение. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение:** в `gps_tracks.js` существующая логика `_syncGpsLayersVisibility` / `applyGpsHaloVisibility` использует `GPS_TRACKS_MIN_ZOOM` как константу — порог переезжает автоматически (verified by `grep` в TRZ §3 REQ-F-01). - **Тесты:** TC-UI-11-Z5-SAT (Playwright со спутниковой подложкой), AC-17. ## R-T-15 — Performance тест PERF-Z5-01 нестабилен на CI - **Описание:** PERF-Z5-01 (REQ-F-13) измеряет p95 build_gps_mvt z=5 при 500 треках. CI-runner может иметь cold I/O в первом прогоне → fail. Это flaky-тест. - **Вероятность / Влияние:** С / Н. - **Митигация:** - **Архитектурное решение:** PERF-тест с маркером `@pytest.mark.perf` запускается отдельным джобом (TRZ §3.13) — **не блокирует merge**. Логируется в `13-test-report.md` для тренд-анализа. - **Дизайн теста:** делать 10 повторов, отбрасывать первый (warmup) — стандартный паттерн для micro-benchmark'ов. - **Gate**: avg ≤ 200 мс, p95 ≤ 500 мс (gentle). ## R-T-16 — Конфигурация nginx gzip для `application/x-protobuf` пропала - **Описание:** Если nginx config был перезатёрт (например, после переустановки) и `application/x-protobuf` не в `gzip_types`, размер MVT z5 пойдёт unzipped (~80 KB на тайл) → мобильный трафик и latency растут. - **Вероятность / Влияние:** Н / С. - **Митигация:** - **Smoke-проверка** в `07-infra-requirements.md` §6.2 шаг 3: `curl -I` смотрит на `content-encoding: gzip` после деплоя. - Если gzip нет — операт восстанавливает nginx config из git (`infra/nginx/openclaw.conf` или эквивалент). ## Сводная таблица | # | Риск | Вер | Влиян | Митигация (тип) | |-------|--------------------------------------------------------------------|-----|-------|--------------------------------------| | R-T-1 | Размер MVT z5 > 200 KB | С | С | Архитектурное (tier T-2) + гейт-тест | | R-T-2 | DP-tolerance ломает геометрию коротких треков | В | Н | Спецификация (z5 = обзор) | | R-T-3 | Линия невидима на 1×-DPR | С | Н | Архитектурное (line-width стоп 0.8) | | R-T-4 | bbox-запрос z5 тянет всю БД | С | Н | Гейт-метрика + index-watch flag | | R-T-5 | LRU 1024 переполнение | Н | Н | Метрика M-11; capacity hook | | R-T-6 | Hint «Зум 8+» забыт | С | Н | grep-проверка + UI-тест | | R-T-7 | Halo «глушит» подложку | Н | Н | Архитектурное (1.8 px) + UI-тест | | R-T-8 | Регрессия z8-z11 из-за tier-rewrite | С | С | Снимок tier в TRZ + регресс-тесты | | R-T-9 | Cache poisoning после deploy | Н | Н | Procedure (cache clear) в infra | | R-T-10| `_simplify_coords` падает на пустых | Н | Н | Existing fallback + unit-тест | | R-T-11| Пустой MVT в регионе без длинных треков | С | Н | Specified behavior + IT-Z5-03 | | R-T-12| Старый клиент в кэше браузера | С | Н | Backwards-compat (контракт) | | R-T-13| DDoS на новый z=5 endpoint | Н | Н | LRU защищает; rate-limit отложен | | R-T-14| Halo не sync на z5 | Н | Н | Existing-pattern reuse + UI-тест | | R-T-15| PERF-тест flaky на CI | С | Н | Marker @perf, отдельный джоб | | R-T-16| nginx gzip пропал | Н | С | Smoke-проверка после деплоя | ## Связанные документы - `01-brd.md` §5 Бизнес-риски R-1..R-10 (часть пересекается) - `02-trz.md` §3 REQ-F-09..F-14 (тесты), §4 NFR - `06-adr/ADR-016-z5-tiling-policy.md` §«Решение», §«Последствия» - `07-infra-requirements.md` §3 (rate-limit), §6 (procedure), §7 (мониторинг) - `08-data-requirements.md` §3.4 (индексы), §5 (контракты)