Class: FeatureHub::Sdk::MemcacheSessionStore

Inherits:
RawUpdateFeatureListener show all
Includes:
SessionStoreHelpers
Defined in:
lib/feature_hub/sdk/memcache_session_store.rb

Overview

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

Uses SHA256-based change detection and compare-and-set 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 Memcache key will cause processes to overwrite each other’s feature states.

On initialization the store reads any previously saved features from Memcache 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 =
"memcache-store"

Instance Method Summary collapse

Methods inherited from RawUpdateFeatureListener

#config_changed

Constructor Details

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

Returns a new instance of MemcacheSessionStore.

Parameters:

  • connection_or_client (String, Dalli::Client)

    Memcache connection string or existing client

  • config (FeatureHubConfig)

    SDK config (provides repository and environment_id)

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

    optional configuration



43
44
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
# File 'lib/feature_hub/sdk/memcache_session_store.rb', line 43

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

  options = opts.is_a?(MemcacheSessionStoreOptions) ? opts : MemcacheSessionStoreOptions.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 dalli_available?

  @dalli = if connection_or_client.is_a?(String)
             Dalli::Client.new(connection_or_client, serializer: JSON)
           else
             connection_or_client
           end

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

Instance Method Details

#closeObject



107
108
109
110
111
112
# File 'lib/feature_hub/sdk/memcache_session_store.rb', line 107

def close
  return if @task.nil?

  @task.shutdown
  @task = nil
end

#delete_feature(feature, source) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/feature_hub/sdk/memcache_session_store.rb', line 98

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

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

#process_update(feature, source) ⇒ Object



87
88
89
90
91
92
93
94
95
96
# File 'lib/feature_hub/sdk/memcache_session_store.rb', line 87

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

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

    merge_features(memcache_features, [feature])
  end
end

#process_updates(features, source) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/feature_hub/sdk/memcache_session_store.rb', line 72

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

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

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