ESTalk to our team

Beyond Frameworks: Building TileserveX for Ultra-Low Latency Geospatial Processing

Vector tile servers usually force a choice between the ergonomics of a dynamic framework and the raw speed of a compiled binary. Pairing Plug and Cowboy with Rust NIFs removes the trade-off: here is the architecture behind TileserveX.

August 16, 2026

When you are building mission-critical geospatial infrastructure, the architecture decision usually collapses into two unappealing options.

High-level dynamic frameworks — Python, Node.js — give you developer ergonomics and a rich geospatial ecosystem. Under sustained CPU-bound load, they can also bring higher memory usage and bottlenecks such as Python’s GIL.

Pure compiled binaries — Rust, C++ — give you execution speed and small memory footprints. They also give you complex distributed state management, a steep learning curve for ordinary web routing, and real maintenance overhead once business logic gets involved.

We built TileserveX to find out whether that trade-off is actually necessary: a vector tile engine combining Elixir (Plug and Cowboy 2) with Rust (Rustler NIFs), implementing the OGC API specifications on top.

Architecture

TileserveX deliberately bypasses a full framework like Phoenix in favor of a minimal HTTP execution pipeline, separating web concurrency, state orchestration, and native CPU computation into three layers.

Three stacked layers. Top, the HTTP and routing layer on Cowboy 2 and Plug: direct dispatch of /ogc/* and /{z}/{x}/{y}.pbf endpoints, zero-overhead header injection and binary stream piping. Middle, the concurrency and state layer on the BEAM and OTP: in-memory ETS cache with TTL and a background janitor, GenServer-backed asynchronous OGC job execution, and PostGIS plus MBTiles and SQLite connection pool management. Bottom, the execution layer of Rust NIFs via Rustler: MVT 2.1 protobuf encoding and decoding, spatial math for buffer, simplify, centroid and convex hull, and coordinate transformations between EPSG:4326 and EPSG:3857.

Bare-metal routing: Plug and Cowboy 2

Phoenix has excellent ergonomics for full-stack interactive applications. Serving high-throughput binary vector tiles is a different problem: what you want is minimal pipeline overhead.

Wiring Plug.Router directly into Cowboy 2 avoids controller pipeline stack allocations, parameter parsing overhead, and channel pub/sub setup. Requests are routed and matched directly, streaming packed Protocol Buffer payloads from the storage layer or cache straight to network sockets.

Spatial computation in Rust NIFs

Computational geometry is CPU-bound by nature — line simplification, polygon clipping (Liang-Barsky, Sutherland-Hodgman), coordinate transformations, MVT 2.1 protobuf encoding.

Rather than letting that math block the primary BEAM schedulers, those operations are delegated to Rust NIFs through Rustler, configured on dirty CPU schedulers. Two properties matter here:

  • Zero-copy serialization. Vector geometry buffers pass between BEAM memory and the compiled Rust code with effectively no serialization overhead.
  • Memory and thread safety at compile time. Rust’s guarantees keep memory leaks and segmentation faults out of the Erlang VM, which is not a place you want either.

OGC API compliance

TileserveX implements the standards a geospatial platform is evaluated against:

  • OGC API — Tiles (Part 1): pre-rendered or dynamic vector and raster tiles in standard pyramid schemas.
  • OGC API — Features (Parts 1–3): queryable vector datasets with spatial filtering, property constraints, and flexible CRS support.
  • OGC API — Processes (Part 1): asynchronous execution of heavy spatial jobs — buffers, centroids, convex hulls — managed through BEAM GenServers.

What the design buys

The hypothesis behind this design is narrow: an Elixir service stripped down to Plug and Cowboy 2 and accelerated by Rust NIFs should keep OTP’s fault tolerance without paying for a framework it does not use.

Process isolation and dirty schedulers are what make that plausible. CPU-bound geometry never blocks the schedulers that answer requests, and a failure in one request stays in that request. When a database goes away, the server answers 503 with Retry-After instead of hanging.

The useful takeaway for anyone making this decision: the choice between compiled execution speed and actor-model concurrency is less binary than it looks. Elixir for connection routing and process isolation, Rust for the heavy spatial math, is a combination worth considering before you commit to either extreme.

Want this thinking on your system?

Tell us what you’re building or what’s stuck.

Talk to our engineers