shoryukenの諸動作

Railsの非同期処理として、今までDelayedJobで運用していたものを、shoryukenに載せ替えたので、使い勝手をまとめます。 最初はsidekiqにしようと思っていたので、ちょいちょいsidekiqとの比較が出てきます。

メッセージのポーリング

メッセージのポーリング間隔はshoryuken.ymlで設定します。ちなみに、ここのポーリング間隔と、エンキューされるキューの合計数でSQSの料金が変わってくるみたい。ただ、SQSの料金プランによると100万件ごとに課金されるので、そこまでシビアに考える必要はなさそうだけれど。

:delay: 25  # 単位は秒

以下のようにしておくと、リクエストを投げ続けてくれる。

:delay: 0

ちなみに、このdelay値は、エンキューされてから、処理が開始されるまでの時間にモロに響いてくるので、もし「即時送らねば!」という処理を積みたい場合は、金額を試算した上で短くすることをおすすめします。

リトライ

リトライ機構

sidekiqではsidekiq.ymlに

:retry: 25

と書いておけば、リトライ回数を設定できたが、shoryukenではそういうわけにはいかない。

重要となるのは、SQSのVisibility Timeoutです。 SQSに蓄積されたメッセージは、蓄積直後、どのshoryukenワーカーからも見える状態になっています。 そして、1つのワーカーがメッセージを受信した時点で、そのメッセージは見えなくなります(処理中という扱い)。 しかし、処理が一定時間終わらなければ、SQS側では「なにか失敗したかな?」ということで、メッセージを再びどのワーカーからも見える状態に戻します。この処理中扱いにしてくれる時間が、Visibility Timeoutです。

処理の成功・失敗にかかわらず、Visibility Timeoutが来てしまえば、再びメッセージが受信可能な状態になり、いずれかのワーカーに処理される可能性があります。

成功時

処理の成功・失敗にかかわらず、Visibility Timeoutが来ればメッセージが受信可能状態になる、と言いましたが、shoryukenにはauto_delete というオプションがあり、これが成功時にメッセージを削除してくれます。 なので、auto_deleteを有効にしておけば、成功したメッセージが再び処理されることはなくなります。

class SampleWorker
  include Shoryuken:Worker
  shoryuken_options queue: "default", auto_delete: true

  def perform(sqs_msg, body)
    # something
  end
end

auto_delete: false はどんなときに使うのか、イマイチ想像できないですね……。

リトライ回数指定

というわけで、失敗時には、メッセージが削除されず、Visibility Timeoutが来た後再びどこかのワーカーが受信し、処理します。 では、このリトライ回数に上限は指定できるのでしょうか?

これは、shoryuken側の設定ではなく、SQS側で設定します。 SQSにはDead Letter Queueというものが設定できます。これは、「n回受信(ワーカーがメッセージを取得)したらDead Letter Queueとして指定された別のキューにメッセージを移す」というものです。 Dead Letter Queueに入ってしまえば、キューが変わるので、shoryukenのワーカーからはまったく見えなくなります。

なので、リトライ回数の指定は、Dead Letter Queueの受信回数条件で設定することが可能です。

:queues:
  - asumibot-patient-queue

みたいなキュー設定をしたら、AWS SQSにDead Letter Queueを作ります。 新しく、asumibot-dead-letter-queue というキューを作り、asumibot-patient-queue の設定画面で、

f:id:h3poteto:20200327214530p:plain

こんな設定をしてやれば、20回目の受信で、Dead Letter Queueに移してくれて、それ以降リトライされなくなります。

リトライしたくない場合は、ここのMaximum Receives を1にしておけば良さそう。

リトライ間隔

リトライ間隔は以下のように、ワーカーごとに設定できます。

class CopyCheckWorker
  include Shoryuken::Worker
  shoryuken_options queue: "default", auto_delete: true, retry_intervals: [60, 120, 180] # 単位は秒

この状態だと、1回目のリトライが60秒後、2回目が120秒後となります。 ただし、retry_intervals で設定した値を超えた回数リトライが発生した場合は、通常通りVisibility Timeoutに依存したリトライとなります。 また、ここで指定するのはあくまで間隔だけで、リトライ上限回数はDead Letter Queueでしか設定できませんでした。

※ただし、retry_intervals はこの時間後にメッセージが見えるようになるだけで、厳密にはポーリング間隔との兼ね合いがあり、ポーリング間隔が長い場合は即時受信してくれるわけではない。

Visibility Timeoutの変更

Visibility Timeoutがかなり重要なことはご理解いただけたと思います。 このVisibility Timeout、デフォルトでは30秒となっています。

もし、ジョブの実行時間が30秒をオーバーしそうな場合には、Visibility Timeoutを延ばす必要がでてきます。

class SampleWorker
  include Shoryuken::Worker
  shoryuken_options queue: "default", auto_delete: true, retry_intervals: [60, 120, 180]

  def perfom(sqs_msg, body)
    sqs_msg.visibility_timeout = 60
    # something
  end
end

スレッド

shoryukenのスレッドは、shoryuken.ymlのconcurrency で設定します。

:concurrency: 25

キューごとのスレッド数を指定したい場合は、

:concurrency: 25
:queues:
  - [ default, 4 ]
  - [ asumiss, 10 ]

と書いておき、合計値がconcurrency を超えないようにしておきます。 この辺はsidekiqと同じですね。

番外編:fake_sqs

ローカルで以上のようなshoryukenの動作を実現したい場合、AWS SQSではなくfake_sqs を使うという手があります(安いとはいえAWS SQSではお金かかりますからね)。 http://qiita.com/iemon7stars/items/d4efdd8872d287906d29

fake_sqsの場合、ほとんどAWS SQSと同等の動作をしてくれますが、2点重要なことがあります。

  1. メモリ上にキューが作られるので、fake_sqsを起動するたびにキューを作ってやる必要がある
  2. retry_intervalsの設定が効かない

1は、まぁ仕組み上仕方ないでしょう。 http://qiita.com/iemon7stars/items/d4efdd8872d287906d29 こちらの記事通り、どこかでキューを作ってやるしかないです。

2については、shoryukenのmiddlewareで失敗時に、sqs_message.attributes['ApproximateReceiveCount'] からattempts (失敗回数)を導き出して失敗後の処理をしていますが、fake_sqsを見る所attributesが空になっているのが原因です。 おそらく、fake_sqsではメッセージのattributesに対応していないので、ここはどうにもならないですね……。