December 08, 2016

Posted by John

Tagged flipper

Older: Flipping ActiveRecord

Flipper Preloading

Flipper is already pretty optimized for production usage (flipper does billions of feature checks a day at GitHub), but the latest release (0.10.2) just received a couple new ones — all thanks to community contributions.

In jnunemaker/flipper#190, @mscoutermarsh said:

Hi,

Would love if there’s a way to preload enabled features for an actor. For Product Hunt, we check several features in a single request for current_user. With activerecord this adds up to quite a few queries. Would love to get it down to one.

From browsing source I don’t believe this is currently available.

I suggested using caching, as we do at GitHub, but also put some thoughts down on how preloading features could work. @gshutler saw the conversation and put pen to paper on a great pull request that made the idea concrete.

Some Background

Often times users of flipper do many feature checks per request. Normally, this would require a network call for each feature check, but flipper comes with a Memoizer middleware that stores the gate values for a feature in memory for the duration of a request. This makes it so the first feature check for a feature performs a network call, but subsequent ones just do in memory Hash fetches (aka dramatic hamster faster).

DSL#preload and Adapter#get_multi

The addition by gshutler was an adapter method get_multi, which takes an array of features and allows the adapter to load all features in one network call. He then added DSL#preload, along with a :preload option for the Memoizer middleware, which use get_multi to load any provided features in one network call instead of N.

By default, the Adapter implementation of get_multi performs one get per feature. Each adapter can then override this functionality with a more efficient means of fetching the data. For example, the active record adapter uses an IN query to select all gate values for all features.

def get_multi(features)
  db_gates = @gate_class.where(feature_key: features.map(&:key))
  grouped_db_gates = db_gates.group_by { |gate| gate.feature_key }
  result = {}
  features.each do |feature|
    result[feature.key] = result_for_feature(feature, grouped_db_gates[feature.key])
  end
  result
end

gshutler provided the redis implementation of get_multi and then I, as the maintainer of flipper, added get_multi for the other core adapters, along with updates to the Adapters and Optimizations documentation.

The Result

mscoutermarsh was kind enough to drop a graph of the result for an endpoint of Product Hunt.

I will leave it up to the reader to determine when he deployed the preloading. :) Looks like ~50% improvement on that endpoint.

I feel like this is a great example of the power of open source and how important it is to efficiently load data for requests, so I thought I would take the time to write it up. Hope you enjoyed it. Happy bulk loading!

1 Comment

  1. Thanks for sharing amazing information.

Sorry, comments are closed for this article to ease the burden of pruning spam.

About

Authored by John Nunemaker (Noo-neh-maker), a programmer who has fallen deeply in love with Ruby. Learn More.

Projects

Flipper
Release your software more often with fewer problems.
Flip your features.