#概略
先日投稿した、H2OをいじったOpenStreetMap 用のタイルサーバの性能を測ってみた。
#tl;dr
- H2Oベースの実装は、既存の nginx ベースのタイルサーバより3倍速い
- ただし、この性能差はソフトウェア ではなく、主にデータ形式の違いによるもの
#実験条件
##実行環境
- 仮想4コア・メモリ8G の VMWare の仮想マシン
- ゲスト OS は Debian 8.0:
Linux debian 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt9-3 (2015-04-23) x86_64 GNU/Linux
- ホストは i7-4770K の Windows 8.1
nginxのパラメータチューニングとh2oをパク参考に、net.*
系のカーネルパラメタを設定しておく。
##比較対象
nginx に Lua をバインドして、タイルサーバ用のスクリプトを入れたもの。具体的には、OSM Japan が公開している tileman から、コードと設定を借りてくる。
nginx 自体は、ngx_openresty (Lua バインドが最初から入ってる) の最新版 (1.7.10.1) をソースで落として、gcc -mtune=native -march=native -O3
でビルドする:
$ wget http://openresty.org/download/ngx_openresty-1.7.10.1.tar.gz
$ export CFLAGS="-O3 -mtune=native -march=native"
#sudo so that /sbin/ldconfig is visible
$ sudo ./configure --with-luajit --with-cc=gcc --with-cc-opt="$CFLAGS" --with-luajit-xcflags="$CFLAGS" --prefix=/usr --conf-path=/etc/nginx/nginx.conf
$ sudo make -j 4
$ sudo make install
##実験方法
リクエストするURL
あらかじめレンダリングが済んだ東京周辺の地図に対して、ズームレベル 0~17 までのタイル1 (約77万ファイル) をひたすらリクエストする:
タイルの URL (の、パス名部分) はこんな感じだ:
$ cat paths.txt
/tiles/0/0/0.png
/tiles/1/1/0.png
/tiles/2/3/1.png
/tiles/3/7/3.png
...
/tiles/17/116822/51974.png
/tiles/17/116823/51974.png
/tiles/17/116824/51974.png
$ wc -l paths.txt
770534 paths.txt
負荷のかけ方
wrk に以下のスクリプトを食わせて、リストファイルのパスを順番にフェッチさせていく:
-- traverse.lua
paths = io.lines("paths.txt")
request = function()
p = paths()
if (p == nil) then
paths = io.lines("paths.txt")
p = paths()
end
return wrk.format(nil, p)
end
$ wrk --latency -c 1000 -t 4 -d 10 -s traverse.lua http://localhost:8080/
なお、wrk を別ホストにするのもやってみたが、VLAN帯域を食いつぶしてマトモな測定にならなかったので、サーバと同じホストで動かすことにした。
タイルデータの形式
H2O でサーブするタイルデータは、各タイルが個別の .png ファイルになっている。つまり、今回の場合だと、約77万個の .png が特定のディレクトリの下に存在していることになる (もちろん、階層はフラットではないが)。
一方、nginx の実装は、「メタタイル」といって、8x8 = 64 枚の .png を tar のようにくっつけた形式のファイルを使う。
このくっつけたメタタイルの中から、「しかるべき .png 一枚分」を切り取ってクライアントに返すわけだ。
というわけで、H2O用の .png 77万枚に加えて、それと等価な nginx 用のメタタイルを用意した。
メタタイルの作り方は割愛するが、mod_tile という Apache モジュールに付属のオフラインレンダラーを使う。
なお、公平のため、上記2種類のデータセットは同じファイルシステムに置いた。
サーバソフトウェアの設定
各サーバソフトの設定は、以下のようにした。これも比較記事の設定を大いにパク参考にした。
H2O
tiles.conf
# to find out the configuration commands, run: h2o --help
num-threads: 8
#num-name-resolution-threads: 1
max-connections: 10240
mapnik-datasource: /usr/local/lib/mapnik/input
mapnik-fonts: /usr/local/lib/mapnik/fonts
mapnik-fonts: /usr/share/fonts
listen: 8080
hosts:
"127.0.0.1.xip.io:8080":
paths:
/tiles:
tile.dir: /opt/osm/tiles
tile.style: /opt/osm/openstreetmap-carto/osm.xml
expires: 1 day
/:
file.dir: /opt/osm/www
access-log: /dev/null
# access-log: /opt/osm/log/access_log
- スレッド数 = 8
- 最大コネクション = 10240
- アクセスログはとらない
- タイルは
/opt/osm/tiles
の下にあるよ
というのが大事で、あとはわりとどうでもいい。
nginx
/etc/nginx/nginx.conf
worker_processes 8;
events {
worker_connections 10240;
accept_mutex_delay 100ms;
}
http {
lua_package_path "/usr/local/lib/lua/?.lua";
include mime.types;
default_type application/octet-stream;
access_log off;
sendfile on;
open_file_cache max=100 inactive=20s;
tcp_nopush on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root /opt/osm/www;
index index.html;
}
location /tiles {
access_by_lua '
local osm_tile = require "osm.tile"
local tirex = require "osm.tirex"
local map = ""
local x, y, z = osm_tile.get_cordination(ngx.var.uri, "/tiles", "png")
-- DO NOT ask tirex to render it (tirex is not set-up properly)
--local priority = 1
--local ok = tirex.request(map, x, y, z, z, priority)
--if not ok then
-- return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
--end
local tilefile = osm_tile.xyz_to_metatile_filename(x, y, z)
local tilepath = "/opt/osm/ng_tiles/"..map.."/"..tilefile
local png, err = osm_tile.get_tile(tilepath, x, y, z)
if png then
ngx.header.content_type = "image/png"
ngx.print(png)
return ngx.OK
end
return ngx.exit(ngx.HTTP_NOT_FOUND)
';
root /opt/osm/tiles/;
}
}
}
スレッド数などは当然 H2O と合わせてある。
本題は access_by_lua
のところで、ざっくり言うと
- リクエストURLをパーズして
x, y, z
(座標とズームレベル) を求めよ - それを元に、しかるべきメタタイルの、しかるべき部分を切り出せ
- 切り出した画像を送り返せ
- メタタイルがなかったり、切り出せなかったら単に 404 を返せ。レンダラーに仕事を投げるとか余計なことは今回はしなくてもいい
ということをやっている。
本来は、メタタイルがなかったらバックエンドのレンダラーにお仕事を投げるのだが、今回、そのバックエンドを用意できなかったので該当箇所をコメントアウトした。
余計な仕事をコメントアウトしただけなので、これで nginx 不利になったりはしないハズだ。
#結果
$ wrk --latency -c 1000 -t 4 -d 10 -s ...
を、H2O と nginx それぞれに対して 10 回ずつ測定した。10回の中で最も結果が良かったものを下に載せる:
H2O
Running 10s test @ http://localhost:8080/
4 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.08ms 4.70ms 210.40ms 91.20%
Req/Sec 38.46k 12.43k 99.92k 77.72%
Latency Distribution
50% 2.98ms
75% 5.07ms
90% 8.32ms
99% 18.54ms
1535421 requests in 10.07s, 7.11GB read
Requests/sec: 152439.26
Transfer/sec: 722.99MB
152k req./秒 ぐらい。
nginx
Running 10s test @ http://localhost/
4 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 92.52ms 232.91ms 1.75s 91.27%
Req/Sec 11.69k 5.73k 28.45k 67.00%
Latency Distribution
50% 5.69ms
75% 48.56ms
90% 257.60ms
99% 1.17s
466463 requests in 10.03s, 3.20GB read
Socket errors: connect 0, read 0, write 0, timeout 271
Requests/sec: 46499.53
Transfer/sec: 327.15MB
46.5k req./秒 ぐらい。
#考察
というわけで、今回の測定では、H2O をいじったタイルサーバは、既存の nginx の実装を3倍ぐらい outperform しており、実際スゴイ。
ただし、この差は ソフトウェアの性能差ではなく、ただの .png とメタタイルという データ形式の違い が大きいと考えられる。
「考えられる」といいつつ実証とかしてないのだが・・・メタタイルを毎回 fseek()
して切り取って・・・っていかにも遅そうやん?
#まとめ
- メタタイルを使わないタイルサーバはすごく速くできる、可能性がある
- じゃあ、メタタイルっていらないんじゃ・・・
なぜ「メタタイル」という一見余計なレイヤーが標準的になったのか、は、機会があれば解説する。明日から頑張る。本当だよ。
-
今更だが、「タイル」というのは地図の一部分を表す画像データだ。大抵は 256x256 ピクセルの PNG を使う。この PNG をブラウザ側でがんばってずらっと縦横に並べることで、地図として見えるわけだ。 ↩