Class: SuperSettings::LocalCache
- Inherits:
-
Object
- Object
- SuperSettings::LocalCache
- Defined in:
- lib/super_settings/local_cache.rb
Overview
Cache that stores the settings in memory so they can be looked up without any network overhead. All of the settings will be loaded in the cache and the database will only be checked every few seconds for changes so that lookups are very fast.
The cache is thread safe and it ensures that only a single thread will ever be trying to update the cache at a time to avoid any dog piling effects.
Instance Attribute Summary collapse
-
#refresh_interval ⇒ Object
Number of seconds that the cache will be considered fresh.
Instance Method Summary collapse
-
#[](key) ⇒ Object
Get a setting value from the cache.
-
#include?(key) ⇒ Boolean
private
Check if the cache includes a key.
-
#initialize(refresh_interval:) ⇒ LocalCache
constructor
A new instance of LocalCache.
-
#load_settings(asynchronous = false) ⇒ void
Load all the settings from the database into the cache.
-
#loaded? ⇒ Boolean
Return true if the cache has already been loaded from the database.
-
#refresh(asynchronous = false) ⇒ void
Load only settings that have changed since the last load.
-
#reset ⇒ void
Reset the cache to an unloaded state.
-
#size ⇒ Object
private
Get the number of entries in the cache.
-
#to_h ⇒ Hash
Return the cached settings as a key/value hash.
-
#update_setting(setting) ⇒ Object
private
Update a single setting directly into the cache.
-
#wait_for_load ⇒ Object
private
Wait for the settings to be loaded if a new load was triggered.
Constructor Details
#initialize(refresh_interval:) ⇒ LocalCache
Returns a new instance of LocalCache.
20 21 22 23 24 |
# File 'lib/super_settings/local_cache.rb', line 20 def initialize(refresh_interval:) @refresh_interval = refresh_interval @lock = Mutex.new reset end |
Instance Attribute Details
#refresh_interval ⇒ Object
Number of seconds that the cache will be considered fresh. The database will only be checked for changed settings at most this often.
17 18 19 |
# File 'lib/super_settings/local_cache.rb', line 17 def refresh_interval @refresh_interval end |
Instance Method Details
#[](key) ⇒ Object
Get a setting value from the cache.
This method will periodically check the cache for freshness and update the cache from the database if there are any differences.
Cache misses will be stored in the cache so that a request for a missing setting does not hit the database every time. This does mean that that you should not call this method with a large number of dynamically generated keys since that could lead to memory bloat.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/super_settings/local_cache.rb', line 36 def [](key) ensure_cache_up_to_date! key = key.to_s value = @cache[key] if value.nil? && !@cache.include?(key) if @refreshing value = NOT_DEFINED else setting = Setting.find_by_key(key) value = (setting ? setting.value.freeze : NOT_DEFINED) # Guard against caching too many cache missees; at some point it's better to slam # the database rather than run out of memory. if setting || @cache.size < 100_000 @lock.synchronize do # Don't overwrite an entry added by a concurrent refresh; it is at least as # fresh as the value read here. A new hash is set so that one thread can # iterate over the cache while it's updated without causing an error. @cache = @cache.merge(key => value).freeze unless @cache.include?(key) end end end end return nil if value == NOT_DEFINED value end |
#include?(key) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Check if the cache includes a key. Note that this will return true if you have tried to fetch a non-existent key since the cache will store that as undefined. This method is provided for testing purposes.
72 73 74 |
# File 'lib/super_settings/local_cache.rb', line 72 def include?(key) @cache.include?(key.to_s) end |
#load_settings(asynchronous = false) ⇒ void
This method returns an undefined value.
Load all the settings from the database into the cache.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/super_settings/local_cache.rb', line 109 def load_settings(asynchronous = false) return if @refreshing @lock.synchronize do return if @refreshing @refreshing = true @next_check_at = Time.now + @refresh_interval end load_block = lambda do values = {} start_time = Time.now Setting.active.each do |setting| values[setting.key] = setting.value.freeze end set_cache_values(start_time) { values } ensure @refreshing = false end if asynchronous Thread.new(&load_block) else load_block.call end end |
#loaded? ⇒ Boolean
Return true if the cache has already been loaded from the database.
101 102 103 |
# File 'lib/super_settings/local_cache.rb', line 101 def loaded? !!@last_refreshed end |
#refresh(asynchronous = false) ⇒ void
This method returns an undefined value.
Load only settings that have changed since the last load.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/super_settings/local_cache.rb', line 141 def refresh(asynchronous = false) last_refresh_time = @last_refreshed return if last_refresh_time.nil? return if @refreshing @lock.synchronize do return if @refreshing @next_check_at = Time.now + @refresh_interval @refreshing = true end refresh_block = lambda do last_db_update = Setting.last_updated_at if last_db_update.nil? || last_db_update >= last_refresh_time - 1 merge_load(last_refresh_time) end ensure @refreshing = false end if asynchronous Thread.new(&refresh_block) else refresh_block.call end end |
#reset ⇒ void
This method returns an undefined value.
Reset the cache to an unloaded state.
172 173 174 175 176 177 178 179 |
# File 'lib/super_settings/local_cache.rb', line 172 def reset @lock.synchronize do @cache = {}.freeze @last_refreshed = nil @next_check_at = Time.now + @refresh_interval @refreshing = false end end |
#size ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Get the number of entries in the cache. Note that this will include cache misses as well.
80 81 82 83 |
# File 'lib/super_settings/local_cache.rb', line 80 def size ensure_cache_up_to_date! @cache.size end |
#to_h ⇒ Hash
Return the cached settings as a key/value hash. Calling this method will load the cache with the settings if they have not already been loaded.
89 90 91 92 93 94 95 96 |
# File 'lib/super_settings/local_cache.rb', line 89 def to_h ensure_cache_up_to_date! hash = {} @cache.each do |key, value| hash[key] = value unless value == NOT_DEFINED end hash end |
#update_setting(setting) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Update a single setting directly into the cache.
193 194 195 196 197 198 199 |
# File 'lib/super_settings/local_cache.rb', line 193 def update_setting(setting) return if Coerce.blank?(setting.key) @lock.synchronize do @cache = @cache.merge(setting.key => setting.value.freeze).freeze end end |
#wait_for_load ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Wait for the settings to be loaded if a new load was triggered. This can happen asynchronously.
203 204 205 206 207 208 209 |
# File 'lib/super_settings/local_cache.rb', line 203 def wait_for_load loop do return unless @refreshing sleep(0.001) end end |