Skip to content

Commit bb3ecc5

Browse files
authored
Merge pull request #175 from launchdarkly/eb/sc-132086/cache-nil-big-seg
always cache big segment query result even if it's nil
2 parents 2b544c5 + 88e6b2a commit bb3ecc5

File tree

2 files changed

+37
-0
lines changed

2 files changed

+37
-0
lines changed

lib/ldclient-rb/impl/big_segments.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ module Impl
1111
BigSegmentMembershipResult = Struct.new(:membership, :status)
1212

1313
class BigSegmentStoreManager
14+
# use this as a singleton whenever a membership query returns nil; it's safe to reuse it because
15+
# we will never modify the membership properties after they're queried
16+
EMPTY_MEMBERSHIP = {}
17+
1418
def initialize(big_segments_config, logger)
1519
@store = big_segments_config.store
1620
@stale_after_millis = big_segments_config.stale_after * 1000
@@ -38,6 +42,7 @@ def get_user_membership(user_key)
3842
if !membership
3943
begin
4044
membership = @store.get_membership(BigSegmentStoreManager.hash_for_user_key(user_key))
45+
membership = EMPTY_MEMBERSHIP if membership.nil?
4146
@cache[user_key] = membership
4247
rescue => e
4348
LaunchDarkly::Util.log_exception(@logger, "Big Segment store membership query returned error", e)

spec/impl/big_segments_spec.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
require "concurrent/atomics"
55

6+
require "mock_components"
67
require "spec_helper"
78

89
module LaunchDarkly
@@ -50,6 +51,7 @@ def with_manager(config)
5051
store = double
5152
expect(store).to receive(:get_metadata).at_least(:once).and_return(always_up_to_date)
5253
expect(store).to receive(:get_membership).with(user_hash).once.and_return(expected_membership)
54+
# the ".once" on this mock expectation is what verifies that the cache is working; there should only be one query
5355
allow(store).to receive(:stop)
5456

5557
with_manager(BigSegmentsConfig.new(store: store)) do |m|
@@ -59,6 +61,36 @@ def with_manager(config)
5961
end
6062
end
6163

64+
it "can cache a nil result" do
65+
store = double
66+
expect(store).to receive(:get_metadata).at_least(:once).and_return(always_up_to_date)
67+
expect(store).to receive(:get_membership).with(user_hash).once.and_return(nil)
68+
# the ".once" on this mock expectation is what verifies that the cache is working; there should only be one query
69+
allow(store).to receive(:stop)
70+
71+
with_manager(BigSegmentsConfig.new(store: store)) do |m|
72+
expected_result = BigSegmentMembershipResult.new({}, BigSegmentsStatus::HEALTHY)
73+
expect(m.get_user_membership(user_key)).to eq(expected_result)
74+
expect(m.get_user_membership(user_key)).to eq(expected_result)
75+
end
76+
end
77+
78+
it "cache can expire" do
79+
expected_membership = { 'key1' => true, 'key2' => true }
80+
store = double
81+
expect(store).to receive(:get_metadata).at_least(:once).and_return(always_up_to_date)
82+
expect(store).to receive(:get_membership).with(user_hash).twice.and_return(expected_membership)
83+
# the ".twice" on this mock expectation is what verifies that the cached result expired
84+
allow(store).to receive(:stop)
85+
86+
with_manager(BigSegmentsConfig.new(store: store, user_cache_time: 0.01)) do |m|
87+
expected_result = BigSegmentMembershipResult.new(expected_membership, BigSegmentsStatus::HEALTHY)
88+
expect(m.get_user_membership(user_key)).to eq(expected_result)
89+
sleep(0.1)
90+
expect(m.get_user_membership(user_key)).to eq(expected_result)
91+
end
92+
end
93+
6294
it "with stale status" do
6395
expected_membership = { 'key1' => true, 'key2' => true }
6496
store = double

0 commit comments

Comments
 (0)