4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PostGIS + Martin + Varnish + Maplibreで実現するダイナミックベクタータイル配信システム

Last updated at Posted at 2025-12-10

PostGIS + Martin + Varnish + Maplibreで実現するダイナミックベクタータイル配信システム

この記事はFOSS4G Advent Calendar 2025の13日目の記事です。

はじめに

ベクタータイルの配信において、パフォーマンスとリアルタイム性の両立は重要な課題です。本記事では、PostGIS、Martin、Varnishを組み合わせて、高性能かつリアルタイムな編集に対応したベクタータイル配信システムを構築する方法を紹介します。

実際のプロジェクトとして、ルワンダの上下水道公社(WASAC)の水道管データを使用したデモシステムを作成しました。

システム構成

今回構築するシステムの全体構成は以下の通りです:

各サービスの役割

サービス ポート 説明 Dockerイメージ
frontend 5173 MapLibre GL JSを使用したSvelteKitアプリケーション frontend/Dockerfile
varnish 8080 HTTPキャッシュアクセラレータ。BANメソッドによる無効化対応 varnish:stable
nginx 80 VarnishとMartin間のリバースプロキシ nginx:alpine
martin 3000 PostGISからベクタータイルを配信するタイルサーバー ghcr.io/maplibre/martin:1.0.0
web 8000 パイプデータ管理とキャッシュパージ用のDjango API server/Dockerfile
db 25432 パイプの地理情報と属性を格納するPostGISデータベース kartoza/postgis:17-3.5

主要技術スタック

Martin - PostGISベクタータイルサーバー

Martinは、PostGISから直接ベクタータイルを生成・配信するオープンソースのタイルサーバーです。以下の特徴があります:

  • PostGISの関数やビューから直接タイル生成
  • 高速なタイル配信
  • 設定ベースの簡単なセットアップ

Varnish - HTTPキャッシュ

Varnishは高性能なHTTPキャッシュサーバーです。今回のシステムでは以下の機能を活用:

  • タイルのキャッシュによる高速配信
  • BANメソッドによる柔軟なキャッシュ無効化
  • 設定可能なキャッシュポリシー

システムの特徴

1. 高性能なタイル配信

Varnishによるキャッシュ機能により、一度生成されたタイルは高速に配信されます。初回アクセス時はPostGISから生成され、以降はキャッシュから配信されるため、大幅な性能向上を実現できます。

2. リアルタイムなキャッシュ管理

データの更新時には、以下の流れでキャッシュを適切に管理します:

  1. フロントエンドからデータ更新リクエスト
  2. Django APIでデータベース更新
  3. VarnishへのBANリクエスト送信
  4. 関連するタイルキャッシュを無効化
  5. 次回アクセス時に最新タイル生成

3. インタラクティブな編集機能

MapLibre GL JSのrefreshTiles()メソッドを活用し、データ更新後に即座にマップを更新します:

// データ更新後のタイル再読み込み
map.refreshTiles('pipes');
map.redraw();

実装のポイント

Martinの設定

Martinのキャッシュ機能は無効化し、上位のVarnishでキャッシュを管理します:

# martin/config.yml
cache_size_mb: 0

Nginxでのリバースプロキシ設定

Martinのエンドポイントを/tiles以下にマッピング:

location ~ /tiles/(?<fwd_path>.*) {
    proxy_set_header        Host $host;
    proxy_set_header        X-Forwarded-Proto "http";
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Host $host:$server_port;
    proxy_set_header        X-Rewrite-URL $uri;
    proxy_redirect          off;

    proxy_connect_timeout   5m;
    proxy_send_timeout      5m;
    proxy_read_timeout      5m;
    send_timeout            5m;

    proxy_cache             off;
    proxy_pass              http://martin_upstream/$fwd_path$is_args$args;
}

Varnishによるキャッシュ管理

/tilesエンドポイントのレスポンスをキャッシュし、BANメソッドによる無効化に対応:

# varnish/default.vcl
sub vcl_recv {
    if (req.url ~ "^/tiles/") {
        return (hash);
    }
}

sub vcl_ban {
    ban("req.url ~ " + req.http.X-Ban-Pattern);
    return (synth(200, "Ban added"));
}

/tiles以外のエンドポイントはキャッシュせずにMartinに常時リクエストします。

例えば、curlを使って、/tiles/function.public.pipes*のキャッシュを無効化したい場合は以下のようにします。

curl -X BAN "http://localhost:8080/tiles/function.public.pipes*"

<!DOCTYPE html>
<html>
  <head>
    <title>200 BAN applied for pattern: /tiles/public.pipes</title>
  </head>
  <body>
    <h1>Error 200 BAN applied for pattern: /tiles/public.pipes</h1>
    <p>BAN applied for pattern: /tiles/public.pipes</p>
    <h3>Guru Meditation:</h3>
    <p>XID: 196615</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>

これにより、全てのtile.jsonと個別のタイルエンドポイントのキャッシュを無効にできます。

Djangoでのキャッシュ無効化の実装

データ更新時にDjangoから特定のタイルパターンを無効化:

# Django views.py
def _purge_cache(self):
    """Purge varnish cache for pipes tiles"""
    varnish_url = os.environ.get('VARNISH_URL', 'http://varnish:80')
    ban_pattern = '/tiles/function.public.pipes*'
    
    try:
        response = requests.request(
            'BAN', 
            varnish_url,
            headers={'X-Ban-Pattern': ban_pattern}
        )
        logger.info(f"Cache purged successfully: {response.status_code}")
    except Exception as e:
        logger.error(f"Failed to purge cache: {e}")

デモアプリケーションの機能

構築したシステムでは、以下の機能を実装しています:

demo

1. インタラクティブなマップ表示

  • 水道管の材質による色分け表示
  • 直径による線幅の視覚化
  • ズームレベルに応じたラベル表示

2. 属性編集機能

  • パイプクリックによるモーダルダイアログ表示
  • 材質、設置年、直径の編集
  • リアルタイムなマップ更新

3. 高性能な表示

  • VarnishキャッシュによるPostGISからの高速タイル配信

セットアップと使用方法

ソースコードのダウンロード

git clone git@github.com:watergis/maplibre-martin-postgis.git
cd maplibre-martin-postgis

環境設定

# 環境変数の設定
cp .env.example .env

Protomapsのアカウントを作成し、APIキーを.envに設定してください。

Protomapsのアカウントはhttps://protomaps.com/accountから作成できます。

キー作成時のCORSは以下のURLを設定してください。

http://localhost:5173
ttp://localhost:4173

Docker環境の構築

# Dockerイメージのビルド
make build

# 全サービスの起動
make up

アクセス

Django APIは以下のようなエンドポイントを実装しています。

API Documentation

パフォーマンス最適化のポイント

1. 適切なキャッシュ戦略

  • タイル単位での細かいキャッシュ制御
  • データ更新時の部分的なキャッシュ無効化

2. 効率的なデータベース設計

  • 適切な空間インデックスの設定
  • PostGIS関数による動的タイル生成

3. 段階的なプロキシ構成

  • Nginx(リバースプロキシ)→ Varnish(キャッシュ)→ Martin(タイル生成)

まとめ

PostGIS + Martin + Varnishの組み合わせにより、以下を実現できました:

  • 高性能: Varnishキャッシュによる高速なタイル配信
  • リアルタイム性: BANメソッドによる柔軟なキャッシュ管理
  • スケーラビリティ: 段階的なプロキシ構成による負荷分散
  • 開発効率: オープンソースツールの組み合わせによる迅速な開発

このアーキテクチャは、地理空間データのWebアプリケーション開発において、パフォーマンスと機能性を両立させる有効なアプローチです。

謝辞

デモデータは、ルワンダの上下水道公社(WASAC, Ltd.)のオープンデータを使用させていただきました。実際のシステムはこちらで運用されています。

データを使用する際は、適切なクレジット表記をお願いします:
©<a href='http://wasac.rw'>WASAC,ltd.</a>

リポジトリ

完全なソースコードは以下のリポジトリで公開しています:
https://github.com/watergis/maplibre-martin-postgis


ライセンス

ソースコードはMITライセンス、デモデータはCC BY 4.0ライセンスで提供されています。

© Jin Igarashi

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?