Puma と Sequel

最近仕事でまた Ruby を使い始めて、簡単な Web API を Grape + Sequel で作ったりした。

SQLで書きたいクエリが分かってる場合は、Sequel の方が ActiveRecord よりも簡単に使えて良い。 レイヤー的には Arel と同じような感じなのかな。Arel 使ったことがないからわからないけど。

Webサーバーとして実環境で WEBrick を使うのはなんとなくイマイチな気がするんで、Rails で標準で使われてるらしい Puma にした。

前の職場では Passenger を使ってたんだけど、Apache のモジュール化するのも面倒だったし(最近はスタンドアローンでも使えるみたいだけど)、Passener はなんか大げさな気がするんで避けた。 (最近はどうかしらないけど、昔は勝手にインターネットからモジュールをダウンロードしようとしたりすることがあったんで、それも気に入らなかった)

ソケットファイルを経由して pumactl というコマンドで状態を得たり再起動したりできる。便利。

マルチプロセスモード(クラスタモード?)で使用していると、プロセスを順番に再起動してくれるんで、クライアントがリクエスト不可になるタイミングが無い。便利。

設定は config/puma.rb というファイルを作ればいいらしい。 設定ファイルに関するドキュメントは何故か見つからないのでソースを参照。 こんな感じで設定。

# 何に影響するのかわからないけど縁起物なので production に設定
environment 'production'

# 待ち受けIPアドレスとポート番号
bind 'tcp://0.0.0.0:8080'

# デーモン化
daemonize true

# PIDファイルの場所
pidfile '/path/to/puma.pid'

# 標準出力と標準エラー出力のパス。既存ファイルに追記する。
# これを設定しとかないと daemonize true 時に標準出力とエラー出力が失われる。
stdout_redirect '/path/to/stdout', '/path/to/stderr', true

# 子プロセス数
workers 3

# プロセスあたりのスレッド数の最小値と最大値
threads 0, 4

# pumactl で制御するためのソケットファイル。トークン無しでアクセス可。
activate_control_app 'unix:///path/to/puma.sock', no_token: true

スレッド数はいくつでもいいと思うんだけど、とりあえず Sequel の max_connections のデフォルト値の 4 に合わせておいた。

Sequel はDBに対して複数の接続をしておいて、プロセス内のスレッドがクエリを発行する時に、空いてる接続を見つけて使ってくれる。空いてるスレッドが無かったら5秒間(pool_timeoutのデフォルト値)空くのを待つんだけど、それでも空かなかったらエラーになってしまうので、いちおう合わせておいた方がいいかなーと。

ところで、MySQLはデフォルトでは、接続しっぱなしでクエリを何も発行しないまま8時間経つと、サーバーから接続を切ってしまう(MySQLのwait_timeoutのデフォルトの28800秒)。 なので、あんまりアクセスが無いサービスだと、いざクエリを発行しようとしたときに、切断されちゃっててエラーになってしまう。

Puma で、アクセスなければワーカー子プロセスを殺す…みたいな設定がないかと思ったけど無さそうだった。8時間毎に pumactl restart するのもなんかアレだし。

以前は Sequel でクエリを発行する前に select 1 みたいなダミーのクエリを発行してエラーになったら再接続というような小細工をしてたんだけど、Sequel に同じようなことをする機能があったのを発見した。

https://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/connection_validator_rb.html

DB.extension :connection_validator

これを設定しておくと、前回その接続でクエリを発行してから1時間以上経過していた場合は、SELECT NULLを発行して、エラーになったら再接続してくれる。便利!

2012年からあったのか、知らなかったなー。