Resque(perl ver)を触っていた
message queueとして長らくQudoのお世話になっていたのだが、もろもろきっかけがありjobの仕組みを考え直すことになった
Qudoを使っていて感じたペインポイントをまとめてみる
Qudoの運用でぱっと思い出せるペインポイント
雑な前提として、自分がここ一年ほど運用していたプロジェクトではかなり多量のjobを投げていた
- enqueueやdequeueのクエリが遅い場合がある
- slow queryにがんがん流れてきていた
- qudoのmysqlをアプリのデータを持つmysqlから独立させていなかったのだが、qudoのクエリでサーバ負荷がかなり高まっていた
- qudoのmysqlを別サーバに分離させたところ劇的にサーバ負荷が改善された
- mysqlのスケールアウトが割りと面倒
だいたいmysqlで運用しなければならないことに起因していて苦労を背負っていたようなきがする。オンプレだったので余計に、ということもあるけど、AWSなどでもEC2を増やすのに比べてRDSのインスタンスを増やすのは比較的ことが大きのではないかと思う。なるべくbackendの部分は台数増やさなくてもスケールしてほしいし、アプリ側からはチューニングの余地がほぼないのでenqueueとdequeueは高速に動いてほしい
そんなわけで、SaaSに寄せないパターンを採択するならRedisを使う系のものを試してみたいなあと考えていたのと、preforkするWorkerの実装が比較的楽そうなもの、ということでResqueに行き着いた。RubyならばSidkiqなどの選択肢もありそうだが、PerlのプロジェクトだったのでPerl実装のあるResqueをとりあえず試している
github.com
基本的な使い方はSYNOPSISとかadvent calendarにあるので省く
起動スクリプトの周りの話
systemdから起動スクリプト叩いて、そこからpreforkしたworkerを立ち上げて使いたい。まじめにやるとシグナルの取り回しなどそこそこ面倒くさい*1*2のだけど、Resque::Workerに最初からそのあたりのことが実装されているので、preforkする側ではシグナルをtrapするだけで済んでくれている。実装読みつつ書いた結果、だいたい以下のような起動スクリプトで行けそう
#!/usr/bin/env perl use strict; use warnings; use utf8; use Resque; use Parallel::Prefork; my $pm = Parallel::Prefork->new(+{ max_workers => 3, trap_signals => +{ TERM => 'TERM', INT => 'INT', QUIT => 'QUIT', HUP => 'QUIT', USR1 => 'USR1', USR2 => 'USR2', CONT => 'CONT', }, }); $pm->start(sub { my $resque = Resque->new(redis => 'localhost'); my $worker = $resque->worker; $worker->add_queue('hoge-queue'); $worker->work; }); $pm->wait_all_children;
Resqueのシグナルのハンドリングはこのあたり
ResqueのWorkerは実際にjobを実行するとき、自身で行うのではなく、一度forkして子プロセスに行わせる仕様になっている。TERMやINITを受け取ると、実際に処理を行うプロセスを容赦なくSIGKILLして自身も終了する。QUITの場合は子プロセスを無理やり殺すことはない。restartの際はsystemdからHUPを送りつけたいので、QUITに直して渡してやる
前述の通り、forkしてから使い捨ての子プロセスで実際の処理が行われるので、max_reqs_per_child的なことは考える必要がなさそう
足りなさそうなこと
retryやjobのtimeoutなどがほしければ、jobの実装の方で別途用意する必要がありそう