GIS Engineering Cloud Architecture Vector Tiles Performance

The Cloud-Native Revolution in Web Mapping

How PMTiles on Cloudflare R2, Rust-powered microservices, and PostGIS optimization are replacing legacy GeoServer stacks—and cutting infrastructure costs by up to 90%.

90% Cost Reduction
97% HTTP overhead drop (PMTiles v3)
87% PostGIS polygon gen speedup
Cloud-native GIS architecture diagram showing PMTiles, Martin, and PostGIS stack
📅 ⏱ 18 min read ✍️ InfryneTechWorks Engineering

Why Monolithic GIS Servers Are Failing Modern Applications #

For almost two decades, Java-based mapping servers like GeoServer and MapServer were the unchallenged backbone of web mapping infrastructure. GeoServer was originally created in 2001 to act as a universal spatial translator—consuming raw PostGIS data and publishing it over OGC standards like WMS and WFS. It worked. But "it works" and "it scales" are very different things.

The core problem is architectural: every map pan, every zoom level change, every tile request must travel all the way to a centralized application server, spin up JVM threads, negotiate a database connection, execute spatial queries, serialize geometries, and return a payload—all in real time. Under high concurrent load, this pipeline becomes a catastrophic bottleneck.

⚠ JVM Memory Trap
When GeoServer's PermGen space (where Java class bytecode lives) is exhausted by its extensive class libraries, the entire server throws a fatal OutOfMemoryError—taking your entire mapping infrastructure offline instantly.

The JVM Tuning Nightmare

GeoServer infrastructure teams live in a perpetual arms race against the Java Virtual Machine. Every production deployment requires meticulous heap allocation, garbage collection tuning, and I/O disk cache management for JAI (Java Advanced Imaging) libraries. This inherently ties the compute node to expensive local block storage—directly undermining the elasticity that modern cloud architecture promises.

GeoServer JVM Parameters and Their Impact on Scalability
JVM Parameter Function Scalability Impact
-XmsInitial heap size on startupWastes idle cloud resources if set too high
-XmxMaximum memory allocation poolLimits concurrent tile rendering threads
-XX:MaxPermSizeClass bytecode storage (PermGen)Exhaustion causes catastrophic server crashes
-XX:NewRatioOld vs new generation heap ratioMust be tuned per-workload for GC stability

The deepest issue? For map data that doesn't change minute-to-minute—municipal boundaries, road networks, flood zones—running a dynamic compute layer is pure architectural waste. That realization is the seed of the modern cloud-native geospatial movement.

Browser Tile Request /z/x/y.pbf HTTP Load Balancer + SSL term Route GeoServer Java / JVM JVM Thread Pool Geometry Serializer ❗ PermGen OOM Risk ~2–8 GB heap required JDBC PostGIS ST_AsGeoJSON WKB Transport Response ← BOTTLENECK ZONE → Fig. 1 — Monolithic GIS Pipeline: Every tile request traverses the full server stack
Fig. 1 — The monolithic mapping pipeline: every request traverses the full server stack, making horizontal scaling expensive and fragile.

Cloud-Native Geospatial Formats: The Foundation #

The paradigm shift doesn't just replace one server with a faster one. It removes the server entirely for the majority of use cases, leveraging a new class of cloud-native file formats engineered around one key HTTP primitive: Range Requests.

Instead of querying a middleware layer, these formats allow clients to request only the exact bytes they need from a static file sitting in object storage—no running process required.

GeoTIFF (monolithic, download-all) Cloud Optimized GeoTIFF (range-request pixel fetch)
Shapefile (desktop-era binary) GeoParquet (columnar, cloud-indexed analytics)
MBTiles (SQLite, requires running DB engine) PMTiles (pure byte-range, no server needed)

Cloud Optimized GeoTIFF (COG) established this methodology for raster data, using internal image pyramids to let clients fetch only the pixels needed for the current viewport. GeoParquet brings the same efficiency to vector analytics via columnar storage with native spatial indexing. FlatGeobuf enables bounding-box-filtered vector streaming directly over HTTP. Together, they prove that intelligent file structure can replace entire tiers of middleware.

PMTiles: The Gold Standard for Static Map Delivery #

For spatial data with update cycles measured in days, weeks, or months—parcel boundaries, road networks, flood zones—PMTiles by Protomaps is the definitive answer. It's a single-file binary archive that stores an entire multi-scale tile pyramid, served directly to browsers via HTTP Range Requests with zero server-side compute.

"PMTiles resolves the MBTiles friction by arranging tiles into an internally structured, read-only contiguous byte sequence navigable using standard HTTP headers—no running database engine required."

PMTiles v3: A Quantum Leap in Efficiency

The v3 specification wasn't just an incremental improvement—it was an architectural rethink of how tiles are addressed, stored, and fetched.

  • 01
    Hilbert Curve Spatial Addressing
    Instead of Z/X/Y coordinates, v3 uses a 64-bit Tile ID derived from a Hilbert curve—a fractal space-filling mathematical construct. Geographically adjacent tiles become sequentially adjacent bytes, dramatically improving CDN cache hit ratios on any given viewport.
  • 02
    Run-Length Encoding for Redundancy
    The physical world is full of redundant empty space. Ocean tiles, desert tiles—millions of identical blobs. PMTiles v3's 4-field entry record (TileId, RunLength, Offset, Length) deduplicates these by pointing multiple consecutive IDs at the same byte offset, massively reducing archive footprint.
  • 03
    97% HTTP Overhead Reduction
    The initial request overhead dropped from 512 KB in v2 to just 16 KB in v3. Compressed directories now use roughly 10% of their previous space. Metadata limits were removed entirely, enabling rich analytical statistics in the archive itself.
PMTiles v2 vs v3 Specification Comparison
FeaturePMTiles v2PMTiles v3Architectural Impact
Tile Addressing17-byte Z/X/Y64-bit Hilbert Curve IDMaximizes spatial locality for sequential byte reads
Initial HTTP Overhead512 KB16 KB97% reduction → dramatically faster first paint
DeduplicationMultiple entries → same offsetRun-Length EncodingSubstantially compresses sparse physical features
Metadata Limit~300 KB hard capUnlimited JSONEnables rich analytical statistics and layer schemas
Directory SizeStandard entriesCompressed (10% of v2)Smaller network payload during heavy panning/zooming
PMTiles v2 — Z/X/Y Order Adjacent tiles scattered in memory → poor CDN locality 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 97% overhead ↓ PMTiles v3 — Hilbert Curve Order Spatially adjacent tiles → sequential bytes → high CDN hit rates 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Fig. 2 — Hilbert curve addressing: spatially adjacent tiles map to sequential byte positions, maximizing CDN locality
Fig. 2 — Hilbert curve spatial addressing: geographically adjacent tiles map to sequential byte positions, maximizing CDN locality and reducing distinct HTTP requests per viewport.

Infrastructure Economics: Escaping the Egress Trap #

PMTiles' technical brilliance only matters if you can serve it economically at scale. This is where the choice of object storage provider becomes the most important infrastructure decision you'll make.

Traditional hyperscale providers—AWS S3 and Google Cloud Storage—have negligible storage costs but impose severe egress fees on data leaving their networks. A high-traffic mapping application executing millions of HTTP GET Range Requests per day can generate billing that rapidly bankrupts a project.

✓ The Cloudflare R2 Solution
Cloudflare R2 is fully S3-compatible and charges zero egress fees, billing only on storage volume and Class A/B request counts (on cache misses only). For PMTiles workloads with high CDN hit rates, this is transformative.

Case Study: Sakay — 90% Cost Reduction in Production

The Sakay mapping application's engineering team executed a three-phase migration that became a widely cited proof point for the PMTiles/Cloudflare approach:

  • 01
    Custom Basemap Generation
    Downloaded raw OpenStreetMap .osm.pbf extracts for the Philippines, processed with Tilemaker on WSL2 into semantic layers (admin boundaries, highway networks), output as an .mbtiles intermediate.
  • 02
    MBTiles → PMTiles Conversion
    Used the go-pmtiles CLI to convert the SQLite-based archive into a single optimized .pmtiles file, ready for edge deployment.
  • 03
    Cloudflare R2 + Worker Deployment
    Transferred the archive via rclone to a Cloudflare R2 bucket, deployed a lightweight Cloudflare Worker to intercept HTTP requests and proxy byte-range fetches—enabling Range Request support across Cloudflare's global edge network.
Before (Mapbox/Maptiler SaaS)
$$$
Per-tile commercial SaaS fees; scaling = linear cost growth
After (PMTiles + Cloudflare R2)
-90%
Near-zero egress; storage cost only; scales freely at the edge
Postholer GIS Dataset
133GB
FEMA flood zones, parcel data, address points, building footprints—all serverless COG + FlatGeobuf

Dynamic Data: Rust-Powered Vector Tile Microservices #

PMTiles is the apex for static and periodically updated data. But not everything is static. Live parcel boundary edits, real-time flood sensor telemetry, continuous vehicle tracking, dynamic spatial aggregations—these require a live database connection and on-demand tile generation.

The engineering objective here shifts from eliminating the server to stripping it to its absolute minimum viable footprint: accept HTTP request, translate to optimized SQL, fetch the binary MVT payload from PostgreSQL, stream it back. Nothing more.

MEMORY FOOTPRINT (lower = better) THROUGHPUT SCORE (higher = better) GeoServer (Java) 95% 12% pg_tileserv (Go) 30% 58% Martin (Rust) 5% 97% Fig. 3 — Martin's Rust runtime delivers near-zero memory overhead and 8x the throughput of GeoServer
Fig. 3 — Architecture evolution from JVM-heavy GeoServer to the ultra-lean Rust-powered Martin microservice with zero-bloat memory model.

pg_tileserv: The Pioneer (Now Unmaintained)

Crunchy Data's pg_tileserv proved the Go microservice model was viable. It pioneered automated discovery—scanning PostGIS catalogs on startup to expose geometry columns as RESTful tile endpoints without configuration. It also enabled parameterized PL/pgSQL tile sources, passing dynamic URL parameters directly into the SQL execution layer.

But as of 2025, pg_tileserv is officially unmaintained. It supports only a single database source simultaneously, lacks asset generation capabilities, and hits performance ceilings under high concurrent workloads. It's a historical landmark, not a production choice.

Martin: The Undisputed Performance Leader

Martin, backed by the MapLibre project and engineered entirely in Rust, is the current gold standard for dynamic vector tile serving. It uses the actix-web framework—one of the fastest HTTP server frameworks in existence—combined with Rust's zero-cost abstractions, strict memory safety, and the moka async cache crate. Independent benchmarks consistently show Martin outperforming pg_tileserv, Tegola, and every Java-based alternative across latency and throughput.

  • 01
    Multi-Source Aggregation
    Connects to multiple Postgres databases simultaneously. Natively serves static PMTiles and MBTiles from disk or over HTTP. Composes disparate sources into composite vector tile layers for the client.
  • 02
    On-the-Fly Asset Generation
    Natively generates map styles, multi-resolution sprites, and font glyphs—consolidating the entire asset chain of a modern MapLibre GL JS application into a single binary deployment.
  • 03
    Native Serverless Readiness
    Rust's instantaneous startup and tiny binary footprint make Martin ideal for AWS Lambda, completely avoiding the catastrophic cold-start penalties that make Java applications unviable in serverless environments.
  • 04
    martin-cp Bulk Generation
    The companion martin-cp utility queries live PostGIS instances and copies tile ranges directly to portable .mbtiles archives, bridging the gap between dynamic compute and static storage optimization.
Feature comparison of GeoServer, pg_tileserv, and Martin
FeatureGeoServerpg_tileservMartin
LanguageJavaGoRust
Memory FootprintMassive (GBs of JVM Heap)LowUltra-Low (near-zero bloat)
Maintenance StatusActive (legacy focus)Unmaintained (2025)Highly Active (MapLibre-backed)
Database SupportExpansive via JDBC pluginsSingle PostgreSQL onlyMultiple simultaneous databases
Static Format ServingMinimal (requires extensions)None nativelyNative PMTiles & MBTiles
Serverless ViabilityUnviable (cold-start penalties)Yes (thin binary)Highly Optimized (AWS Lambda native)

PostGIS Optimization: Pushing the Database to its Limits #

A lean Martin microservice only shifts the bottleneck—from middleware to database. The true performance ceiling of your vector tile stack is PostGIS. The ST_AsMVT() and ST_AsMVTGeom() functions, introduced in PostGIS 2.5, moved the entire MVT encoding pipeline inside the database kernel. No more WKB transport to Mapnik middleware.

The ST_AsMVT Pipeline

-- Optimized CTE for vector tile generation
WITH mvt_geom AS (
  SELECT
    ST_AsMVTGeom(
      geom,
      ST_TileEnvelope(z, x, y),  -- tile bounding box
      4096,                       -- grid extent
      64,                         -- buffer pixels
      true                        -- clip geometries
    ) AS geom,
    id, name, category
  FROM parcels
  WHERE geom && ST_TileEnvelope(z, x, y)   -- bounding box pre-filter (uses spatial index)
)
SELECT ST_AsMVT(mvt_geom.*, 'parcels')      -- aggregate into Protocol Buffer binary
FROM mvt_geom
WHERE mvt_geom.geom IS NOT NULL;
SQL / PostGIS

ST_AsMVTGeom reprojects to Web Mercator (EPSG:3857), clips against the tile bounding box, and snaps vertices to a 4096-unit grid. ST_AsMVT then delta-encodes coordinates for maximum compression and serializes to the Protocol Buffer binary format.

CARTO's Optimization Findings: 87% Faster Polygons

CARTO's engineering team profiled ST_AsMVTGeom deeply and found that the function was performing redundant polygon validity checks before grid snapping—computationally prohibitive and mathematically unnecessary. By disabling deep validity tests and applying vertex simplification before coordinate translation:

PostGIS ST_AsMVTGeom performance comparison: standard vs optimized
Geometry TypeFeature CountStandard PostGISOptimized PostGISImprovement
Complex Polygons5,1455,299 ms687 ms87% Faster
Line Strings49,846350 ms290 ms17% Faster
Points489,4462,574 ms1,864 ms28% Faster

Spatial Indexing Strategy

Without the right indexes, none of the above matters. Three rules govern PostGIS indexing for vector tile workloads:

📐 Index Rules
1. GiST indexes are mandatory on all geometry columns for bounding box intersection. 2. SP-GiST indexes dramatically outperform GiST on dense point clusters. 3. Compound spatial indexes prevent full sequential scans on queries filtering by both geography and semantic attributes.

Every tile query must use the && (bounding box overlap) operator before any expensive geometric operations. This forces the query planner to use the spatial index and discard irrelevant rows before touching ST_Intersects or ST_Contains.

ST_Subdivide: Fixing the "Giant Polygon" Problem

A single highly detailed polygon—a national boundary, a vast forest footprint—can contain hundreds of thousands of vertices. Its bounding box covers enormous areas, triggering thousands of false-positive index reads on every tile request.

The solution is Geometry Subdivision during the ETL pipeline:

-- ETL step: subdivide large polygons before loading
INSERT INTO parcels_subdivided (geom, id, name)
SELECT ST_Subdivide(geom, 256), id, name   -- max 256 vertices per chunk
FROM parcels_raw
WHERE ST_NPoints(geom) > 256;
SQL / PostGIS

This fractures massive geometries into small, index-friendly chunks. The database then retrieves only the specific fragment physically intersecting the client's current viewport— radically reducing I/O load and serialization overhead.

Multi-Tiered Caching: The Final Performance Layer #

Even a perfectly optimized PostGIS database shouldn't be generating every tile from scratch on every request. Vector tile URLs are deterministic (/{z}/{x}/{y}.pbf)— making them ideal cache targets. Modern architectures deploy caching in two layers.

Global CDN Edge Cloudflare / Fastly CDN Hit ~85% Nginx Proxy Cache Tile URL z/x/y Proxy Hit ~10% Martin (Rust) moka async cache App Hit ~4% PostGIS Database ST_AsMVT + GiST DB Gen ~1% Disk / R2 Archive PMTiles static Static ← Request propagates inward only on cache miss (99%+ of requests served from CDN / proxy) Fig. 4 — Multi-tiered caching strategy reduces PostGIS load by 99%
Fig. 4 — Multi-tiered caching strategy: Nginx proxy cache intercepts repeated requests at the edge; Redis or moka provides application-layer TTL control for dynamic invalidation scenarios.

Deploying Nginx immediately in front of Martin provides a high-throughput caching tier resilient to traffic spikes. Repeated tile requests are served from disk cache without ever touching the Rust runtime or Postgres connection pool. Nginx's disk-based cache is memory-efficient, making horizontal scaling trivial.

Layer 2: Redis / Martin's Internal moka Cache

For applications requiring precise cache invalidation—live fleet tracking, incident response maps, real-time edits—Redis provides TTL control per key. Martin's internally integrated moka async cache framework provides similar benefits without the operational overhead of managing an external Redis cluster, making it the preferred choice for lean teams.

The Zoom-Level Caching Paradox

One of the subtler challenges in vector tile architecture is the non-linear cost relationship between zoom level and tile generation complexity:

Zoom-level specific caching strategies
Zoom LevelGeographic ExtentGeneration CostCache Strategy
z=0–5 (Low)Continental / GlobalExtremely HighMandatory: pre-bake with long TTL
z=6–12 (Mid)Regional / StateModerateStandard Nginx/Redis caching
z>13 (High)City Block / LocalExtremely LowBypass cache: fetch direct from PostGIS
💡 Key Insight
At high zoom levels (z>13), there are too many possible tiles for caching to be efficient (high churn rate), but PostGIS can generate them almost instantaneously due to sparse data. Let them bypass the cache entirely. Reserve cache memory for the expensive low-zoom planetary tiles.

Putting It Together: The Modern Geospatial Stack #

The modern cloud-native geospatial architecture is not a single technology—it's a deliberate composition of purpose-built tools across the entire pipeline:

  • Static / Periodic Data → PMTiles on Cloudflare R2
    Eliminate the server entirely. Zero egress fees. Global CDN edge delivery. Automated OSM-to-PMTiles pipelines for freshness.
  • Dynamic / Live Data → Martin on PostGIS
    Ultra-lean Rust microservice. Multi-source aggregation. Native serverless deployment. No JVM, no heap tuning, no cold-start hell.
  • Database Layer → Optimized PostGIS
    ST_AsMVT pipelines with proper && predicates, GiST/SP-GiST indexes, and ST_Subdivide preprocessing. CARTO-style validity optimization where needed.
  • Delivery → Zoom-Aware Multi-Tiered Caching
    Nginx proxy cache at the edge. Redis or moka for dynamic TTL invalidation. Pre-baked low-zoom tiles. Direct PostGIS for high-zoom locality.

The era of provisioning multi-gigabyte Java application servers to serve tiles that haven't changed since last Tuesday is over. The geospatial engineering community has found a better way—and it's cheaper, faster, and dramatically more scalable.

Ready to Migrate Your Mapping Stack?

InfryneTechWorks builds cloud-native geospatial infrastructure for organizations moving off legacy GIS servers. PMTiles deployment, Martin configuration, PostGIS optimization, and CDN architecture—we've done it.

Talk to Our Engineers More Articles

PMTiles Martin PostGIS Vector Tiles Cloud-Native GIS Cloudflare R2 GeoServer Migration ST_AsMVT