金曜日の夕方ぐらいから全身倦怠感と頭痛がはじまり、微熱(37度台)に。熱は土曜日中ぐらいで下がったが頭痛は治らず、今(月曜日)でもまだ痛い。
Google Search Console のページのダウンロード時間
sitemap.xml を送信した翌日ぐらいに Googlebot からの連続したアクセスがくるようになる。Googlebot は HTTP/1.1 で Keep-Alive が有効なため、普段にパラパラくるアクセスよりも、このときのように連続してアクセスされる場合、多くの場合でコネクション時間がなくなるために高速にレスポンスを返せる。
連続したアクセスの場合、2分ぐらい Keep-Alive している。
これは結果として Search Console の「クロールの統計情報」に表示されている「ページのダウンロード時間 」のグラフにも反映されているような感じで、sitemap.xml を追加した次の日あたりで平均ダウンロード時間がかなり少なく表示される。
Server::Starter を node.js のサーバ起動に使う
Server::Starter は hot deploy 用の汎用スーパーデーモンで、Perl で書かれています。h2o の起動にも使われているのでみなさんおなじみでしょう!
Server::Starter がやってることは、Server::Starter 側で listen したソケットの fd を環境変数につっこんで子プロセスを起動というものです。子プロセス側では渡ってきた環境変数を読んで、fd について accept すれば良いことになります。
これを node.js でやるには以下のようにすれば良いようです。
//#!/usr/bin/env node
"use strict";
const http = require('http');
const server_starter_port = process.env['SERVER_STARTER_PORT'];
if (!server_starter_port) {
console.log('SERVER_STARTER_PORT is not set');
process.exit(1);
}
const fds = server_starter_port.split(/;/).map( (i) => i.split(/=/) );
const server = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
process.on('SIGTERM', () => {
console.log('server exiting');
server.close();
});
for (let fd of fds) {
console.log('listen', fd);
server.listen({ fd: +fd[1] });
}
起動はたとえば以下のように
start_server --port=5001 -- node server.js
http.Server の listen() のドキュメントには fd を渡せるバージョンが書いてありませんが、net.Server の listen() がサポートしているので、同様に渡せるようです。
これだけで node.js のプロセスの hot deploy が簡単にできます。
余談:cluster
node.js には cluster というのがあります。これは node.js を複数プロセスで動かすための仕組みなんですが、これの woker プロセス側の listen() は master プロセスと ipc してうまいことやるみたいな感じのようです。
複数の psgi を1つのサーバでサービスするときにメモリをケチる
このサーバはVPS 1台で動いていて、メモリは1GBしかありません。常時メモリ上限まで使いきっており、スワップファイルもそこそこあります。そういうわけで、できるだけメモリ消費をケチりたいのです。
稼働率の低い複数のサービスを1つのプロセスにまとめる
このサーバ上で動いているサービスがいくつかあるのですが、実際のところどれも殆どアクセスはありません。一番アクセスされていてこの日記システムぐらいです。
それらのサービスをそれぞれ別プロセスで起動させておくと、たいへん無駄なので、できるだけプロセス自体を同居させています。
psgi の vhost 化
Plack::Builder が提供している mount() を使うと、vhost を実現できます。
builder {
mount 'http://lowreal.net' => do {
my $guard = cwd_guard("lowreal.net/");
Plack::Util::load_psgi("script/app.psgi")
};
mount 'http://env.lowreal.net' => do {
my $guard = cwd_guard("env.lowreal.net/");
Plack::Util::load_psgi("script/app.psgi")
};
};
このようにすると、Host ヘッダに応じて、ディスパッチするアプリケーションを変えることができます。
これを利用して、複数の psgi アプリケーションを1つの psgi アプリケーションにまとめて起動しています。
ただし、起動時 (load_psgi時) にだけ cwd を設定しているため、cwd 依存のコードがあるとうまく動きません。
単にプロセス数も減らせますが、それぞれのアプリケーションで共通で使うモジュールがかなり多いので、それらを fork 前にロードして共有しておくことで、プライベートメモリ使用量を削減しようという意図もあります。
メモリリークや Copy on Write が発生したプロセスを捨てる
上記のまとめた psgi アプリケーションを start_server と plackup 経由で起動しています。
start_server 用には以下のような環境変数を設定しています
export KILL_OLD_DELAY=15
export ENABLE_AUTO_RESTART=1
export AUTO_RESTART_INTERVAL=3600
exec setuidgid cho45 \
$PERL/bin/start_server --path=/tmp/backend.sock --port=5001 -- $PERL/bin/plackup -p 5001 -s Starlet\
--max-workers=5 \
--max-reqs-per-child=500 \
-a backend.psgi
ENABLE_AUTO_RESTART によって、時間経過で自動的にプロセス全体を再起動しています。再起動間隔は AUTO_RESTART_INTERVAL に決まり、この場合1時間ごとになっています。
KILL_OLD_DELAY には新プロセスが起動して、リクエストが受けつけられるようになるまでにかかる時間を余裕をもって指定します。これを指定しないと、プロセス再起動がかかるたびに、モジュールなどのロードが終わるまでの数秒アクセス不能時間ができるので、特に ENABLE_AUTO_RESTART する場合は必須そうです。
これで、もしメモリリークしていても1時間以内に綺麗になることが保証されるとともに、fork した worker プロセスが働いて Copy on Write が起こってプライベートメモリ量が増えたとしても、1時間以内に fork しなおしになるため、共有メモリ量が一定より高い状態を保つことができます。
余談:プライベートメモリ使用量を観測する
Linux のプロセスが Copy on Write で共有しているメモリのサイズを調べる - naoyaのはてなダイアリー にある shared_memory_size.pl が便利でした。共有メモリの割合を表示してくれるのでわかりやすいです。
Starlet のプロセス名をわかりやすくしたかったのも、このスクリプト実行時に指定しやすくするためでした。
こんな感じで使っています。
shared-memory-size.pl `pgrep -f /srv/www/backend.psgi` PID RSS SHARED 29583 55460 47792 (86%) 29584 55760 44688 (80%) 29585 65224 41396 (63%) 29586 56280 44704 (79%) 29587 55352 44660 (80%) 29588 58908 46748 (79%)
起動してすこし経ったあとの状態です。起動直後は90%以上ですが、プロセスが動くごとに少しずつ共有割合が減っていきます。自分の環境で、観測したうちだと60%ぐらいまで下がるようです。
ps -c で見るメモリ使用量には共有分が考慮されていないので、実際の物理メモリ使用量よりもかなり大きく出ます。仮に1プロセスあたり70MBぐらいメモリを消費していても、うち40MBぐらいが共有であることを考えると、worker プロセス1つあたり30MB、5プロセスで150MB程度の物理メモリ消費量になります。
このエントリを参照するエントリ
Applebot
アクセスログを見ていたら Applebot なるものからのアクセスがあった。
https://support.apple.com/ja-jp/HT204683
ほんとに Apple のウェブクローラらしい。ただ
robots の制御指示で Applebot には言及していなくても Googlebot について指定されている場合、Apple のロボットは Googlebot に対する指示に従います。
と書いてあって、それはどうなんだ、という感じがする。
crawler/1.0 という User-Agent
UAからしてなんか怪しいが、210.160.8.236 からくる BOT で、DNSを逆引きすると target.microad.jp. らしい。マイクロアドを使ってるつもりはないんだけど、Adsense 貼るとくるのかな?
まともなクローラのつもりなら、UA をもうちょっとわかりやすくしといてほしいですね。まともな会社なら自社クローラのUAと目的ぐらい書いとくべきだと思います。