Skip to content

Instantly share code, notes, and snippets.

@drbobbeaty
Created September 19, 2014 17:10
Show Gist options
  • Save drbobbeaty/d7e7e13ae0e83dc7658b to your computer and use it in GitHub Desktop.
Save drbobbeaty/d7e7e13ae0e83dc7658b to your computer and use it in GitHub Desktop.
Bucketing Service code for experiment variants
(ns finch-experiments.bucket
"Namespace to deal with all the bucketing issues with the experiments and
users so that we can respond as the finch client would."
(:require [clj-endpoints :as ep]
[clj-endpoints.logging :refer [log-execution-time!]]
[clj-endpoints.util :refer [md5 to-uuid nil-if-empty compact]]
[finch-experiments.routing :as routing]
[finch-experiments.web :as web]
[finch-experiments.data :refer [experiment-list]]))
(def min-in-millis (* 1000 60))
(defn make-bucket
"Function to take a UUID and a layer name and use the same method as the finch
code does now to compute a bucket number for this UUID."
[id & [ln]]
(if id
(let [hv (md5 (str id "," (or ln "layer-name-for-test")))]
(mod (Long/parseLong (.substring hv 24) 16) 1000))))
(defn grab-config
"Function to get a map of all the *active* experiment config data. This is
the core of the bucketing as we need to know the layer, the experiment, and
the buckets in order to make a decision about any one person. This will be
cached for 5 mins at a time to save load on the services and speed access."
[cluster]
(let [cfg (ep/config cluster)
exps (experiment-list)]
(merge (select-keys (web/get-config cfg) exps)
(select-keys (routing/get-config cfg) exps))))
(defn- variant-for-population
"Function to take a bucket number, a population, a sequence of variants,
and a country code and return the appropriate variant for this bucket in
this population - given that the bucket is in the experiment. If not,
this function will return `nil`."
[b p vs cc]
(if (and b p vs cc)
(let [cvs (count vs)
roll (nil-if-empty (:rollout p))
ok-cc? ((set (:countries p)) cc)
[lbn hbn] (:buckets p)]
(or roll
(if (and ok-cc? (<= lbn b hbn))
(nth vs (mod (- b lbn) cvs) nil))))))
(defn to-variants
"Function to take a very brute-force approach to the bucketing of a user
into the active experiments. This looks at all the configured experiments
from all sources, and then buckets the privided user's b-cookie into each
of the experiments and returns a list of all the experiments that the user
is involved in (and it's not all of them) - based on the variants and the
bucketing of the b-cookie."
[cluster bcookie cc]
(let [cfg (grab-config cluster)
bId (to-uuid bcookie)]
(compact (for [[en edata] cfg
:when (:unique edata)
:let [ln (:layer-name edata)
vns (:variant-names edata)
bn (make-bucket bId ln)]
pd (:populations edata)
:let [vn (variant-for-population bn pd vns cc)]]
(if vn
{ :layer ln
:experiment en
:bucket bn
:variant vn })))))
(log-execution-time! to-variants {:msg-fn (fn [ret c bc cc] (format "(%s) %s in %s" c bc cc))})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment