Class: SuperSettings::LocalCache

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize(refresh_interval:) ⇒ LocalCache

Returns a new instance of LocalCache.

Parameters:

  • refresh_interval (Numeric)

    number of seconds to wait between checking for setting updates



24
25
26
27
28
# File 'lib/super_settings/local_cache.rb', line 24

def initialize(refresh_interval:)
  @refresh_interval = refresh_interval
  @lock = Mutex.new
  reset
end

Instance Attribute Details

#refresh_intervalObject

Number of seconds that the cache will be considered fresh. The database will only be checked for changed settings at most this often.



21
22
23
# File 'lib/super_settings/local_cache.rb', line 21

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.

Parameters:

  • key (String, Symbol)

    setting key



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/super_settings/local_cache.rb', line 40

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 : 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
          # For case where one thread could be iterating over the cache while it's updated causing an error
          @cache = @cache.merge(key => value).freeze
        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.

Parameters:

  • key (String, Symbol)

    setting key

Returns:

  • (Boolean)


74
75
76
# File 'lib/super_settings/local_cache.rb', line 74

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.

Parameters:

  • asynchronous (Boolean) (defaults to: false)

    whether to load settings in a background thread



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/super_settings/local_cache.rb', line 112

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
    begin
      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
  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.

Returns:

  • (Boolean)


104
105
106
# File 'lib/super_settings/local_cache.rb', line 104

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.

Parameters:

  • asynchronous (Boolean) (defaults to: false)

    whether to refresh settings in a background thread



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/super_settings/local_cache.rb', line 146

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
    return if @cache.empty?

    @refreshing = true
  end

  refresh_block = lambda do
    begin
      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
  end

  if asynchronous
    Thread.new(&refresh_block)
  else
    refresh_block.call
  end
end

#resetvoid

This method returns an undefined value.

Reset the cache to an unloaded state.



181
182
183
184
185
186
187
188
# File 'lib/super_settings/local_cache.rb', line 181

def reset
  @lock.synchronize do
    @cache = {}.freeze
    @last_refreshed = nil
    @next_check_at = Time.now + @refresh_interval
    @refreshing = false
  end
end

#sizeObject

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.

Returns:

  • the number of entries in the cache.



82
83
84
85
# File 'lib/super_settings/local_cache.rb', line 82

def size
  ensure_cache_up_to_date!
  @cache.size
end

#to_hHash

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.

Returns:

  • (Hash)


91
92
93
94
95
96
97
98
99
# File 'lib/super_settings/local_cache.rb', line 91

def to_h
  ensure_cache_up_to_date!
  hash = {}
  @cache.each do |key, data|
    value, _ = data
    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.



202
203
204
205
206
207
208
# File 'lib/super_settings/local_cache.rb', line 202

def update_setting(setting)
  return if Coerce.blank?(setting.key)

  @lock.synchronize do
    @cache = @cache.merge(setting.key => setting.value)
  end
end

#wait_for_loadObject

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.



212
213
214
215
216
217
218
# File 'lib/super_settings/local_cache.rb', line 212

def wait_for_load
  loop do
    return unless @refreshing

    sleep(0.001)
  end
end