PERF: Redis snapshotting during tests (#15260)

We can fake redis transactions so that `fab!` works for redis and PG
data, but it's too slow to be used indiscriminately. Instead, you can
opt into it with the `use_redis_snapshotting` helper.

Insofar as snapshotting allows us to `fab!` more things, it provides a
speedup.
This commit is contained in:
Daniel Waterworth 2021-12-10 14:25:26 -06:00 committed by GitHub
parent e42f33b6ba
commit 02245ce41f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 5 deletions

View File

@ -53,7 +53,8 @@ class DiscourseRedis
:msetnx, :persist, :pexpire, :pexpireat, :psetex, :pttl, :rename, :renamenx, :rpop, :rpoplpush, :rpush, :rpushx, :sadd, :scard, :msetnx, :persist, :pexpire, :pexpireat, :psetex, :pttl, :rename, :renamenx, :rpop, :rpoplpush, :rpush, :rpushx, :sadd, :scard,
:sdiff, :set, :setbit, :setex, :setnx, :setrange, :sinter, :sismember, :smembers, :sort, :spop, :srandmember, :srem, :strlen, :sdiff, :set, :setbit, :setex, :setnx, :setrange, :sinter, :sismember, :smembers, :sort, :spop, :srandmember, :srem, :strlen,
:sunion, :ttl, :type, :watch, :zadd, :zcard, :zcount, :zincrby, :zrange, :zrangebyscore, :zrank, :zrem, :zremrangebyrank, :sunion, :ttl, :type, :watch, :zadd, :zcard, :zcount, :zincrby, :zrange, :zrangebyscore, :zrank, :zrem, :zremrangebyrank,
:zremrangebyscore, :zrevrange, :zrevrangebyscore, :zrevrank, :zrangebyscore ].each do |m| :zremrangebyscore, :zrevrange, :zrevrangebyscore, :zrevrank, :zrangebyscore,
:dump, :restore].each do |m|
define_method m do |*args, **kwargs| define_method m do |*args, **kwargs|
args[0] = "#{namespace}:#{args[0]}" if @namespace args[0] = "#{namespace}:#{args[0]}" if @namespace
DiscourseRedis.ignore_readonly { @redis.public_send(m, *args, **kwargs) } DiscourseRedis.ignore_readonly { @redis.public_send(m, *args, **kwargs) }

41
lib/redis_snapshot.rb Normal file
View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
class RedisSnapshot
def self.begin_faux_transaction(redis = Discourse.redis)
@stack ||= []
@stack.push(RedisSnapshot.load(redis))
end
def self.end_faux_transaction(redis = Discourse.redis)
@stack.pop.restore(redis)
end
def self.load(redis = Discourse.redis)
keys = redis.keys
values =
redis.pipelined do
keys.each do |key|
redis.dump(key)
end
end
new(keys.zip(values).delete_if { |k, v| v.nil? })
end
def initialize(dump)
@dump = dump
end
def restore(redis = Discourse.redis)
redis.pipelined do
redis.flushdb
@dump.each do |key, value|
redis.restore(key, 0, value)
end
end
nil
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module RedisSnapshotHelper
def use_redis_snapshotting
before(:all) do
RedisSnapshot.begin_faux_transaction
end
after(:all) do
RedisSnapshot.end_faux_transaction
end
before(:each) do
RedisSnapshot.begin_faux_transaction
end
after(:each) do
RedisSnapshot.end_faux_transaction
end
end
end

View File

@ -78,6 +78,7 @@ end
# in spec/support/ and its subdirectories. # in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/fabricators/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/fabricators/*.rb")].each { |f| require f }
require_relative './helpers/redis_snapshot_helper'
# Require plugin helpers at plugin/[plugin]/spec/plugin_helper.rb (includes symlinked plugins). # Require plugin helpers at plugin/[plugin]/spec/plugin_helper.rb (includes symlinked plugins).
if ENV['LOAD_PLUGINS'] == "1" if ENV['LOAD_PLUGINS'] == "1"
@ -181,6 +182,7 @@ end
RSpec.configure do |config| RSpec.configure do |config|
config.fail_fast = ENV['RSPEC_FAIL_FAST'] == "1" config.fail_fast = ENV['RSPEC_FAIL_FAST'] == "1"
config.silence_filter_announcements = ENV['RSPEC_SILENCE_FILTER_ANNOUNCEMENTS'] == "1" config.silence_filter_announcements = ENV['RSPEC_SILENCE_FILTER_ANNOUNCEMENTS'] == "1"
config.extend RedisSnapshotHelper
config.include Helpers config.include Helpers
config.include MessageBus config.include MessageBus
config.include RSpecHtmlMatchers config.include RSpecHtmlMatchers

View File

@ -1756,11 +1756,13 @@ RSpec.describe TopicsController do
end end
describe '#show' do describe '#show' do
fab!(:private_topic) { Fabricate(:private_message_topic) } use_redis_snapshotting
let!(:topic) { Fabricate(:post).topic }
let!(:p1) { Fabricate(:post, user: topic.user) } fab!(:private_topic) { Fabricate(:private_message_topic) }
let!(:p2) { Fabricate(:post, user: topic.user) } fab!(:topic) { Fabricate(:post).topic }
fab!(:p1) { Fabricate(:post, user: topic.user) }
fab!(:p2) { Fabricate(:post, user: topic.user) }
describe 'when topic is not allowed' do describe 'when topic is not allowed' do
it 'should return the right response' do it 'should return the right response' do