Rails の悲観的ロック戦略が Rails 7 / Postgresql アプリで機能しない
概要
Rails 7 アプリでは、Postgresql データベースを使用して、jsonb フィールド データを含むモデル Stat があります。 jsonb フィールド データに独自の行を追加するいくつかの非同期プロセスで、大規模な同時実行が発生しています。
同時実行の回復力をテストするために、Rspec
テストを作成しました。キーと値のペア {“
threads = 20.times.map.with_index do |_, index|
Thread.new do
stat = Stat.find(stat_id)
stat.with_lock do
stat.update!(data: stat.data.merge({index => "ok"}))
end
end
end
threads.map &:join
stat = Stat.find(stat_id)
expect(stat.data.length).to eq(20)
解決策
Rails のデフォルトでは、テスト サンプルは単一のトランザクションで実行され、テストの最後にロールバックされて DB が元の状態に復元されます。これは、これらすべてのスレッドを作成しているにもかかわらず、それらはすべてプールから同じ DB 接続に集められていることを意味します。つまり、各スレッドのロックは他のスレッドから分離されていません。
use_transactional_fixtures をオフにすると、希望する動作が表示されます (RSpec::Rails v6 には、トランザクション テストをオフにするための優れたヘルパーがあります)。ただし、DB を自分でクリーンアップする必要もあります。これをテストに投げ込みます。
include RSpec::Rails::FixtureSupport
uses_transaction "the name of your test"
after { Stat.delete_all }
また、おそらく、database.yml の接続プール サイズを増やして、少なくとも 20 個の接続を使用できるようにする必要があります。