Benchmarks Tile servers
Candidates
Martin vs go-pmtiles vs tileserver-gl vs mbtileserver vs BBOX
Tick the candidates you care about and the charts, winners and table redraw for just them: same run, same rig, same samples. Winners come from the numbers alone; the story is in the post.
Single-tile latency
Test 1 Street level z14
Mean latency over one connection. The densest kind of tile: a 1.5 km Prenzlauer Berg tile with every building, road, POI and landuse polygon of the OpenMapTiles schema. (LOWER ms = FASTER)
Test 2 District z12
Mean latency over one connection. Inner-city tile covering Prenzlauer Berg and Mitte: roads, landuse, water and place labels, buildings dropped. (LOWER ms = FASTER)
Test 3 Region z9
Mean latency over one connection. Low-zoom tile covering most of Berlin: boundaries, major roads, water, place names. (LOWER ms = FASTER)
Test 4 Overview z5
Mean latency over one connection. Small tile at the top of the pyramid: the extract's footprint in one tile, mostly boundaries and a few labels. (LOWER ms = FASTER)
Winner mbtileserver (1.1 ms)
Throughput under concurrency
Test 1 Throughput, 10 connections
Successful street level z14 tiles per second with 10 closed-loop clients. (HIGHER req/s = BETTER)
Test 2 Throughput, 100 connections
Successful street level z14 tiles per second with 100 closed-loop clients. (HIGHER req/s = BETTER)
Test 3 p99 latency, 10 connections
Median across passes of each pass's p99 response time at 10 connections. A server that completed no request in a pass is charged the whole 12 s pass. (LOWER ms = FASTER)
Test 4 p99 latency, 100 connections
Median across passes of each pass's p99 response time at 100 connections. A server that completed no request in a pass is charged the whole 12 s pass. (LOWER ms = FASTER)
Test 5 Failed requests, 100 connections
Non-2xx responses plus connection errors and 60 s timeouts as a share of all requests at 100 connections. (LOWER % = FASTER)
Winner Martin (PMTiles) (12,634.2 req/s)
Random tile walk
Test 1 Throughput, 10 connections
Successful tiles per second when 10 closed-loop clients each request a random z14 tile from a 256-tile block of central Berlin. (HIGHER req/s = BETTER)
Test 2 p99 latency, 10 connections
Median across passes of each pass's p99 response time on the random walk. A server that completed no request in a pass is charged the whole 12 s pass. (LOWER ms = FASTER)
Winner Martin (MBTiles) (13,724.0 req/s)
Viewport burst
Test 1 Viewport, mean
Mean wall time until all four z14 tiles have arrived, requested in parallel. (LOWER ms = FASTER)
Test 2 Viewport, slowest
The slowest of the measured viewport loads. (LOWER ms = FASTER)
Winner Martin (PMTiles) (1.2 ms)
Cold start and footprint
Test 1 Time to first tile
Mean time from container process start until the street-level tile is served, including opening the archive and reading its metadata, over five starts. (LOWER ms = FASTER)
Test 2 Idle memory
Container RSS five seconds after the first tile, before any load. (LOWER MB = FASTER)
Test 3 Memory after load
Container RSS right after the concurrency passes. (LOWER MB = FASTER)
Winner BBOX (PMTiles) (117.6 ms)
Test conditions: rig, corpus, protocol
- Machine
- MacBook Pro
- Chip
- Apple M2 Max
- Cores
- 12 CPU cores (8 performance, 4 efficiency)
- Memory
- 96 GB
- OS
- macOS 26.6.2 (arm64)
- Runtime
- Docker 29.4.0 (OrbStack); one tile server container at a time (4 CPUs, 4 GB) with both archives bind-mounted read-only; oha 1.16.0 on the host
- Warmups
- 3 unmeasured passes
- Measured
- 20 passes per tool
- Process
- One server container at a time reading one of two archives with identical tiles (berlin.mbtiles from Planetiler 0.10.2, berlin.pmtiles converted from it by go-pmtiles); 4 workers where the concept exists; 5 cold starts, 20 single-tile passes of 20 requests per tile, 20 four-tile viewport bursts, 8 passes of 12 s at 10 and at 100 connections on one tile, 8 passes of 12 s at 10 connections over 256 random tiles
- Cache
- No server-side tile cache configured anywhere (go-pmtiles keeps its default 64 MB directory cache); archives in the page cache after warmups; every request accepts gzip, as map clients do, except the identity variant
- Output
- oha 1.16.0 JSON (latency percentiles, request counts, status codes); Bun fetch for cold start, parity and viewport bursts; docker stats for RSS
Scorecard
| Candidate | Single-tile latency (ms, lower is better) | Throughput under concurrency (req/s, higher is better) | Random tile walk (req/s, higher is better) | Viewport burst (ms, lower is better) | Cold start and footprint (ms, lower is better) |
|---|---|---|---|---|---|
| Martin (PMTiles) | 1.2 ms | 12,634.2 req/s | 12,992.8 req/s | 1.2 ms | 158.4 ms |
| Martin (MBTiles) | 1.2 ms | 9,185.5 req/s | 13,724.0 req/s | 1.2 ms | 153.2 ms |
| go-pmtiles | 2.0 ms | 4,513.5 req/s | 4,533.1 req/s | 1.9 ms | 157.4 ms |
| tileserver-gl-light | 14.3 ms | 281.2 req/s | 793.2 req/s | 18.7 ms | 276.4 ms |
| mbtileserver | 1.1 ms | 7,856.6 req/s | 7,110.4 req/s | 2.2 ms | 137.2 ms |
| BBOX (PMTiles) | 2.3 ms | 3,961.5 req/s | 8,292.2 req/s | 1.9 ms | 117.6 ms |
| BBOX (MBTiles) | 2.7 ms | 2,202.2 req/s | 6,503.4 req/s | 2.6 ms | 149.8 ms |
Martin is a project of the MapLibre organization. go-pmtiles and the PMTiles format are developed by Protomaps. tileserver-gl is developed by MapTiler. mbtileserver is developed by the Conservation Biology Institute. BBOX is developed by Sourcepole. Planetiler is developed by Michael Barry and contributors; the tile schema is OpenMapTiles. Map data: OpenStreetMap contributors, ODbL, via the Geofabrik Berlin extract of 2026-01-01; water polygons from OSMData, Natural Earth public domain.