Featurevisor

Definitions in depth

Bucketing

Bucketing gives each user a fixed position from 0 to 100 for each feature. Percentages and variation weights then decide what that position gets, so a user who sees a change keeps seeing it as the rollout widens. This page explains how the position is calculated and what keeps a user's result stable.

How a bucket is calculated

The SDK builds a bucket key from the context values named in the feature's bucketBy, followed by the feature key, joined with dots. It hashes the key with MurmurHash v3 into a number from 0 to 100,000, which is the bucket with three decimal places.

For checkout, with bucketBy: userId:

ContextBucket keyBucket
userId: 'user-5'user-5.checkout15.5
userId: 'user-9'user-9.checkout38.5
No userIdcheckout5.6
The bucket keys user-5.checkout, user-9.checkout and checkout each pass through the MurmurHash v3 hash and land at 15.5, 38.5 and 5.6 on a ruler from 0 to 100.

Nothing else goes into the key. Rules, segments, percentages and weights do not move a user's bucket, so the same user gets the same position on every device, in every SDK, for as long as the feature key stays the same.

From bucket to result

With the bucket known, the SDK takes the first rule whose conditions match the context:

  • The feature is enabled when the bucket is at most the rule's percentage. At 25%, user-5 at 15.5 is in and user-9 at 38.5 is out.
  • A user outside the matching rule's percentage is disabled. Later rules are not checked.
  • For a feature with variations, the bucket also picks the variation, from ranges allocated by weight. See Variations.

The evaluation flow lists every step, including force entries and requirements that come before the rules.

Choose what to bucket by

bucketBy takes one attribute, several, or the first one present:

bucketByBucket key for the context { organizationId: acme, userId: user-9 }
userIduser-9.checkout
[organizationId, userId]acme.user-9.checkout
or: [userId, deviceId]user-9.checkout, or the deviceId when there is no userId

Bucket by whatever should keep its result: a user, a device, or an organization when everyone in it should see the same thing.

Missing attributes

A bucketBy attribute that is missing from the context adds nothing to the key. With bucketBy: userId, every context without a userId shares the key checkout and the bucket 5.6. All anonymous visitors outside the Netherlands are therefore in checkout together, or out together at a lower percentage.

Two rulers with the everyone rule of checkout at 25%. With a userId, user-5 at 15.5 is in and user-9 at 38.5 is out. Without one, every visitor stands at 5.6, so all of them are in.

For applications with signed in users and anonymous visitors, fall back to a device identifier:

features/checkout.yml
# ...
bucketBy:
or: [userId, deviceId]

Declare both attributes, and pass at least one. A visitor who signs in moves from their deviceId bucket to their userId bucket, so their result can change at that moment.

Consistent bucketing

A user's bucket never changes, but their result can. What changes it:

  • Lowering a percentage takes out the users above the new value.
  • Lowering a percentage, changing weights or renaming a rule redivides the variation ranges. See Variations.
  • Losing the state files that record variation ranges between builds.
  • Renaming the feature, which changes every bucket key.

Raising a percentage keeps everyone who was already in, and keeps their variation, unless the rule sets its own variationWeights.

Test a bucket

Specs fix the bucket with at, from 0 to 100, so a test does not depend on real user IDs:

tests/features/checkout.spec.yml
feature: checkout
assertions:
- description: Out above the rollout
at: 38.5
context:
country: de
expectedToBeEnabled: false

To see a real user's bucket and key, run npx featurevisor evaluate with --explain. It prints a line such as Bucket: 38533 / 100000 · key "user-9.checkout". SDK modules can change the bucket key or value before rules are checked.

Previous
Schemas
Next
Tags