簡易歴なJob Queueシステムを設計するときに、どういう機能を用意すればいいのかメモ。
「Job Queueシステムを使いたいんだけど、今回使いたい言語用のライブラリには低機能のものしかない、追加機能を少しだけ自分で作る必要がある」というときに、どういう機能を最低限作れば、システムリソースの使い過ぎ等を避けられるかメモ。
Google App Engine(GAE)のTaskQueueが以前使ってて便利だったので、「それと同じ機能が最低限あれば、とりあえずOKでは?」みたいな方針で書いてます。用語は基本的にGAEに合わせますが、各システムによって様々だったりします。
この記事に書かれた内容が絶対とかそういうわけではないのでご注意ください。
RateとBucket Size
トークンバケットアルゴリズムでタスクを実行していく場合、RateとBucket Sizeの値を設定することで、結果的に、単位時間あたりに実行するタスクの数を設定することができます。
具体的な数字は完全にシステム構成に依存しますが、Rate 10個/秒、Bucket Size 40、とかそれくらいの感覚になります。すぐ終わるタスクなら数百個/秒でも全然おかしくないです。
- Rate(レート:単位時間あたりに補充するトークンの数)
- Bucket Size(バケットサイズ:トークンをためておくバケットのサイズ)
いきなり、トークンとバケットという専門用語がでてきますが、難しい話ではないです。詳しい説明は下記の2つの資料を読んでみてください。
GAE Task Queue Configuration(この資料だけで充分詳しいです)
Token Bucket(英語版Wikipedia)
英語の資料だけだと分かりづらいと思うので、GAEのドキュメントからToken Bucketに関する部分を抜粋し意訳しました。
キューにタスクが入っていて、かつ、トークンがまだバケットに残っているなら、App Engineはそのトークンを可能な限り多く消費し、消費したトークンと同じ数のタスクを実行します。
- Configuring the Processing Rateから一部抜粋してむっちゃ意訳
トークンの数だけタスクを実行できます、トークンは(バケツに水を注ぐように)自動で補充されます、水を注ぐ量がRateで、バケツのサイズがBucket Sizeです。
- Configuring the Processing Rateから一部抜粋してむっちゃ意訳
再度になりますが、つまり、RateとBucket Sizeを設定することで、「単位時間あたりに何個のタスクを処理するのか」を決定することができる、ということです。
ただ、この設定だけだと、同時実行タスク数が急激に増える「バースト」に対応できないため、さらに、続いて説明する設定値が必要になります。
Max Concurrent Request
(上記のRateとBucket Sizeだけだと、)同時に実行するタスク数が急激に増える(バースト)ことがあります。そうなると、システムリソースを消費し過ぎることになり、ユーザーからのリクエストへの応答が遅くなってしまいます。
- Configuring the Processing Rateから一部抜粋して意訳
ここでいうシステムリソースとは、CPU時間やメモリ使用量のことです。CPU時間がとられすぎると、同じサーバーで動く他のプログラムの応答が極端に遅くなります。また、メモリを使いすぎると、プロセスごとkillされたりします。そういう事態を避ける必要がありますよ、というわけです。
一つのタスクあたりにかかる時間がごくごく短いなら、バーストは起きません。毎秒10個のタスクを実行して、毎秒10個のタスクが終了するなら問題ないわけです。
しかし、一つのタスクに長い時間(例として10秒)かかったりすると、「毎秒10個のタスクが実行される。そのタスクが終了するよりも早く、次の10個が実行されてしまう、さらに次の10個が・・・」みたいな状況=バーストが起きてしまうわけです。
上記の問題を防ぐためには、「同時に実行できる最大タスク数」を決める必要があります。それがMax Concurrent Requestです。
例えばこの値を10に設定することで、「毎秒10個のタスクを実行します、次のタスク実行のタイミングがきました、けど、まだ以前の10個が終わっていないので、次のタスクを実行しません」という具合にバーストを回避できます。
具体的な数値はこれまたシステム構成に完全に依存するんですが、タスクの実行単位がスレッドなのか、プロセスなのか、システムリソースにどれくらい余裕があるのか、等によって適切な値を決定しましょう。
今までは、タスクの実行回数を適切な数に抑える設定でした。次は、タスクが失敗した場合に適切にリトライする設定を説明します。
タスク失敗時の自動リトライの設定
タスクの自動リトライに必要な項目は下記の通りです。
- 最大リトライ回数
- 次のリトライまでの時間間隔
- タスクの寿命
それぞれ説明していきます。
最大リトライ回数
タスクのリトライを繰り返す最大の数です。これが設定されていないと、失敗し続けるタスクが無限に残ってしまうことになります。
次のリトライまでの時間間隔
タスクをリトライする際の待ち時間です。どんどん大きくなるようにします。例えば、1回目の失敗は10秒待つ、2回目は20秒待つ、3回目は40秒待つ、・・・、という具合です。この値を定数にしてしまうと、複数のタスクが続けて失敗した場合にリトライタスクばかりが実行されて正常なタスクの実行が妨害されてしまいます。
タスクの寿命
タスクが残り続ける時間のことです。これがないと、何らかの原因でタスクの消化が遅くなったとき、古いタスクがどんどん増え続けてしまいます。
まとめ
これまでの説明から、Job Queueシステムにはおおよそ下記のような設定項目が最低限あれば、システムリソースを使い尽くしてしまうような事態は避けられる、はずです。
- Rate
- Bucket Size
- Max Concurrent Request
- 最大リトライ回数
- 次のリトライまでの時間間隔
- タスクの寿命
これが正解というわけではなく、あくまでも一例である点にご注意ください。また、各項目には特に固定の名前があるわけではありません。
ざっくり用語説明
Job Queueシステムやキューに関するちょっとした説明です。
Job Queueシステム
長時間かかる処理をバックグラウンドで効率よく実行する仕組みのことです。キューへの追加、取り出し、エラー時の自動リトライ、モニタリング、などの便利機能を一緒に持っていることが多いです。
キュー(Queue)
待ち行列と呼ばれるデータ構造のことです。ラーメン屋の行列もキューの一種です。先に入ったものが先に出ていく(FIFO - First In First Out)構造になっています。
ジョブ、タスク(Job、Task)
キューに入れる要素のことです。ラーメン屋の行列での「人」です。
エンキュー(enqueue)
キューに要素を追加することです。
デキュー(dequeue)
キューから要素を出すことです。