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. リアルタイムなキャッシュ管理
データの更新時には、以下の流れでキャッシュを適切に管理します:
- フロントエンドからデータ更新リクエスト
- Django APIでデータベース更新
- VarnishへのBANリクエスト送信
- 関連するタイルキャッシュを無効化
- 次回アクセス時に最新タイル生成
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}")
デモアプリケーションの機能
構築したシステムでは、以下の機能を実装しています:
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
アクセス
- フロントエンドアプリケーション: http://localhost:5173
- Django API ドキュメント: http://localhost:8000/docs
- Martin WebUI: http://localhost:8080 (Varnish経由でアクセス)
Django APIは以下のようなエンドポイントを実装しています。
パフォーマンス最適化のポイント
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

