Posted at

RailsでSQLite3を複数Thread/Processから同時WriteするときにBusyExceptionを回避


TL;DR

RailsのDBにSQLite3を使ってしまうと,Redis等々で同時WriteするとSQLite3::BusyExceptionが発生する.

database.ymltimeoutも効かぬらしい.

仕方がないので自分でプロセス横断の排他Lockを実装する.


やったこと

ActiveRecord.save/.save!でfalseが返ったり例外発生したりするので,成功するまでRetryするとかしても良かったかもしれませんが,あまり格好良くないので,排他Lock制御します.

Rubyの排他Lockの実装自体はぐぐるといっぱい出てくるので,それらを参考に↓のようなblockを排他実行させるClassを用意しました.


LockBlock.rb

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に用意します.


app/models/application_record.rb

def safe_save!

LockBlock::locked(`db_lock`) {
self.save!
}
end

あとは各Modelの.save/.save!やってるところをsafe_save!に置き換えれば完了.


これでいいのか疑問

ひとまず ↑ の対応を入れてRedis使って平行動作させて様子見してますが,FileのLock/Unlockのアトミック性とかよくわかっておらず,これで100%保証されてるのかどうかが不明...

# そもそも`SQLite3やめいとかは言わないように...

# 最初はシンプル機能のお手軽Appになるはずやったんや...

---///