December 08, 2016
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
Jul 18, 2017
Thanks for sharing amazing information.
Sorry, comments are closed for this article to ease the burden of pruning spam.