class LaunchDarkly::Impl::Integrations::Redis::RedisFeatureStoreCore

Internal implementation of the Redis feature store, intended to be used with CachingStoreWrapper.

Constants

REDIS_ENABLED

Public Class Methods

new(opts) click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 20
    def initialize(opts)
      if !REDIS_ENABLED
        raise RuntimeError.new("can't use Redis feature store because one of these gems is missing: redis, connection_pool")
      end

      @redis_opts = opts[:redis_opts] || Hash.new
      if opts[:redis_url]
        @redis_opts[:url] = opts[:redis_url]
      end
      if !@redis_opts.include?(:url)
        @redis_opts[:url] = LaunchDarkly::Integrations::Redis::default_redis_url
      end
      max_connections = opts[:max_connections] || 16
      @pool = opts[:pool] || ConnectionPool.new(size: max_connections) do
        ::Redis.new(@redis_opts)
      end
      # shutdown pool on close unless the client passed a custom pool and specified not to shutdown
      @pool_shutdown_on_close = (!opts[:pool] || opts.fetch(:pool_shutdown_on_close, true))
      @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)

      with_connection do |redis|
        @logger.info("RedisFeatureStore: using Redis instance at #{redis.connection[:host]}:#{redis.connection[:port]} \
and prefix: #{@prefix}")
      end
    end

Public Instance Methods

get_all_internal(kind) click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 73
def get_all_internal(kind)
  fs = {}
  with_connection do |redis|
    hashfs = redis.hgetall(items_key(kind))
    hashfs.each do |k, json_item|
      fs[k.to_sym] = Model.deserialize(kind, json_item)
    end
  end
  fs
end
get_internal(kind, key) click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 67
def get_internal(kind, key)
  with_connection do |redis|
    get_redis(redis, kind, key)
  end
end
init_internal(all_data) click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 50
def init_internal(all_data)
  count = 0
  with_connection do |redis|
    redis.multi do |multi|
      all_data.each do |kind, items|
        multi.del(items_key(kind))
        count = count + items.count
        items.each do |key, item|
          multi.hset(items_key(kind), key, Model.serialize(kind,item))
        end
      end
      multi.set(inited_key, inited_key)
    end
  end
  @logger.info { "RedisFeatureStore: initialized with #{count} items" }
end
initialized_internal?() click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 116
def initialized_internal?
  with_connection { |redis| redis.exists?(inited_key) }
end
stop() click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 120
def stop
  if @stopped.make_true
    return unless @pool_shutdown_on_close
    @pool.shutdown { |redis| redis.close }
  end
end
upsert_internal(kind, new_item) click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 84
      def upsert_internal(kind, new_item)
        base_key = items_key(kind)
        key = new_item[:key]
        try_again = true
        final_item = new_item
        while try_again
          try_again = false
          with_connection do |redis|
            redis.watch(base_key) do
              old_item = get_redis(redis, kind, key)
              before_update_transaction(base_key, key)
              if old_item.nil? || old_item[:version] < new_item[:version]
                result = redis.multi do |multi|
                  multi.hset(base_key, key, Model.serialize(kind, new_item))
                end
                if result.nil?
                  @logger.debug { "RedisFeatureStore: concurrent modification detected, retrying" }
                  try_again = true
                end
              else
                final_item = old_item
                action = new_item[:deleted] ? "delete" : "update"
                @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
        end
        final_item
      end

Private Instance Methods

before_update_transaction(base_key, key) click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 129
def before_update_transaction(base_key, key)
  @test_hook.before_update_transaction(base_key, key) if !@test_hook.nil?
end
cache_key(kind, key) click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 137
def cache_key(kind, key)
  kind[:namespace] + ":" + key.to_s
end
get_redis(redis, kind, key) click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 149
def get_redis(redis, kind, key)
  Model.deserialize(kind, redis.hget(items_key(kind), key))
end
inited_key() click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 141
def inited_key
  @prefix + ":$inited"
end
items_key(kind) click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 133
def items_key(kind)
  @prefix + ":" + kind[:namespace]
end
with_connection() { |redis| ... } click to toggle source
# File lib/ldclient-rb/impl/integrations/redis_impl.rb, line 145
def with_connection
  @pool.with { |redis| yield(redis) }
end