Home -> Benchmarks

Benchmarks

Fair, reproducible, and honest about the wins.

We put TerraServe head to head with MapServer 8.6, GeoServer 2.26 and TiTiler (rio-tiler) on the same data, the same style and the same container, and we report what we found, including where TerraServe does not win. Everyday render speed is a close race. The durable gaps are memory returned after load, footprint, and how each engine holds up under a crowd.

Method

What we measured.

A benchmark is only worth the setup behind it. Here is the whole setup, written down, so you can check it or run it yourself.

Data COS2023, the official Portuguese land-cover map: 842,413 MultiPolygons in EPSG:3763. Neutral third-party data from the national mapping agency, not a TerraServe fixture.
Style 108 fill classes from one SLD. TerraServe and GeoServer read that SLD directly; MapServer reads a mapfile generated from the same SLD. The rendered output was checked to be identical.
Workload WMS 1.3.0 GetMap, 256×256, panning over 81 distinct 12 km windows in interior Portugal, so every tile actually has data to draw.
Load 300 warmup requests discarded, then 600 measured at concurrency 16, which is the host core count.
MapServer The production model: 16 persistent FastCGI workers (not a single mapscript process), with its GDAL cache capped to match TerraServe's memory budget.
GeoServer Tomcat with a 4 GB heap, warmed before measuring, with its tile cache off so it is a like-for-like live render.
TerraServe The 146 MB release image, reading the file windowed. Measured both with no cache and with a response cache.
Second workload The TiTiler comparison is a different job and gets its own run: a Cloud-Optimized GeoTIFF turned into z/x/y raster tiles, first from local disk and then from object storage with NDVI computed per tile. TiTiler does not serve styled vector WMS, so it cannot appear in the three-engine table above.
TiTiler (rio-tiler) rio-tiler 9.4.2 behind 16 gunicorn workers, one per core, the same as MapServer gets. The published image ships with a single worker, so extra gunicorn workers have to be added for the comparison to be fair. Both engines pinned to 256 pixel tiles, both reprojecting on the fly, and the two PNGs compared before any timing was trusted.
Memory The memory a process must free itself (cgroup anonymous memory, which excludes reclaimable page cache), sampled at baseline, peak and after the load settles.
Hardware 16 cores, 63 GB. The numbers move with the machine and the data; the method does not.

Live render

A close race, and we say so.

Drawing the map live, with no front cache, the three engines are within a whisker of each other. TerraServe does 279 requests a second, which is 0.94 times MapServer and 1.40 times GeoServer. MapServer 8.6 is genuinely fast on this workload and beats TerraServe on per-request latency. There is no "many times faster render" claim here, and we will not make one.

Engine (live render) req/s p50 ms p95 ms memory, settled image
MapServer 8.6.4 FastCGI × 16 296 25 40 408 MB 595 MB
TerraServe windowed, no cache 279 55 94 119 MB 146 MB
GeoServer 2.26.1 Tomcat, 4 GB heap 200 72 146 1905 MB ~608 MB

Wondering where TiTiler is? It does not serve styled vector WMS, so putting it in this table would compare two different jobs. It gets its own run on the workload it is built for, a raster COG turned into tiles: TerraServe vs TiTiler / rio-tiler.

All three engines returned 600 of 600 requests and drew an identical map.

The real win

Memory it gives back.

Peak memory under load is similar for TerraServe and MapServer, around 425 MB. The difference is what each engine gives back when the rush passes. TerraServe settles to 119 MB. MapServer holds 408 MB, about 3.4 times more, because its GDAL cache stays warm. GeoServer holds 1905 MB, about 16 times more, because the JVM keeps its heap. TerraServe frees a request's memory the instant it returns, so it settles back below where it started.

Memory settled after a burst
119 MB
TerraServe, versus 408 MB kept by MapServer and 1905 MB by GeoServer.
The whole server image
146 MB
Clean-room, no GDAL, against roughly 600 MB for the others.

Under load

The crowd is where it flips.

At low concurrency MapServer has the better tail. As more people arrive together the picture changes. Throughput plateaus for everyone once you pass the core count, so the number that matters is how long the slowest requests wait. Here is p95 tail latency as the crowd grows from 16 to 128 at once.

p95 tail (ms) 16 at once32 at once64 at once128 at once
TerraServe 98212324569
MapServer 8.6 416010272039

At 128 requests at once, TerraServe's slowest 5% wait about 0.57 seconds. MapServer's wait about 2 seconds. That gap, plus the memory it hands back, is what makes TerraServe comfortable on a small box in a traffic spike.

vs rio-tiler

A draw on speed, and we will say so.

MapServer and GeoServer are the incumbents. TiTiler, built on rio-tiler and GDAL, is the modern peer: the same job, a Cloud-Optimized GeoTIFF turned into a tile on demand, and the stack most new tile services actually deploy. Someone asked us for this comparison publicly, so we ran it. Same COG, same z/x/y tiles, same 256 pixel PNG, both reprojecting on the fly, output checked to be the same scene.

At once TerraServe req/s TiTiler req/s TerraServe memory TiTiler memory
1 42.3 23.9 406 MB 2335 MB
16 239.5 203.9 626 MB 2541 MB
64 235.8 221.4 806 MB 2896 MB

On throughput this is a draw, about 1.1 to 1.3 times, and with our cache switched off the two are level per request. rio-tiler and GDAL are well built and we are not claiming a faster reader. The gap is memory: 3.5 to 6 times less. TiTiler needs around 2.4 GB to keep up because it reaches that speed with 16 worker processes, each carrying its own Python interpreter and GDAL cache. TerraServe does it in one process, and hands the memory back afterwards.

Two settings decide whether a number like this means anything

The TiTiler image ships with one worker. Measured that way it reaches 14.5 requests a second under load, so extra gunicorn workers have to be added for the comparison to be fair: we ran it with 16 workers, one per core, the same as MapServer gets. The reverse applies to us. Our own tiles default to 512 pixels against TiTiler's 256, which would have had us render four times the pixels for no reason, so we pinned both to 256 and checked the two images matched before trusting a single timing. Neither default is wrong, they are just different, and a benchmark that ignores that is measuring packaging rather than software.

From object storage

In the cloud, the network wins.

We ran the same pair against a Sentinel-2 stack sitting in object storage, computing NDVI live per tile. This is the case rio-tiler was designed for, and we expected our deeper read parallelism to show. It did not.

At once TerraServe req/s TiTiler req/s
1 2.4 1.9
4 3.8 4.3
16 4.1 4.3

Both engines sit at four to five requests a second, and the giveaway is that TiTiler with one worker matches TiTiler with sixteen. When the worker count stops mattering, you are measuring the link, not the software. Fetching a tile's worth of bytes across the internet costs both of us about half a second, and no amount of engine design hides that. The memory gap does survive the trip, at roughly six times. We think the honest reading is that for cloud-hosted data the network is the bottleneck, and the engine you pick matters less than where you run it.

On caching

The number we left out.

TerraServe's response cache serves a repeated tile in a few milliseconds, over 5000 a second. That is a cache-hit number, not a render number, so we keep it out of the comparison. A fair cached test would put a tile cache in front of MapServer and GeoServer too. Publishing our cache against their live render is exactly the unfair setup this whole method exists to avoid.

The same rule cost us a much better looking number in the cloud test. With its tile cache on, TerraServe reports 232 requests a second against TiTiler's four. That is not a cloud result: the test area is only 182 distinct tiles, small enough to sit entirely in the cache, so it was answering from memory while the other engine fetched from Paris. The figure in the table above is the one with our cache off, because that is the one that compares like with like.

Reproduce it

Run it yourself.

Every number on this page comes from a script that ships with the code. Point it at the three engines and you get this table, on your hardware and your data.

# the exact three-engine comparison above, reproducible
python3 bench/cos2023_vector_bench.py

# TerraServe against rio-tiler, on local disk and then from object storage
python3 bench/cog_tiles_bench.py
PROFILE=s3 python3 bench/cog_tiles_bench.py

Measured on a 16-core, 63 GB host: the three-engine comparison 2026-07-23, the rio-tiler comparison 2026-07-27. MapServer 8.6.4, GeoServer 2.26.1, TiTiler with rio-tiler 9.4.2, and the TerraServe release image.