# OSRM routing engine (self-hosted) — #39 Self-hosted [OSRM](https://project-osrm.org/) для site-finder `/analyze`: реальные дорожные / пешие расстояния до POI вместо straight-line `ST_Distance(geography)`. > **Scope этого PR — INFRA ONLY.** Здесь стоит контейнер + build-скрипт + healthcheck. > Интеграция в `/analyze` (замена `ST_Distance` на OSRM `route`/`table`) — отдельный > follow-up (A2 изохроны, A3 per-category routing-decay). Backend пока к osrm НЕ ходит. ## Архитектура ``` backend ──http://osrm:5000──▶ osrm (osrm-routed --algorithm mld) │ volume ./data/osrm:/data └─ /data/sverdlovsk.osrm* (MLD-граф, ~ХХХ MB) ``` - Compose service `osrm` в `docker-compose.prod.yml`, сеть `default` (project `gendesign`). - **Наружу НЕ публикуется** — нет `ports:`. Достижим только из backend по `http://osrm:5000`. Caddy его не проксирует (routing — internal API, не должен быть публичным). - `mem_limit: 1.5g`, `restart: unless-stopped`. - Граф (`/data/*.osrm*`) НЕ в git (`.gitignore` → `data/osrm/*`). Строится offline на VPS. ## Build / refresh графа Скрипт: [`scripts/build_osrm.sh`](../scripts/build_osrm.sh). Запускается **вручную** на VPS (НЕ часть `deploy.yml` — download+build тяжёлые). ```bash cd /opt/gendesign bash scripts/build_osrm.sh # car-профиль, Свердл-clip (default) ``` ### Источник pbf и решение по региону Geofabrik **не** публикует Свердловскую область отдельно — есть только [Уральский ФО](https://download.geofabrik.de/russia/ural-fed-district-latest.osm.pbf) (≈600 MB). Скрипт по умолчанию (`CLIP=1`) скачивает Ural-FO и **обрезает по bbox Свердл. обл.** через `osmium extract` → легче граф, меньше RAM в runtime. | Режим | Команда | pbf | Граф в RAM (warm) | Когда | |---|---|---|---|---| | **Свердл-clip** (default) | `bash scripts/build_osrm.sh` | clip из Ural-FO | ~0.8–1.2 GB | рекоменд., extensibility на обл | | ЕКБ-tight | `EKB_TIGHT=1 bash scripts/build_osrm.sh` | узкий bbox ЕКБ | ~0.4–0.6 GB | если RAM на VPS критична | | Ural-FO zero-clip | `CLIP=0 bash scripts/build_osrm.sh` | весь Ural-FO | ~1.5 GB+ | если нужен запас на соседние регионы | bbox Свердл (с запасом): `lon 57.2..66.2, lat 56.0..62.0`. ЕКБ-tight: `lon 59.0..62.5, lat 56.2..58.2`. ### Pipeline (MLD, не CH) `--algorithm mld` в compose требует pipeline **extract → partition → customize** (скрипт делает это через тот же `osrm/osrm-backend` образ). `osrm-contract` — это CH-алгоритм, он несовместим с `--algorithm mld`, поэтому НЕ используется. ### Walking-профиль + per-category routing (#39 A3 — LIVE) Issue хочет оба профиля (car + foot). A3 их свёл в **per-category routing**: пешеходные POI (школа, д/сад, магазины, аптека, парк, остановки) считаются по **пешему** графу (`osrm-walk`), авто-POI (ТЦ, больница) — по **car** (`osrm`). Это убирает завышение дистанций driving-профилем до близких-пешком POI (валидация #39: driving-медиана 1.6–2.9× straight-line). Маппинг категория→профиль: `settings.osrm_walk_categories`. **Сборка пешего графа** (`sverdlovsk-foot.osrm*`): ```bash CAR=0 WALK=1 bash scripts/build_osrm.sh # только пеший граф, НЕ трогает живой car-граф # (полный refresh обоих: WALK=1 bash scripts/build_osrm.sh) ``` `CAR=0` пропускает car-этап (foot-only refresh не перезаписывает работающий `sverdlovsk.osrm`, который mmap'нут живым контейнером `osrm`). **Compose-service `osrm-walk`** добавлен в `docker-compose.prod.yml` (рядом с `osrm`, internal-only, `mem_limit 1.5g`). Backend ходит к нему по `http://osrm-walk:5000` (`settings.osrm_walk_local_url`). До сборки графа контейнер crash-loop'ит — безвредно (backend не depends_on, флаг OFF). Поднять после сборки: ```bash docker compose -p gendesign -f docker-compose.prod.yml up -d --no-deps osrm-walk docker run --rm --network gendesign_default curlimages/curl:8 -fsS \ 'http://osrm-walk:5000/route/v1/foot/60.6,56.84;60.65,56.86' # ожидаем code:Ok ``` ### Monthly refresh osm.pbf на Geofabrik обновляется ежедневно. Граф освежать ~ежемесячно: ```cron 0 4 1 * * cd /opt/gendesign && bash scripts/build_osrm.sh >> /var/log/osrm-build.log 2>&1 \ && docker compose -p gendesign -f docker-compose.prod.yml up -d --force-recreate --no-deps osrm ``` Скрипт idempotent: `wget -N` качает только если на сервере новее, граф перестраивается overwrite. ## Развёртывание (что выполняет оркестратор на VPS) ```bash cd /opt/gendesign git fetch origin main && git reset --hard origin/main # подтянуть compose + скрипт # 1. Build графа (~30–50 мин, ~600MB download + ~3GB peak RAM на extract) bash scripts/build_osrm.sh # 2. Поднять контейнер docker compose -p gendesign -f docker-compose.prod.yml up -d osrm # 3. Verify (curl ВНУТРИ образа нет — используем sidecar curl в той же сети) docker run --rm --network gendesign_default curlimages/curl:8 -fsS \ 'http://osrm:5000/route/v1/driving/60.6,56.84;60.65,56.86' # ожидаем JSON: {"code":"Ok","routes":[{...}], ...} ``` ## Healthcheck Compose-healthcheck — TCP-probe порта 5000 через `bash /dev/tcp` (образ `osrm/osrm-backend` НЕ содержит curl/wget, HTTP-probe падал бы на missing binary). Полный route-probe — verify-команда выше (sidecar curl). ## Риски / open - **RAM на Beget VPS**: +~1 GB warm для Свердл-clip. Перед `up -d osrm` проверить свободную RAM (`free -h`). Если жмёт — `EKB_TIGHT=1` (~0.4–0.6 GB) или upgrade plan. - **Build на VPS медленный** (50+ мин). Альтернатива: собрать локально и `scp` `data/osrm/*.osrm*` в `/opt/gendesign/data/osrm/`. - **Размер pbf неточен**: Ural-FO ≈600 MB на скачку; «1.5GB» из issue — оценка по всему ФО + intermediate-файлы на диске во время build (нужно ~5 GB свободного диска).