Class: Magick::Adapters::Registry
- Inherits:
-
Object
- Object
- Magick::Adapters::Registry
- Defined in:
- lib/magick/adapters/registry.rb
Constant Summary collapse
- CACHE_INVALIDATION_CHANNEL =
'magick:cache:invalidate'
Instance Method Summary collapse
- #all_features ⇒ Object
- #delete(feature_name) ⇒ Object
- #exists?(feature_name) ⇒ Boolean
- #get(feature_name, key) ⇒ Object
-
#initialize(memory_adapter, redis_adapter = nil, active_record_adapter: nil, circuit_breaker: nil, async: false, primary: nil) ⇒ Registry
constructor
A new instance of Registry.
-
#invalidate_cache(feature_name) ⇒ Object
Explicitly trigger cache invalidation for a feature This is useful for targeting updates that need immediate cache invalidation Invalidates memory cache in current process AND publishes to Redis for other processes.
-
#publish_cache_invalidation(feature_name) ⇒ Object
Publish cache invalidation message to Redis Pub/Sub (without deleting local memory cache) This is useful when you’ve just updated the cache and want to notify other processes but keep the local memory cache intact.
-
#redis_available? ⇒ Boolean
Check if Redis adapter is available.
-
#redis_client ⇒ Object
Get Redis client (public method for use by other classes).
- #set(feature_name, key, value) ⇒ Object
Constructor Details
#initialize(memory_adapter, redis_adapter = nil, active_record_adapter: nil, circuit_breaker: nil, async: false, primary: nil) ⇒ Registry
Returns a new instance of Registry.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/magick/adapters/registry.rb', line 8 def initialize(memory_adapter, redis_adapter = nil, active_record_adapter: nil, circuit_breaker: nil, async: false, primary: nil) @memory_adapter = memory_adapter @redis_adapter = redis_adapter @active_record_adapter = active_record_adapter @circuit_breaker = circuit_breaker || Magick::CircuitBreaker.new @async = async @primary = primary || :memory # :memory, :redis, or :active_record @subscriber_thread = nil @subscriber = nil @last_reload_times = {} # Track last reload time per feature for debouncing @reload_mutex = Mutex.new # Only start Pub/Sub subscriber if Redis is available # In memory-only mode, each process has isolated cache (no cross-process invalidation) start_cache_invalidation_subscriber if redis_adapter end |
Instance Method Details
#all_features ⇒ Object
129 130 131 132 133 134 135 |
# File 'lib/magick/adapters/registry.rb', line 129 def all_features features = [] features += memory_adapter.all_features if memory_adapter features += redis_adapter.all_features if redis_adapter features += active_record_adapter.all_features if active_record_adapter features.uniq end |
#delete(feature_name) ⇒ Object
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/magick/adapters/registry.rb', line 99 def delete(feature_name) memory_adapter&.delete(feature_name) if redis_adapter begin redis_adapter.delete(feature_name) # Publish cache invalidation message publish_cache_invalidation(feature_name) rescue AdapterError # Continue even if Redis fails end end return unless active_record_adapter begin active_record_adapter.delete(feature_name) rescue AdapterError # Continue even if Active Record fails end end |
#exists?(feature_name) ⇒ Boolean
121 122 123 124 125 126 127 |
# File 'lib/magick/adapters/registry.rb', line 121 def exists?(feature_name) return true if memory_adapter&.exists?(feature_name) return true if redis_adapter&.exists?(feature_name) == true return true if active_record_adapter&.exists?(feature_name) == true false end |
#get(feature_name, key) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/magick/adapters/registry.rb', line 25 def get(feature_name, key) # Try memory first (fastest) - no Redis calls needed thanks to Pub/Sub invalidation value = memory_adapter.get(feature_name, key) if memory_adapter return value unless value.nil? # Fall back to Redis if available if redis_adapter begin value = redis_adapter.get(feature_name, key) # Update memory cache if found in Redis if value && memory_adapter memory_adapter.set(feature_name, key, value) return value end # If Redis returns nil, continue to next adapter rescue StandardError, AdapterError # Redis failed, continue to next adapter end end # Fall back to Active Record if available if active_record_adapter begin value = active_record_adapter.get(feature_name, key) # Update memory cache if found in Active Record memory_adapter.set(feature_name, key, value) if value && memory_adapter return value rescue StandardError, AdapterError # Active Record failed, return nil nil end end nil end |
#invalidate_cache(feature_name) ⇒ Object
Explicitly trigger cache invalidation for a feature This is useful for targeting updates that need immediate cache invalidation Invalidates memory cache in current process AND publishes to Redis for other processes
140 141 142 143 144 145 146 |
# File 'lib/magick/adapters/registry.rb', line 140 def invalidate_cache(feature_name) # Invalidate memory cache in current process immediately memory_adapter&.delete(feature_name) # Publish to Redis Pub/Sub to invalidate cache in other processes publish_cache_invalidation(feature_name) end |
#publish_cache_invalidation(feature_name) ⇒ Object
Publish cache invalidation message to Redis Pub/Sub (without deleting local memory cache) This is useful when you’ve just updated the cache and want to notify other processes but keep the local memory cache intact
163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/magick/adapters/registry.rb', line 163 def publish_cache_invalidation(feature_name) return unless redis_adapter begin redis_client = redis_adapter.instance_variable_get(:@redis) redis_client&.publish(CACHE_INVALIDATION_CHANNEL, feature_name.to_s) rescue StandardError => e # Silently fail - cache invalidation is best effort warn "Failed to publish cache invalidation: #{e.message}" if defined?(Rails) && Rails.env.development? end end |
#redis_available? ⇒ Boolean
Check if Redis adapter is available
149 150 151 |
# File 'lib/magick/adapters/registry.rb', line 149 def redis_available? !redis_adapter.nil? end |
#redis_client ⇒ Object
Get Redis client (public method for use by other classes)
154 155 156 157 158 |
# File 'lib/magick/adapters/registry.rb', line 154 def redis_client return nil unless redis_adapter redis_adapter.instance_variable_get(:@redis) end |
#set(feature_name, key, value) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/magick/adapters/registry.rb', line 61 def set(feature_name, key, value) # Update memory first (always synchronous) memory_adapter&.set(feature_name, key, value) # Update Redis if available if redis_adapter update_redis = proc do circuit_breaker.call do redis_adapter.set(feature_name, key, value) end rescue AdapterError => e # Log error but don't fail - memory is updated warn "Failed to update Redis: #{e.message}" if defined?(Rails) && Rails.env.development? end if @async && defined?(Thread) # For async updates, publish cache invalidation synchronously # This ensures other processes are notified immediately, even if Redis update is delayed publish_cache_invalidation(feature_name) Thread.new { update_redis.call } else update_redis.call # Publish cache invalidation message to notify other processes publish_cache_invalidation(feature_name) end end # Always update Active Record if available (as fallback/persistence layer) return unless active_record_adapter begin active_record_adapter.set(feature_name, key, value) rescue AdapterError => e # Log error but don't fail warn "Failed to update Active Record: #{e.message}" if defined?(Rails) && Rails.env.development? end end |