File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -15,13 +15,25 @@ def initialize(context, adapter = nil)
1515
1616 def cleanup_old_experiments!
1717 return if @cleaned_up
18- keys_without_finished ( user . keys ) . each do |key |
19- experiment = Experiment . new key_without_version ( key )
20- if experiment . nil? || experiment . has_winner? || experiment . start_time . nil?
21- user . delete key
22- user . delete Experiment . finished_key ( key )
18+ keys = keys_without_finished ( user . keys )
19+ names = keys . map { |key | key_without_version ( key ) }
20+
21+ unless names . empty?
22+ winners , start_times = Split . redis . pipelined do |pipe |
23+ pipe . hmget ( :experiment_winner , *names )
24+ pipe . hmget ( :experiment_start_times , *names )
25+ end
26+
27+ keys . each_with_index do |key , index |
28+ has_winner = !winners [ index ] . nil?
29+ not_started = start_times [ index ] . nil?
30+ if has_winner || not_started
31+ user . delete key
32+ user . delete Experiment . finished_key ( key )
33+ end
2334 end
2435 end
36+
2537 @cleaned_up = true
2638 end
2739
Original file line number Diff line number Diff line change 1+ # frozen_string_literal: true
2+
3+ require "redis-client"
4+
5+ module SplitRedisCallInstrumentation
6+ KEY = :split_redis_call_count
7+
8+ def call ( command , config )
9+ SplitRedisCallInstrumentation . increment
10+ super
11+ end
12+
13+ def call_pipelined ( commands , config )
14+ SplitRedisCallInstrumentation . increment
15+ super
16+ end
17+
18+ class << self
19+ def count
20+ previous = Thread . current [ KEY ]
21+ Thread . current [ KEY ] = 0
22+ yield
23+ Thread . current [ KEY ]
24+ ensure
25+ Thread . current [ KEY ] = previous
26+ end
27+
28+ def increment
29+ count = Thread . current [ KEY ]
30+ Thread . current [ KEY ] = count + 1 if count
31+ end
32+ end
33+ end
34+
35+ RedisClient . register ( SplitRedisCallInstrumentation )
36+
37+ RSpec ::Matchers . define :make_redis_calls do |expected |
38+ supports_block_expectations
39+
40+ match do |block |
41+ @actual = SplitRedisCallInstrumentation . count ( &block )
42+ values_match? ( expected , @actual )
43+ end
44+
45+ failure_message do
46+ "expected block to make #{ description_of ( expected ) } Redis roundtrip(s), but made #{ @actual } "
47+ end
48+
49+ failure_message_when_negated do
50+ "expected block not to make #{ description_of ( expected ) } Redis roundtrip(s), but it did (#{ @actual } )"
51+ end
52+
53+ description do
54+ "make #{ description_of ( expected ) } Redis roundtrip(s)"
55+ end
56+ end
Original file line number Diff line number Diff line change 8888 @subject . cleanup_old_experiments!
8989 end
9090 end
91+
92+ context "with many experiments" do
93+ let ( :user_keys ) do
94+ {
95+ "with_winner" => "red" ,
96+ "not_started" => "red" ,
97+ "active" => "red"
98+ }
99+ end
100+
101+ before do
102+ with_winner = Split ::ExperimentCatalog . find_or_create ( "with_winner" , "red" , "blue" )
103+ with_winner . start
104+ with_winner . winner = "red"
105+
106+ Split ::ExperimentCatalog . find_or_create ( "active" , "red" , "blue" ) . start
107+ end
108+
109+ it "keeps active experiments while dropping finished/not-started ones" do
110+ @subject . cleanup_old_experiments!
111+
112+ expect ( @subject . keys ) . to eq ( [ "active" ] )
113+ end
114+
115+ it "batches the winner/start-time lookups into a single roundtrip" do
116+ expect { @subject . cleanup_old_experiments! } . to make_redis_calls ( 1 )
117+ end
118+ end
91119 end
92120
93121 context "allows user to be loaded from adapter" do
You can’t perform that action at this time.
0 commit comments