Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

This document provides a high-level overview of Martin’s architecture, describing its major components, how they interact, and the rationale behind key design decisions.

Overview

Martin is a blazing fast tile server written in Rust that generates and serves vector tiles on the fly from multiple data sources. It is designed to handle heavy traffic and optimize for speed while maintaining a clean separation of concerns.

Data Storage

Martin Tile Server

Tile Sources

HTTP Requests

Cached Tiles/Resources

Supporting Resources

Sprite Generation
SVG to PNG

Font Glyphs
PBF Format

MapLibre Styles
JSON

Map Client
MapLibre, Leaflet, etc.

CLI Entry Point
martin binary

HTTP Server
Actix-Web

PostgreSQL
Tables & Functions

MBTiles Files

PMTiles Files
Local & Remote

Cloud Optimized
GeoTIFF

Tile Catalog
Source Registry

Tile/Resources Cache
Moka

PostgreSQL
PostGIS

File System
MBTiles/PMTiles

Object Storage
S3/Azure/GCP

Core Components

Martin’s architecture is organized into four main Rust crates, each with distinct responsibilities:

Purpose: The main tile server binary and HTTP service layer.

Location: /martin

Key Responsibilities:

  • HTTP server using Actix-Web framework
  • Request routing and endpoint handling
  • Configuration parsing (CLI args, env vars, config files)
  • Tile source discovery and initialization
  • Serving the Web UI for tile inspection

Main Modules:

  • src/bin/martin.rs - Server entry point
  • src/bin/martin-cp.rs - Bulk tile copying tool
  • src/srv/ - HTTP service handlers
    • server.rs - Main server setup and routing
    • tiles/ - Tile serving endpoints
    • fonts.rs - Font glyph serving
    • sprites.rs - Sprite serving
    • styles.rs - Style serving
  • src/config/ - Configuration management
    • args/ - CLI argument parsing
    • file/ - Config file parsing
  • martin-ui/ - React-based web interface

Data Flow

Data StoreTile SourceCacheCatalogHTTP ServerClientData StoreTile SourceCacheCatalogHTTP ServerClientalt[Tile in cache][Tile not in cache]GET /source_id/z/x/yResolve source_idSource referenceCheck cacheCached tile200 OK (tile data)Get tile(z, x, y)Query dataRaw dataGenerate MVTTile dataStore tile200 OK (tile data)

Key Design Decisions

Note

🧠 This section provides background and context, not required knowledge. You don’t need to understand or remember all of this to use or contribute to Martin. Read it when you’re curious why certain choices were made.

Rust for Performance and Safety

Why Rust: Martin is written in Rust to balance high performance with strong safety guarantees. (click to expand)
  • Near-C performance without manual memory management
  • Memory safety (no null pointers or buffer overflows)
  • Safe concurrency without data races
  • Zero-cost abstractions that compile to efficient code

Actix-Web Framework

Why Actix-Web: It offers a fast, production-ready async HTTP stack. (click to expand)
  • High-performance async request handling
  • Mature middleware ecosystem
  • Built-in compression and caching headers
  • Easy Prometheus metrics integration

Async/Await Throughout

Why async/await: Allows Martin to handle many concurrent requests efficiently. (click to expand)
  • Handles thousands of concurrent connections
  • Avoids blocking database queries
  • Enables efficient file and network I/O
  • Keeps thread usage low under load

Crate Separation

Why multiple crates: The codebase is split into crates with clear responsibilities. (click to expand)
  • martin-core — reusable core logic and tile sources
  • mbtiles — standalone MBTiles tooling
  • martin — HTTP server, configuration, and runtime wiring
  • martin-tile-utils — shared low-level tile utilities

This makes it easier to:

  • Embed Martin as a library in other Rust projects
  • Use MBTiles tools independently
  • Maintain clear API boundaries and versioning

PostgreSQL Connection Pooling

Why connection pooling: Reuse database connections instead of reconnecting per request. (click to expand)
  • Uses deadpool-postgres
  • Avoids per-request connection overhead
  • Configurable pool sizing
  • Automatic connection health checks

In-Memory Tile Caching

Why caching: Avoid regenerating frequently requested tiles. (click to expand)
  • Fast LRU cache with optional TTLs
  • Automatic eviction of least-used entries
  • Configurable memory limits (default: 512 MB)
  • Thread-safe concurrent access
  • Significant performance improvements for repeated requests

Automatic Source Discovery

Why auto-discovery: Martin tries to work out of the box with minimal configuration. (click to expand)

It can currently automatically detect:

  • PostgreSQL tables with geometry columns
  • PostgreSQL functions that return MVT
  • MBTiles and PMTiles files in configured directories

This keeps common setups close to zero-config.

Multi-Protocol Tile Support

Why multiple formats: Different workloads benefit from different storage models. This lets operators pick the best format for their use case:

Dynamic tiles generated from live data. Best for frequently changing datasets.

On-the-Fly Resource Generation

Why generate resources dynamically: Sprites, fonts, and styles are created on demand. (click to expand)
  • No pre-processing step required
  • Simpler deployments (just provide source files)
  • URL-based customization
  • Less storage overhead

Modular Configuration

Why layered configuration: Martin supports multiple configuration sources. (click to expand)
  • CLI flags for quick testing and overrides
  • Environment variables for containerized deployments
  • Config files for larger or more complex setups
  • Clear precedence between configuration layers

Component Interactions

PostgreSQL

Martin Server

Connection Pool
deadpool-postgres

Tables with
Geometry Columns

MVT Functions

PostGIS Extension

How it works:

  1. Martin connects to PostgreSQL using connection string
  2. Queries geometry_columns view to discover tables
  3. Queries pg_proc to discover MVT-returning functions
  4. Maintains connection pool for efficient query execution
  5. Generates tile SQL queries with bbox parameters
  6. Returns results as MVT tiles

Deployment Patterns

Martin supports multiple deployment patterns:

  • Single binary with embedded WebUI
  • Direct PostgreSQL connection
  • Local file serving
  • Suitable for small to medium deployments

Performance Characteristics

  • Bottleneck: Complex geometry queries on large tables
  • Optimization: Spatial indexes (GIST), connection pooling, query tuning
  • Increase CPU for faster tile generation
  • Increase memory for larger tile cache
  • Faster disk I/O for file sources
  • All tile coordinates validated (z/x/y bounds)
  • SQL injection prevention through parameterized queries
  • Path traversal prevention for file sources
  • URL parsing with strict validation

Extensibility Points

Adding New Tile Sources

To add a new tile source type:

  1. Implement the Source trait in martin-core
  2. Add configuration parsing in martin
  3. Register source in the catalog
  4. Add integration tests

Example source types that could be added:

  • Direct GeoJSON file serving
  • Vector tile rendering from raster data
  • Integration with other spatial databases

Adding New Resource Types

To add new resource endpoints:

  1. Implement resource generator in martin-core
  2. Add HTTP handler in martin/src/srv/
  3. Add configuration support
  4. Update catalog/discovery

Custom Authentication/Authorization

Martin doesn’t include built-in auth, but supports:

  • Reverse proxy authentication (recommended)
  • Custom Actix-Web middleware
  • Token-based access control via proxy

Monitoring and Observability

Martin exposes Prometheus metrics via /metrics:

  • HTTP request counters and histograms
  • Tile generation time histograms
  • Cache hit/miss rates
  • Database connection pool stats
  • Error rates by type