Class: FeatureHub::Sdk::RedisSessionStore

Inherits:
RawUpdateFeatureListener show all
Includes:
SessionStoreHelpers
Defined in:
lib/feature_hub/sdk/redis_session_store.rb,
sig/feature_hub/featurehub.rbs

Overview

Persists feature values from a FeatureHubRepository in Redis so they survive process restarts and are shared across multiple processes.

Uses SHA256-based change detection and Redis WATCH/MULTI/EXEC for multi-process safety.

WARNING: Do not use with server-evaluated features. Each server-evaluated context sends different resolved values; storing them in a shared Redis key will cause processes to overwrite each other's feature states.

On initialization the store reads any previously saved features from Redis and replays them into the repository. A periodic timer re-reads the SHA key so that updates published by other processes are picked up automatically.

Constant Summary collapse

SOURCE =

Returns:

  • (String)
"redis-store"

Instance Method Summary collapse

Methods inherited from RawUpdateFeatureListener

#config_changed

Constructor Details

#initialize(connection_or_client, config, opts = nil) ⇒ RedisSessionStore

Returns a new instance of RedisSessionStore.

Parameters:

  • connection_or_client (String, Array, Object)

    Redis URL, list of cluster URLs, or an existing Redis client

  • config (FeatureHubConfig)

    SDK config (provides repository and environment_id)

  • opts (RedisSessionStoreOptions, Hash, nil) (defaults to: nil)

    optional configuration

  • connection_string: (String)
  • repository: (FeatureHubRepository)
  • opts: ({ namespace: Integer?, prefix: String?, timeout: Integer?, password: String?, logger: untyped }, nil)


45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/feature_hub/sdk/redis_session_store.rb', line 45

def initialize(connection_or_client, config, opts = nil)
  super()

  options = opts.is_a?(RedisSessionStoreOptions) ? opts : RedisSessionStoreOptions.new(opts)

  @repository = config.repository
  @environment_id = config.environment_id
  @prefix = options.prefix
  @backoff_timeout = options.backoff_timeout
  @retry_update_count = options.retry_update_count
  @refresh_timeout = options.refresh_timeout
  @internal_sha = nil
  @mutex = Mutex.new
  @task = nil
  @logger = options.logger

  return unless redis_available?

  @redis = if connection_or_client.is_a?(String)
             Redis.new(url: connection_or_client, db: options.db)
           else
             connection_or_client
           end

  config.register_raw_update_listener(self)

  @logger&.debug("[featurehubsdk] started redis store")
  Concurrent::Future.execute { load_from_redis }
  start_timer
end

Instance Method Details

#closeObject



111
112
113
114
115
116
# File 'lib/feature_hub/sdk/redis_session_store.rb', line 111

def close
  return if @task.nil?

  @task.shutdown
  @task = nil
end

#delete_feature(feature, source) ⇒ Object



102
103
104
105
106
107
108
109
# File 'lib/feature_hub/sdk/redis_session_store.rb', line 102

def delete_feature(feature, source)
  return if source == SOURCE || !redis_available? || !feature["id"]

  perform_store_with_retry do |redis_features|
    updated = redis_features.reject { |f| f["id"] == feature["id"] }
    updated.length < redis_features.length ? updated : nil
  end
end

#process_update(feature, source) ⇒ Object



91
92
93
94
95
96
97
98
99
100
# File 'lib/feature_hub/sdk/redis_session_store.rb', line 91

def process_update(feature, source)
  return if source == SOURCE || !redis_available?

  perform_store_with_retry do |redis_features|
    existing = redis_features.find { |f| f["id"] == feature["id"] }
    next nil if existing && version_of(existing) >= version_of(feature)

    merge_features(redis_features, [feature])
  end
end

#process_updates(features, source) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/feature_hub/sdk/redis_session_store.rb', line 76

def process_updates(features, source)
  return if source == SOURCE || !redis_available?

  incoming_sha = calculate_sha(features)
  return if incoming_sha == @redis.get(sha_key)

  perform_store_with_retry do |redis_features|
    has_newer = features.any? do |f|
      existing = redis_features.find { |rf| rf["id"] == f["id"] }
      existing.nil? || version_of(f) > version_of(existing)
    end
    has_newer ? merge_features(redis_features, features) : nil
  end
end