はじめに
Python でキューを利用したバックグラウンド処理を実現可能なライブラリの1つとして rq がある。
メッセージキューとして Redis を利用したライブラリであり、任意の数のワーカープロセスを立ち上げることでバックグラウンド処理が実行できるようになる。 Rabbit MQ や Kafka などのメッセージキューイング用の特別なミドルウェアを用いなくても、小規模なバックグラウンド処理用のシステムを構築できる。
これを本番で使ってみて、安全な更新を行うにはどうすればよいのかを調査した結果、一癖あったのでその調査結果と手順を書き記す。
問題点
アプリケーション(ワーカー)を更新する場合、全てのワーカーが動作していない状態で更新しないと、ジョブが処理途中のままになってしまう。 そのため、更新時には先だってフラグを立て、各ワーカーがこれ以上新しいジョブを実行しないようにしておき、全てのタスクが完了した状態で更新を行いたい。
この問題を解消するために suspend モードがある。 rq で提供されているcliで、rq suspend
として実行することで、各ワーカーがこれ以上新しいジョブを一時的に受け取らないようにできる。 もちろん、以下の実行例でも記されているように、現在ワーカーで実行中のジョブは停止しない。
$ rq suspend
Suspending workers. No new jobs will be started. But current jobs will be completed
この挙動から、利用者が考えるのは「suspend 状態にしたから、新しいジョブを受け取らないだろう」ということなのだが、実はこのような直感的な挙動になっていない。
idle状態で、何のジョブも実行していないワーカーは新しくジョブを受け入れる状態を続けている。 そして、1つでもジョブを完了させると suspend 状態となり、新しくジョブを受け入れない状態となる。
この問題は以下の Issue にまとめられている。
解決策
上記Issueで対処法がいくつか書いてあるが、更新時に何もしないジョブ(番兵)を適当な個数実行させるのが簡単である。 bash での判断例は以下の通り。
# idle 状態のワーカーがいるかどうかを確認して、件数分番兵ジョブを登録する
$ rq info | grep idle | wc -l
# 全てのワーカーが suspend 状態であればワーカーを再起動して更新を反映
# なお suspend 状態で起動したワーカーは最初から状態が suspend になっている
$ rq info | grep suspend | wc -l
# 再起動後は suspend を解除
$ rq resume