発生する問題
Railsのrails db:seed
を実行すると、以下のようなエラーが起きる場合がある。
$ rails db:seed
rails aborted!
ActiveRecord::StatementInvalid: SQLite3::BusyException: database is locked
/path-to-your-app/db/seeds.rb:69:in `<main>'
エラーが発生するコード例
エラーが発生した周辺のコードは次のようになっている。
# ...
User.destroy_all
100.times do |n|
user = User.create!(
email: "sample-#{n}@example.com",
password: 'password',
name: Faker::Name.name
)
image_url = Faker::Avatar.image(slug: user.email, size: '150x150')
# ActiveStorageを使ってavatarを設定
user.avatar.attach(io: URI.parse(image_url).open, filename: 'avatar.png')
end
# ...
加えて、このアプリケーションではSQLite3を使っている。
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development:
<<: *default
database: db/development.sqlite3
エラーが発生する原因
このエラーは以下の2つの要因で引き起こされる。
- ActiveStorageを使っているため、画像のアップロードや削除が非同期で行われる
- SQLite3は並行処理に弱い(参考)
User.destroy_all
をしたときは既存のデータに対して画像の削除処理が非同期で実行される。既存のデータが大量にあると、データベースに対して読み込みや書き込みのクエリが並行して発行される。
user.avatar.attach
も同様に非同期で画像のアップロード処理が行われる。上のコード例では100件のユーザーデータを作成しようとしたため、やはりデータベースに対して読み込みや書き込みのクエリが並行して発行される。
SQLiter3が並行処理に耐えきれなくなると、SQLite3::BusyExceptionが発生する。
エラー回避策
SQLite3の性能上の制約を考慮し、画像の削除処理やアップロード処理を同期的に行うようにする。具体的には以下の2行をconfig/seeds.rb
に追加する。
+ActiveStorage::AnalyzeJob.queue_adapter = :inline
+ActiveStorage::PurgeJob.queue_adapter = :inline
User.destroy_all
100.times do |n|
user = User.create!(
email: "sample-#{n}@example.com",
password: 'password',
name: Faker::Name.name
)
image_url = Faker::Avatar.image(slug: user.email, size: '150x150')
user.avatar.attach(io: URI.parse(image_url).open, filename: 'avatar.png')
end
ActiveStorage::AnalyzeJobはアップロード時に、ActiveStorage::PurgeJobは削除時にそれぞれ利用されるActiveJobのクラスである。
このqueue_adapter
を:inline
に変更することで、画像のアップロードや削除を同期的に実行できる。
参考 https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html
動作確認環境
- Rails 6.1.0
- Ruby 3.0.0
- SQLite3 3.32.3