Techioz Blog

Rails の悲観的ロック戦略が Rails 7 / Postgresql アプリで機能しない

概要

Rails 7 アプリでは、Postgresql データベースを使用して、jsonb フィールド データを含むモデル Stat があります。 jsonb フィールド データに独自の行を追加するいくつかの非同期プロセスで、大規模な同時実行が発生しています。

同時実行の回復力をテストするために、Rspec テストを作成しました。キーと値のペア {“” => “ok”} をデータ jsonb フィールドに 20 回追加します。最後に、データ フィールドのランダムな長さを取得します。弾力性がある場合、長さは常に「20」でなければなりません。

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 個の接続を使用できるようにする必要があります。