ckdake/setler

Is it possible to cache application level settings?

Opened this issue · 2 comments

Would be nice if the "thing id" = null's out there that are generally application level settings were somehow cacheable to avoid invoking a query every time the setting needed to be checked.

Starts to add up after a while in a high traffic app especially if there are multiple settings per page. Is there any way to do that currently? I didn't see it in the README.

This is not something that Setler does currently, but it would be a nice feature to have.

I set this up on my application in the Settings class which was extending Setler. You could roll a lot of this into Setler itself fairly easily. Also, make sure that this is turned off during tests. I wasn't sure how to do per-request detection from a model so there is a very rigged up solution in the code instead.

class Settings < Setler::Settings

  cattr_accessor :last_cleared_at, :expire_checked_at, :expired_at, :cache_settings, :cache_values

  @cache_settings = nil
  @cache_values = {}

  # Get and Set variables when the calling method is the variable name
  def self.[](var)
    return super(var) unless self.cache_enabled?

    self.clear_cache if self.clear_cache?
    @cache_values[var.to_s] = super(var) unless @cache_values.has_key?(var.to_s)    

    @cache_values[var.to_s]
  end

  def self.[]=(var, value)
    response = super(var,value)
    if self.cache_enabled?
      self.expire_central_cache
      self.clear_cache
    end

    response
  end

  def self.cache_enabled?
    @cache_settings = Myapp::Application.config.settings_cache_enabled if @cache_settings.nil?
    @cache_settings == true
  end

  def self.expire_central_cache
    # Class/instance variables are stored in RAM on a per-dyno/server basis
    # This centrally notifies them to update local caches
    Rails.cache.write(:settings_cache_expired_at,Time.now.utc)
  end

  def self.central_cache_expired_at
    if @expire_checked_at.nil? || @expire_checked_at <= 1.second.ago
      # This is a poor man's per-request cache
      # Without this, we still hit Rails.cache on each check which can incur network latency depending on the cache
      @expired_at = Rails.cache.read(:settings_cache_expired_at)
      @expire_checked_at = Time.now.utc
    end

    @expired_at
  end

  def self.clear_cache?
    @last_cleared_at ||= Time.now.utc
    updated_at = self.central_cache_expired_at
    !(updated_at.nil? || updated_at < @last_cleared_at)
  end

  def self.clear_cache
    @cache_values = {}
    @last_cleared_at = Time.now.utc
  end
end