TL;DR
Rails
のDBにSQLite3
を使ってしまうと,Redis
等々で同時WriteするとSQLite3::BusyException
が発生する.
database.yml
のtimeout
も効かぬらしい.
仕方がないので自分でプロセス横断の排他Lockを実装する.
やったこと
ActiveRecord.save/.save!
でfalseが返ったり例外発生したりするので,成功するまでRetryするとかしても良かったかもしれませんが,あまり格好良くないので,排他Lock制御します.
Ruby
の排他Lockの実装自体はぐぐるといっぱい出てくるので,それらを参考に↓のようなblockを排他実行させるClassを用意しました.
require 'tmpdir'
class LockBlock
class << self
def locked(lock_file_name)
File::open(File::join(Dir::tmpdir, lock_file_name), 'w') { |file|
begin
file.flock(File::LOCK_EX)
yield
ensure
file.flock(File::LOCK_UN)
end
}
end
end
end
この排他Lockで守った状態でActiveRecord.save/.save!
するためのMethodをApplicationRecord
に用意します.
def safe_save!
LockBlock::locked(`db_lock`) {
self.save!
}
end
あとは各Modelの.save/.save!
やってるところをsafe_save!
に置き換えれば完了.
これでいいのか疑問
ひとまず ↑ の対応を入れてRedis
使って平行動作させて様子見してますが,FileのLock/Unlockのアトミック性とかよくわかっておらず,これで100%保証されてるのかどうかが不明...
# そもそも`SQLite3やめいとかは言わないように...
# 最初はシンプル機能のお手軽Appになるはずやったんや...
---///