fetlife / rollout

Feature flippers.
MIT License
2.89k stars 211 forks source link

Should memoization be used? #147

Open berniechiu opened 4 years ago

berniechiu commented 4 years ago

Description

I found the feature setting is retrieved directly from redis often if used on every request. Should we memoize it?

jordanfbrown commented 4 years ago

We noticed this as well and ended up subclassing the Rollout class to add request-level memoization via the RequestStore gem. There's a probably a better way to do it but this worked well enough for us.

class MemoizedRollout < Rollout
  def active?(feature, user = nil)
    key = key_for(feature, user)
    RequestStore.fetch(key) { super }
  end

  def activate(feature)
    clear_cache(feature)
    super
  end

  def deactivate(feature)
    clear_cache(feature)
    super
  end

  def activate_user(feature, user)
    clear_cache(feature)
    super
  end

  def deactivate_user(feature, user)
    clear_cache(feature)
    super
  end

  private

  def clear_cache(feature)
    RequestStore.store.keys.each do |key|
      next unless key.to_s.start_with?("rollout:#{feature}")
      RequestStore.delete(key)
    end
  end

  def key_for(feature, user)
    ['rollout', feature.to_s, user&.class&.to_s, user&.id].compact.join(':')
  end
end
berniechiu commented 4 years ago

Cool! @jordanfbrown, thanks for sharing!

jnunemaker commented 4 years ago

Rollout isn’t overly public about it but I believe it uses the adapter pattern by only using like two of Redis methods. You can inject anything you like which means you can make a class with same interface that wraps the Redis client with memorization.

jnunemaker commented 4 years ago

Kind what @jordanfbrown did but you shouldn’t need to subclass rollout. You can just define the same interface and initialize with a Redis instance. Your class could be MemoizedRedis. Then you just pass that into Rollout.new. The only other issue with memoization is to turn it on per request or job and ensure it is cleared after. You can do this with middleware or in before action calls in rails. Middleware early in the stack is best.

berniechiu commented 4 years ago

@jnunemaker Ha thanks!

I was just thinking about how https://github.com/jnunemaker/flipper works when opened the issue here