lib/ldclient-rb/impl/integrations/redis_impl.rb in launchdarkly-server-sdk-6.4.0 vs lib/ldclient-rb/impl/integrations/redis_impl.rb in launchdarkly-server-sdk-7.0.0
- old
+ new
@@ -3,21 +3,102 @@
module LaunchDarkly
module Impl
module Integrations
module Redis
+ #
+ # An implementation of the LaunchDarkly client's feature store that uses a Redis
+ # instance. This object holds feature flags and related data received from the
+ # streaming API. Feature data can also be further cached in memory to reduce overhead
+ # of calls to Redis.
+ #
+ # To use this class, you must first have the `redis` and `connection-pool` gems
+ # installed. Then, create an instance and store it in the `feature_store` property
+ # of your client configuration.
+ #
+ class RedisFeatureStore
+ include LaunchDarkly::Interfaces::FeatureStore
+
+ # Note that this class is now just a facade around CachingStoreWrapper, which is in turn delegating
+ # to RedisFeatureStoreCore where the actual database logic is. This class was retained for historical
+ # reasons, so that existing code can still call RedisFeatureStore.new. In the future, we will migrate
+ # away from exposing these concrete classes and use factory methods instead.
+
+ #
+ # Constructor for a RedisFeatureStore instance.
+ #
+ # @param opts [Hash] the configuration options
+ # @option opts [String] :redis_url URL of the Redis instance (shortcut for omitting redis_opts)
+ # @option opts [Hash] :redis_opts options to pass to the Redis constructor (if you want to specify more than just redis_url)
+ # @option opts [String] :prefix namespace prefix to add to all hash keys used by LaunchDarkly
+ # @option opts [Logger] :logger a `Logger` instance; defaults to `Config.default_logger`
+ # @option opts [Integer] :max_connections size of the Redis connection pool
+ # @option opts [Integer] :expiration expiration time for the in-memory cache, in seconds; 0 for no local caching
+ # @option opts [Integer] :capacity maximum number of feature flags (or related objects) to cache locally
+ # @option opts [Object] :pool custom connection pool, if desired
+ # @option opts [Boolean] :pool_shutdown_on_close whether calling `close` should shutdown the custom connection pool.
+ #
+ def initialize(opts = {})
+ core = RedisFeatureStoreCore.new(opts)
+ @wrapper = LaunchDarkly::Integrations::Util::CachingStoreWrapper.new(core, opts)
+ end
+
+ #
+ # Default value for the `redis_url` constructor parameter; points to an instance of Redis
+ # running at `localhost` with its default port.
+ #
+ def self.default_redis_url
+ LaunchDarkly::Integrations::Redis::default_redis_url
+ end
+
+ #
+ # Default value for the `prefix` constructor parameter.
+ #
+ def self.default_prefix
+ LaunchDarkly::Integrations::Redis::default_prefix
+ end
+
+ def get(kind, key)
+ @wrapper.get(kind, key)
+ end
+
+ def all(kind)
+ @wrapper.all(kind)
+ end
+
+ def delete(kind, key, version)
+ @wrapper.delete(kind, key, version)
+ end
+
+ def init(all_data)
+ @wrapper.init(all_data)
+ end
+
+ def upsert(kind, item)
+ @wrapper.upsert(kind, item)
+ end
+
+ def initialized?
+ @wrapper.initialized?
+ end
+
+ def stop
+ @wrapper.stop
+ end
+ end
+
class RedisStoreImplBase
begin
require "redis"
require "connection_pool"
REDIS_ENABLED = true
rescue ScriptError, StandardError
REDIS_ENABLED = false
end
def initialize(opts)
- if !REDIS_ENABLED
+ unless REDIS_ENABLED
raise RuntimeError.new("can't use #{description} because one of these gems is missing: redis, connection_pool")
end
@pool = create_redis_pool(opts)
@@ -26,11 +107,11 @@
@prefix = opts[:prefix] || LaunchDarkly::Integrations::Redis::default_prefix
@logger = opts[:logger] || Config.default_logger
@test_hook = opts[:test_hook] # used for unit tests, deliberately undocumented
- @stopped = Concurrent::AtomicBoolean.new(false)
+ @stopped = Concurrent::AtomicBoolean.new()
with_connection do |redis|
@logger.info("#{description}: using Redis instance at #{redis.connection[:host]}:#{redis.connection[:port]} and prefix: #{@prefix}")
end
end
@@ -53,17 +134,15 @@
private def create_redis_pool(opts)
redis_opts = opts[:redis_opts] ? opts[:redis_opts].clone : Hash.new
if opts[:redis_url]
redis_opts[:url] = opts[:redis_url]
end
- if !redis_opts.include?(:url)
+ unless redis_opts.include?(:url)
redis_opts[:url] = LaunchDarkly::Integrations::Redis::default_redis_url
end
max_connections = opts[:max_connections] || 16
- return opts[:pool] || ConnectionPool.new(size: max_connections) do
- ::Redis.new(redis_opts)
- end
+ opts[:pool] || ConnectionPool.new(size: max_connections) { ::Redis.new(redis_opts) }
end
end
#
# Internal implementation of the Redis feature store, intended to be used with CachingStoreWrapper.
@@ -133,10 +212,11 @@
try_again = true
end
else
final_item = old_item
action = new_item[:deleted] ? "delete" : "update"
+ # rubocop:disable Layout/LineLength
@logger.warn { "RedisFeatureStore: attempted to #{action} #{key} version: #{old_item[:version]} in '#{kind[:namespace]}' with a version that is the same or older: #{new_item[:version]}" }
end
redis.unwatch
end
end
@@ -149,11 +229,11 @@
end
private
def before_update_transaction(base_key, key)
- @test_hook.before_update_transaction(base_key, key) if !@test_hook.nil?
+ @test_hook.before_update_transaction(base_key, key) unless @test_hook.nil?
end
def items_key(kind)
@prefix + ":" + kind[:namespace]
end
@@ -174,25 +254,25 @@
#
# Internal implementation of the Redis big segment store.
#
class RedisBigSegmentStore < RedisStoreImplBase
KEY_LAST_UP_TO_DATE = ':big_segments_synchronized_on'
- KEY_USER_INCLUDE = ':big_segment_include:'
- KEY_USER_EXCLUDE = ':big_segment_exclude:'
+ KEY_CONTEXT_INCLUDE = ':big_segment_include:'
+ KEY_CONTEXT_EXCLUDE = ':big_segment_exclude:'
def description
"RedisBigSegmentStore"
end
def get_metadata
value = with_connection { |redis| redis.get(@prefix + KEY_LAST_UP_TO_DATE) }
Interfaces::BigSegmentStoreMetadata.new(value.nil? ? nil : value.to_i)
end
- def get_membership(user_hash)
+ def get_membership(context_hash)
with_connection do |redis|
- included_refs = redis.smembers(@prefix + KEY_USER_INCLUDE + user_hash)
- excluded_refs = redis.smembers(@prefix + KEY_USER_EXCLUDE + user_hash)
+ included_refs = redis.smembers(@prefix + KEY_CONTEXT_INCLUDE + context_hash)
+ excluded_refs = redis.smembers(@prefix + KEY_CONTEXT_EXCLUDE + context_hash)
if !included_refs && !excluded_refs
nil
else
membership = {}
excluded_refs.each { |ref| membership[ref] = false }