本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはnekokakさんこと小林篤さんで、テーマは「ジョブキューで後回し大作戦」です。
ジョブキューとは
一時代前は時間のかかる処理もすべてWebアプリケーションで行っていましたが、最近ではいろいろな部分で処理の非同期化が行われるようになってきました。たとえばWebのインタフェース側ではAjaxがその最たるものでしょう。アプリケーションのバックグラウンド側でも今回のテーマであるジョブキューと呼ばれるしくみが多く利用されるようになりました。ジョブキューを賢く上手に利用することで、ユーザにストレスを与えることなく、またサーバのリソースも有効に使えるようになります。
ジョブキューは延々と動き続けるバッチ処理、というイメージが最もわかりやすいでしょう。通常のバッチ処理であればcrondを利用し、一定周期でプログラムを起動して処理を実行することになります。ジョブキューはcrondなどを利用せずプロセスを起動させ続け、処理すべきデータが渡ってくるのを待ち構えます。
現在のアプリケーション開発でジョブキューは必須のアーキテクチャと言えます。そこで本稿では、Perl界隈でよく利用されるジョブキューのしくみを中心に説明していきます。
なぜジョブキューを使うのか
ジョブキューを使う主な理由は、Webアプリケーションで処理するには重く、リアルタイム性が必要ない処理をWebのサイクルから切り離して処理を行うためです。Webアプリケーションとは物理的にサーバを分けることも可能となるので、サービスが大きくなったときなどにスケールアウトさせやすくなります。
実際の使いどころ
たとえば会員登録した際、ユーザに登録完了メールを送信することは一般的でしょう。しかしメール送信処理はリアルタイム性が必要のないものです。というのもメール送信に使われるSMTP(Simple Mail Transfer Protocol )自体がリアルタイム性を担保しておらず、Webで頑張って処理を行っても結局ユーザにはリアルタイムでメールが届くわけではないからです。そもそものプロトコルがリアルタイム性を持たないのであれば、Webアプリケーションでは登録完了メールを送ることをジョブキューに登録し、実際にメールが送信されたことを確認する前にユーザにすぐに登録完了画面を見せてあげるほうが親切な設計でしょう。そしてジョブキューがメール送信処理をWebのサイクルと切り離して行います。
また、ユーザがアップロードした画像に対して何か処理を行う必要があるとします。画像処理を行うライブラリは一般的にメモリを多く使用し動作も低速です。そのため、Webアプリケーション側で処理を行うには無駄が多くなります。ジョブキューで画像処理を行うようにしてしまえば、Webアプリケーションを省メモリかつ高速に動作させることができます。
COLUMN ジョブキューとメッセージキューの違い
厳格にはレスポンスを期待するのがジョブキュー、レスポンスを必要としないのがメッセージキューです。「 実際の使いどころ」の項で紹介した例は、Webアプリケーション側ではキューの処理結果を受け取らずに処理を終了しているので、厳格な意味ではメッセージキューです。
ただ、ジョブキューとして作られているいくつものミドルウェアでは、レスポンスをクライアント側に返さなくてもジョブキューと名乗っています。通常は「ジョブキュー」=「メッセージキュー」と考えてよいでしょう。しょせん、キューとはデータをFIFO(First In,First Out)( 注a )で取り扱うしくみにすぎません。
GearmanとTheSchwartz
Perlで作られた有名なジョブキューライブラリとしては、Gearman とTheSchwartz があります。両方ともmemcachedの作者Brad Fitzpatrick氏によって書かれたプロダクトです。Perl界隈ではおそらくこの2つが一番使われています。それぞれの特徴を簡単に説明します。
Gearman
Gearmanはgearmandという専用デーモンを立ち上げて利用します。アプリケーションからはgearmandに対してジョブを登録します。gearmandは受け取ったジョブ情報をオンメモリで確保しつつ、登録されているワーカに処理をディスパッチします。ジョブ情報がオンメモリで持たれているため、gearmandを再起動してしまうとジョブが失われます(図1 ) 。
図1 Gearmanの処理構成
TheSchwartz
TheSchwartzはGearmanのような専用デーモンを必要とせず、MySQLなどのRDBMSを利用します。アプリケーションからはTheSchwartz用に定義されたスキーマに対してジョブをINSERTします。登録されたジョブはユーザが作成したワーカプログラムによって取り出され処理されます。Gearmanと大きく異なる点は、ワーカプロセスを再起動しても処理すべきデータはデータベースに保存されているので、ジョブが失われないことです(図2 ) 。
図2 TheSchwartzの処理構成
どのように使い分けるか
では、GearmanとTheSchwartzはどのように使い分けるべきなのでしょうか。一般的にはGearmanはジョブ情報がロストしても問題なく、ある程度のリアルタイム性が必要な場合に利用され、TheSchwartzはジョブ情報のロストを守り、ある程度の遅延が許容されるときに利用されます。
たとえばメール送信処理であれば、ジョブがロストされることは許されませんが、送信の遅延はある程度許されるので、TheSchwartz を利用します。逆にGearmanの使いどころの一例はページングの処理などです。ユーザからのアクセスを受け、次のページを先読みしてキャッシュデータを作っておき、ユーザの次のアクセスに備える場合などに使います。先読みでデータを作成する場合、完全なリアルタイム性は必要ありませんが、ユーザの次のアクセスまでにはキャッシュデータができていることが望ましいです。
TheSchwartzの使い方
紙幅の都合で今回はTheSchwartz的なジョブキューの使い方のみを取り上げます。
TheSchwartzを使うにはデータベースが必要です。最新のバージョンではMySQL、SQLite、PostgreSQLに対応しています。ここではMySQLを使った方法を紹介します。
TheSchwartzのディストリビューションにschema.sqlというファイルが同梱されているので、このsqlファイルをお使いのMySQLに流しこんでください。TheSchwartz自体はCPANからインストールできます。
アプリケーションからジョブを登録するには、TheSchwartzのinsertメソッドを利用します。
#! /usr/bin/perl
use strict;
use warnings;
use TheSchwartz;
my $client = TheSchwartz->new(
databases => $connect_info
);
$client->insert('MyWorker', \%args);
登録したジョブを処理するワーカは次のようになります。
package MyWorker;
use strict;
use warnings;
use parent 'TheSchwartz::Worker';
sub work {
my ($self, $job) = @_;
my $arg = $job->arg;
# ここでワーカにジョブを処理させる
$job->completed();
}
ワーカクラスはTheSchwartz::Workerクラスを継承したクラスを作成し、workメソッドをオーバーライドして実際に処理したい内容を記述します。workメソッドの第2引数にTheSchwartz::Jobのインスタンスオブジェクトが渡ってきます。TheSchwartz::Jobのargsメソッドを呼び出すことで登録してあるジョブの情報を取り出せます。そして、workメソッド内で行いたい処理が正常に終了したら、TheSchwartz::Jobインスタンスのcompletedメソッドを実行し、ジョブが正常に終了したことを記録します。
上記で作成したワーカを使って実際にジョブを処理させるには、次のようにします。
#! /usr/bin/perl
use strict;
use warnings;
use TheSchwartz;
my $client = TheSchwartz->new(
databases => $connect_info
);
$client->can_do('MyWorker');
$client->work;
TheSchwartzクラスのworkメソッドを実行することで処理が無限ループに入り、クライアント側からジョブが登録されるのを待ちます。ジョブが登録されたら対応するワーカクラスに処理を振り分けます。
Webアプリケーションなどのクライアント側で行うべき処理は、単純にデータベースにジョブをinsertするだけです。
ただ、TheSchwartz自体はクライアント側の処理とワーカ側の処理を行える作りになっているため、ジョブをinsertするだけにしては若干重い作りになってしまっています。そもそも重い処理を非同期に行うしくみなのにジョブの登録で時間がかかっては本末転倒ということで、宮川達彦氏によってTheSchwartz::Simple というライブラリが作成されました。TheSchwartz::Simpleを使うと、TheSchwartzをそのまま使うよりも高速にジョブをinsertできるので、より早くユーザにレスポンスを返せます。TheSchwartz::Simpleのインタフェース自体はTheSchwartzと大きく変わらないので、今までTheSchwartzだけ使っていた場合も簡単に置き換えることができます。