アトミックな操作をしたいときのロック状態の作り方
Crontabで実行しているバッチで重複した実行をしてほしくないときにロック状態を作ったりしますが、どうやるのがいいのかの選択肢を整理。
令和の時代と思えない話だけど今も必要な話だった。
おおむね、サイボウズのsatさんの書いていることを確認した感じになります。 https://zenn.dev/satoru_takeuchi/articles/e0636407a0040c
選択肢は以下。
おすすめは一番最後のファイルロック。
汎用性のある話なので、Linuxのコマンドで例を書いています。高等言語でもたいていラッパーがあるし。
ロックファイルを作る
if test -e /var/lock/foobar then exit fi touch /var/lock/foobar # do something rm /var/lock/foobar
最初でファイルの有無を確認し、なければ作成して続行。処理の最後で削除する。プリミティブ。
単純だけどあんまりおすすめはしない。
- Pros
- 単純
- Cons
- 確認と作成に時間差があるので、その隙間で重複実行されることがあるかもしれない。crontabからの実行とかなら問題となりにくいけど
- プロセスが途中で死ぬとロックファイルが残ってしまう
- ロックファイル名が衝突する可能性がある
シンボリックリンクを作る
ln -s $0 /var/lock/foobaz # do something unlink /var/lock/foobaz
ln -s
は既に存在していたらexitcode 1を返す。なので、存在確認と作成を同時にできるので、時間差がない。
- Pros
- 時間差がないので、隙間で重複実行のリスクがない(はず)
- Cons
- プロセスが途中で死ぬとロックファイルが残ってしまう
- ロックファイル名が衝突する可能性がある
pidofコマンドでプロセスがあるかを調べる
/usr/sbin/pidof -x cron_job.sh >/dev/null || /path/to/your/cron_job.sh
一番簡単かもcron起動プロセスの多重起動防止ワンライナー - Qiita
ワンライナーでやるならこれがいいかも。だけど、プロセスを探すときのクエリーを間違えそう。引数が多い時とか特に。
- Pros
- 解除処理が不要
- プロセスが途中で死んでも問題ない
- ファイルに頼らないので、ファイル名の衝突などのリスクがない
- Cons
- プロセスを探すときのクエリーを間違えそう
実行スクリプト自身をファイルロックする
flock -xn foobar.rb vi foobar.rb
Exclusive, non-blockingのオプションをつければ、実行プロセスは一つにして、他のプロセスは待たせないですぐ完了にできる。これが一番いいと思う。
- Pros
- 解除処理が不要
- プロセスが途中で死んでも問題ない
- ファイルに頼らないので、ファイル名の衝突などのリスクがない
- スクリプト自身をロックすれば依存するものを減らせる
- Cons
- ないと思う
Rubyでの実装例
以下、Rubyでの実装例
def main puts "Start #{Time.now}" locked = lock_myself if locked puts "Grab lock, Stay running" else puts "Quit due to process duplicated" return end sleep_sec = 65 puts "sleep #{sleep_sec} sec" sleep sleep_sec puts "Completed" end def lock_myself f = File.open(__FILE__, "a") # ロックが取れない場合、falseを返す f.flock(File::LOCK_EX | File::LOCK_NB) end main
こんなスクリプトが以下のようにcrontabに登録されていると、
ubuntu@ip-172-31-10-98:~$ crontab -l * * * * * ruby /home/ubuntu/flock_test.rb >> /var/log/cron_log
以下のように出力される。
出力の順番がずれているのは、スクリプトの完了時に標準出力がフラッシュされるため。ロックが取れないケースの方が先に完了するので先に出る。
ubuntu@ip-172-31-10-98:~$ tail -f /var/log/cron_log Start 2022-10-17 04:09:01 +0000 Quit due to process duplicated Start 2022-10-17 04:08:01 +0000 Grab lock, Stay running sleep 65 sec Completed Start 2022-10-17 04:11:01 +0000 Quit due to process duplicated Start 2022-10-17 04:10:01 +0000 Grab lock, Stay running sleep 65 sec Completed Start 2022-10-17 04:13:01 +0000 Quit due to process duplicated Start 2022-10-17 04:12:01 +0000 Grab lock, Stay running sleep 65 sec Completed Start 2022-10-17 04:15:01 +0000 Quit due to process duplicated Start 2022-10-17 04:14:01 +0000 Grab lock, Stay running sleep 65 sec Completed