# Serving Architecture

How the Martin tile server is put together and how it answers requests. [Source Architecture](<https://maplibre.org/martin/architecture-sources/index.md>) covers how each kind of source works and how sources reload at runtime, and [Generation Architecture](<https://maplibre.org/martin/architecture-generate/index.md>) covers the bulk mode of `martin cp` and how the server renders table sources with `layers`.

## 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.

```
graph TB
    Client[Map Client<br/>MapLibre, Leaflet, etc.]

    subgraph Martin["Martin Tile Server"]
        CLI[CLI Entry Point<br/>martin binary]
        Server[HTTP Server<br/>Actix-Web]

        subgraph Sources["Tile Sources"]
            PG[PostgreSQL<br/>Tables & Functions]
            MBT[MBTiles Files]
            PMT[PMTiles Files<br/>Local & Remote]
            COG[Cloud Optimized<br/>GeoTIFF]
            DuckDB[DuckDB / GeoParquet]
        end

        subgraph Resources["Supporting Resources"]
            Sprites[Sprite Generation<br/>SVG to PNG]
            Fonts[Font Glyphs<br/>PBF Format]
            Styles[MapLibre Styles<br/>JSON]
        end

        Engine[Tile generation<br/>martin-tilegen]
        Catalog[Tile Catalog<br/>Source Registry]
        Cache[Tile/Resources Cache<br/>Moka]
    end

    subgraph Storage["Data Storage"]
        DB[(PostgreSQL<br/>PostGIS)]
        Files[File System<br/>MBTiles/PMTiles/GeoParquet]
        S3[Object Storage<br/>S3/Azure/GCP]
        HTTP[HTTP<br/>GeoParquet]
    end

    Client -->|HTTP Requests| Server
    Server --> Catalog
    Catalog --> Sources
    Server --> Resources
    Server --> Cache

    PG --> DB
    PG -->|tables with layers| Engine
    MBT --> Files
    PMT --> Files
    PMT --> S3
    COG --> Files
    COG --> S3
    DuckDB --> Files
    DuckDB --> HTTP

    Cache -.->|Cached Tiles/Resources| Client
```

## Crates

Martin's architecture is organized into six main Rust crates, each with distinct responsibilities:

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

**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
- Runtime source reloading (filesystem watch / polling) without a restart
- Serving the Web UI for tile inspection

**Purpose**: Core abstractions and implementations for tile sources and supporting resources.

**Key Responsibilities**:

- Abstract tile source traits and implementations
- PostgreSQL connection pooling and query execution
- MBTiles and PMTiles reading
- Cloud Optimized GeoTIFF (COG) tile extraction
- DuckDB / GeoParquet tile generation
- Sprite, font, and style resource generation
- Tile format handling (MVT protocol buffers)

**Purpose**: MBTiles format support and manipulation tools.

**Key Responsibilities**:

- SQLite-based MBTiles reading and writing
- Metadata management
- Tile compression (gzip, brotli)
- Validation and integrity checking
- Diff/patch operations between MBTiles files
- Schema management

**Purpose**: Low-level tile manipulation and conversion utilities.

**Key Responsibilities**:

- Tile coordinate system conversions
- Tile encoding/decoding
- Tile format utilities
- Bounding box calculations

**Purpose**: The tile generation engine behind the bulk mode of `martin cp` and PostgreSQL table sources with `layers`. It turns features into tiles inside Martin, using the tile generation method adapted from [Planetiler](<https://github.com/onthegomap/planetiler>).

**Key Responsibilities**:

- Compiling layers and their CEL expressions into a plan
- Rendering every feature at every zoom into tile pieces
- Sorting the pieces on disk by tile, and merging them back
- Encoding tiles in parallel and writing them in archive order
- Rendering a requested tile, or its prefetch block, in memory for the tile server

See [Generation Architecture](<https://maplibre.org/martin/architecture-generate/index.md>).

**Purpose**: Clipping integer geometries into per-tile pieces on a tile grid, used by `martin-tilegen`.

**Key Responsibilities**:

- Slicing lines, points and polygons into the tiles they touch, keeping original vertices
- Valid polygon pieces with synthetic edges along the tile buffer
- Reassembling pieces from neighboring tiles into whole geometries

## Requests and startup

```
sequenceDiagram
    participant Client
    participant Server as HTTP Server
    participant Catalog
    participant Cache
    participant Source as Tile Source
    participant DB as Data Store

    Client->>Server: GET /source_id/z/x/y
    Server->>Catalog: Resolve source_id
    Catalog-->>Server: Source reference
    Server->>Cache: Check cache

    alt Tile in cache
        Cache-->>Server: Cached tile
        Server-->>Client: 200 OK (tile data)
    else Tile not in cache
        Server->>Source: Get tile(z, x, y)
        Source->>DB: Query data
        DB-->>Source: Raw data
        Source->>Source: Generate MVT
        Source-->>Server: Tile data
        Server->>Server: Postprocess (MLT, compress)
        Server->>Cache: Store postprocessed tile
        Server-->>Client: 200 OK (tile data)
    else Tile not in cache, source prefetches blocks
        Server->>Source: Get block holding (z, x, y)
        Source->>DB: Query features of the block
        DB-->>Source: Features
        Source->>Source: Render every tile of the block
        Source-->>Server: Tiles of the block
        Server->>Server: Postprocess each tile
        Server->>Cache: Store every postprocessed tile
        Server-->>Client: 200 OK (tile data)
    end
```

```
sequenceDiagram
    participant CLI
    participant Config as Config Parser
    participant Discovery as Source Discovery
    participant Sources as Tile Sources
    participant Server as HTTP Server

    CLI->>Config: Parse args & config file
    Config->>Discovery: Initialize sources

    alt PostgreSQL Source
        Discovery->>Discovery: Connect to database
        Discovery->>Discovery: Query tables & functions
        Discovery->>Sources: Register table sources
        Discovery->>Sources: Register function sources
    end

    alt File Sources
        Discovery->>Discovery: Scan MBTiles files
        Discovery->>Discovery: Scan PMTiles files
        Discovery->>Sources: Register file sources
    end

    alt DuckDB Sources
        Discovery->>Discovery: Read duckdb.sources from config
        Discovery->>Discovery: Introspect GeoParquet metadata
        Discovery->>Discovery: Discover database tables & macros
        Discovery->>Sources: Register DuckDB sources
    end

    Sources-->>Server: Source catalog
    Server->>Server: Setup routes
    Server->>Server: Start HTTP listener
```

```
sequenceDiagram
    participant Client
    participant Server
    participant Sprite as Sprite Generator
    participant Font as Font Generator
    participant FS as File System

    Client->>Server: GET /sprite/sprite_id
    Server->>Sprite: Generate sprite
    Sprite->>FS: Read SVG files
    FS-->>Sprite: SVG data
    Sprite->>Sprite: Render to PNG
    Sprite->>Sprite: Generate JSON index
    Sprite-->>Server: Sprite sheet
    Server-->>Client: PNG/JSON response

    Client->>Server: GET /font/fontstack/range
    Server->>Font: Generate glyphs
    Font->>FS: Read font files
    FS-->>Font: Font data
    Font->>Font: Rasterize glyphs
    Font->>Font: Encode as PBF
    Font-->>Server: Glyph data
    Server-->>Client: PBF response
```

## Deployment

Martin supports multiple deployment patterns:

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

- Docker image with all dependencies
- Configuration via environment variables
- Health check endpoints
- Suitable for Kubernetes and container orchestrators

- Lambda adapter for Actix-Web
- Cold start optimization
- Stateless operation
- Suitable for sporadic traffic

- NGINX or Apache fronting Martin
- Additional caching layer
- SSL termination
- Load balancing across multiple Martin instances

## Performance

- **Bottleneck**: Complex geometry queries on large tables
- **Optimization**: Spatial indexes (GIST), connection pooling, query tuning

- **Bottleneck**: MVT encoding CPU time
- **Optimization**: Tile caching, pre-generated MBTiles for static data

- **Bottleneck**: High tile request rate
- **Optimization**: Async I/O, connection keep-alive, compression

- **Bottleneck**: Large tile cache size
- **Optimization**: LRU eviction, configurable cache size, streaming responses

- Increase CPU for faster tile generation
- Increase memory for larger tile cache
- Faster disk I/O for file sources

- Run multiple Martin instances behind load balancer
- Each instance maintains its own cache
- Shared PostgreSQL database with connection pooling
- CDN for tile distribution

## Security

- 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

- Read-only database user recommended
- Connection string security (avoid logging)
- SSL/TLS support for PostgreSQL connections
- Certificate validation for secure connections

- CORS configuration for cross-origin requests
- Rate limiting (via reverse proxy)
- Authentication/authorization (via reverse proxy)
- HTTPS termination (via reverse proxy)

## Extending Martin

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:

- Vector tile rendering from raster data
- Integration with other spatial databases

To add new resource endpoints:

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

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

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

## Monitoring

These are the current monitoring and observability options:

Martin exposes Prometheus metrics via `/_/metrics`:

- HTTP request counters and histograms

If you need more in-depth observability, we would be happy to review PRs that add additional metrics; the current implementation is intentionally minimal.

We allow health checks via `/health` and `/catalog` endpoints:

- `/health` - Basic health check (HTTP 200)
- `/catalog` - Source availability check

- logging via `env_logger`
- Configurable log levels
- Request/response logging
