# Self-hosting routing

> The geolith router library and its Cloudflare Worker app, which reads a .glx routing part from R2 block by block. Not deployed for public use. Latency and CPU measured on a test Worker, and the work in progress to make it fast.

**Status: in development, not deployed for public use. There is no released router to download yet.** The router library (`geolith-router`, `crates/router` in `geolith/routing`) and a Cloudflare Worker app around it (`apps/worker`) exist. The Worker runs only as a test deployment on workers.dev. This page says how it runs and what was measured.

## The router library

The router library is pure Rust and does no I/O of its own: it asks for the bytes a request needs and the program around it fetches them. It builds for `wasm32-unknown-unknown`, so the same code runs natively and as WebAssembly. It keeps the tile index in memory and a byte-bounded cache of decompressed blocks (16 MiB by default).

## The Worker app

The Worker serves the six [endpoints](/routing/api) over the routing part of a `.glx` file in R2. It reads the blocks a request needs with R2 range reads and merges adjacent blocks into one read. Every answer carries its cost in `x-route-*` headers: R2 reads, bytes, fetch rounds, time spent waiting for R2, and wall time.

## Measured on the edge

A test Worker on workers.dev, with geolith's own Berlin build in R2, answered the 100 test routes from Mumbai. Two runs on 8 Nov 2026:

| Measure                        | Result                                    |
| ------------------------------ | ----------------------------------------- |
| Routes answered                | 100/100, no out-of-memory errors          |
| Latency, median                | 7.6 s and 7.7 s                           |
| Latency, p95                   | 27 s to 28 s                              |
| Fetch rounds per route, median | 26 to 27, each a 270 ms to 300 ms R2 read |
| Engine CPU per route, median   | 445 ms and 480 ms                         |
| `/isochrone`, 5-minute contour | Hits the Worker CPU limit                 |

The router is fast; the storage round trips are not. Each fetch round waits for the blocks the search has just found it needs, so a route pays one R2 read after another.

## In progress

- **A block store in KV and the Cache API**, as geolith search uses, so warm blocks do not need an R2 read.
- **Spatial read-ahead**, so one fetch round brings the blocks the search will need next.

The target is a warm median of 400 ms or less. Routing is not offered until it is fast enough.

## The file

A router needs a `.glx` file with a routing part. The builder writes one:

```bash
geolith --output-format routing --osm-pbf berlin.osm.pbf \
  --routing-output berlin-routing.glx
```

A file with both a search part and a routing part serves both: `glx-parts merge` joins the parts of a search file and a routing file into one file, and `glx-parts info` lists the parts a file holds and checks their checksums. Each reader reads only its own part.
