Walk-relevant POIs (school/shop/park/kindergarten/pharmacy/stops) now route via a FOOT OSRM graph (osrm-walk service), car-relevant POIs (mall/hospital + unknown) keep the DRIVING graph. Validation showed driving overstated pedestrian-proximity distance — median 1.6-2.9x straight-line (#39). - config: osrm_walk_local_url + osrm_walk_categories (frozenset, 9 walk cats) - osrm_client_local: base_url override on get_road_distances_m (default unchanged) - _apply_osrm_road_distances: split POIs by category, per-group OSRM call with independent graceful fallback (one server down -> its group keeps straight-line), in-place write-back by original index; never raises; flag-OFF byte-identical - docker-compose: osrm-walk service (foot graph, internal, mem_limit 1.5g) - build_osrm.sh: CAR=0 gate for foot-only refresh (doesn't touch live car graph) - tests: per-category split, per-group fallback, asymmetric intra-group write-back Still flag-gated (use_osrm_distances OFF) — enabling is a product decision. Refs #39
128 lines
7.1 KiB
Markdown
128 lines
7.1 KiB
Markdown
# 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 свободного диска).
|