• Home
  • Integrations
  • SDKs
  • Guides
  • API docs
No results for ""
EXPAND ALL

EDIT ON GITHUB

Flag evaluation rules in server-side SDKs

Read time: 6 minutes
Last edited: Dec 14, 2021

Overview

This topic explains the algorithm used to evaluate a feature flag. This document is intended to be used by developers contributing or building server-side SDKs for LaunchDarkly.

Client-side and server-side SDKs evaluate feature flags differently:

  • Server-side SDKs evaluate feature flags internally using embedded evaluation rules
  • Client-side SDKs evaluate feature flags by contacting LaunchDarkly, which runs the following evaluation rules on the back end

SDKs evaluate flags for specific users. The arguments needed to evaluate a flag are the flag key, user, and the default variation value.

Evaluating a flag for a specific user involves the following steps:

  1. Preliminary checks: for example, verifying that the flag is on and the supplied user has a key
  2. Prerequisite checks: determine whether the flag's prerequisites are met
  3. Individual targeting checks: determine whether the user matches an individual user targeting rule
  4. Targeting rule checks: determine whether the user matches other targeting rules
  5. Fallthrough: return the default variation value

The result of the evaluation is:

  1. The evaluated variation value
  2. The evaluation reason for returning that variation

Variation values are often referenced by their index. For example, if a flag has two variations, true and false, then true will be variation index 0, and false will be variation index 1.

As a side effect of the evaluation, the SDKs send events back to LaunchDarkly to report that the evaluation has occurred. The data sent back for each evaluation (including prerequisites) follows the schema described in the schema reference guide for Data Export.

Preliminary checks

Five preliminary checks occur before the main flag evaluation algorithm executes:

  • If the SDK is offline, return the provided default value with the ERROR evaluation reason and the corresponding error code.
  • Fetch the flag definition from the flag store. If the definition is not found, return the provided default value with the ERROR evaluation reason and the corresponding error code.
  • If the user doesn't have a key attribute, return the provided default value with the ERROR evaluation reason and the corresponding error code.
  • If the user key is an empty string, log a warning and proceed.
  • If the flag is turned off, return the flag's off variation value with the OFF evaluation reason.

If all the above checks pass, the evaluation algorithm proceeds with prerequisite checks.

Prerequisite checks

A flag may have prerequisites. A prerequisite is defined by a tuple of prerequisite flag key and variation index. A prerequisite is satisfied if, for a given user, the prerequisite flag evaluates to the specified variation index.

In the prerequisite check step, the SDK iterates through each flag's prerequisites and verifies that each prerequisite is satisfied. If a prerequisite is not satisfied, the SDK returns the flag's off variation value, with the PREREQUISITE_FAILED evaluation reason and the prerequisite flag key that failed. Prerequisite evaluation is short-circuited, which means the SDK returns after the first failure.

Below are the steps for prerequisite evaluations:

  • Retrieve the prerequisite flag. If not found, the prerequisite fails.
  • If the prerequisite flag is off, the prerequisite fails.
  • Evaluate the prerequisite flag for the given user with no default value.
  • If the prerequisite flag evaluation result's variation index matches the variation index defined for the prerequisite, the prerequisite is satisfied.

Prerequisite flags themselves may also have their own prerequisites, in which case the evaluations are recursive. LaunchDarkly prevents recursive cycles during flag creation and modification.

Once all prerequisites have passed, or there were no prerequisites, the evaluation algorithm proceeds with individual targeting checks.

Individual targeting checks

Flags may have targets. A target is a list of user keys associated with a specific variation index. SDKs iterate through flags' targets and check whether the targets contain the given user's key.

If a target contains the user's key, return the associated variation with the TARGET_MATCH evaluation reason.

If there were no targets, or no targets matched the user, the evaluation algorithm proceeds with the targeting rule checks.

Targeting rule checks

Flags may have rules. Rules offer more complex ways to match arbitrary user attributes and segments, and to execute controlled rollouts based on those criteria. A rule is defined as a tuple of the ID, collection of clauses, and either the variation index or a rollout plan, as described in more detail below. For a rule to match, all rules' clauses must be satisfied.

SDKs iterate through flags' rules to find the first rule that matches the given user. If the matched rule has a variation index, return the corresponding variation, with the rule's ID and the RULE_MATCH evaluation reason. If, on the other hand, the rule has a rollout, follow the rollout logic to arrive at the variation and also return the corresponding variation, along with the rule ID and the RULE_MATCH evaluation reason.

If a targeting rule references any custom attributes with null values, then the flag skips that rule. If there were no rules, or no rules matched the user, the evaluation algorithm proceeds with the fallthrough.

Clauses

Rules have clauses, all of which must be satisfied for a rule to match the user. Clauses are defined as a tuple of:

  • attribute: user attribute to consider for comparison.
  • operator: comparison to perform. To learn more, read Operators.
  • values: list of arbitrary values to compare to.
  • negate: boolean, whether to negate the outcome of clause's comparison. In the LaunchDarkly UI, negated clauses are represented as separate negated operators.

Evaluating a clause is a matter of taking the value of the given user attribute and performing the given operator comparison against the given values, and then applying whether the outcome should be negated. If this produces a positive result, the clause has been satisfied.

If the user attribute value is an array (or list), then the comparison is performed for each of those attribute values, against each of the clause values. In this case, a single user attribute value match to a single clause value is enough to satisfy the clause.

Operators

This table explains operators and their arguments and conditions:

OperatorArgument TypesCondition
inAnyThe user attribute value exactly matches the clause value, including its type.
endsWithStringEither the clause value is an empty string, or the user attribute value ends with the clause value.
startsWithStringThe user attribute value starts with the clause value.
matchesStringThe clause value, evaluated as a regex, matches the user attribute value.
containsStringThe user value attribute value contains the clause value.
lessThanNumberThe user attribute value is less than the clause value.
lessThanOrEqualNumberThe user attribute value is less than or equal to the clause value.
greaterThanNumberThe user attribute value is greater than the clause value.
greaterThanOrEqualNumberThe user attribute value is greater than or equal to the clause value.
beforeInteger or string parsed as timestamp. This value is a UNIX epoch time in milliseconds.The user attribute value less than the clause value.
afterInteger or string parsed as timestamp. This value is a UNIX epoch time in milliseconds.The user attribute value greater than the clause value.
semVerEqualString parsed as semantic versionThe user attribute value and the clause value are semantically equivalent.
semVerLessThanString parsed as semantic versionThe user attribute value precedes the clause value.
semVerGreaterThanString parsed as semantic versionThe clause value precedes the user attribute value.
segmentMatchStringSegment match operation succeeds (see below)

Segments

Clauses' segmentMatch operator allows for more fine-grained control of segments of users. The clause values are segment keys. To determine if this clause is satisfied, retrieve each of the segments identified by those keys and determine if the user is part of any of those segments.

Segments' properties include a list of included users, a list of excluded users, and a list of rules of their own, which themselves contain rule clauses with one exception: segment rule clauses may not contain segmentMatch operators.

To evaluate whether a user is in a segment:

  • If the user's key is in the segment's included list, the result is true.
  • If the user's key is in the segment's excluded list, the result is false.
  • If neither, then iterate over the segment's rules to determine if any match:
    • Iterate over the segment rule's clauses:
      • Evaluate if the clause matches the user.
      • If any clause doesn't match, the rule doesn't match and move on to the next rule.
    • If the segment rule's weight is null, the rule matches and the result is true.
    • Get the segment rule's bucketBy value. If the value is not set, use key.
    • Compute the variation bucket for the user using the segment rule's bucketing key and the segment's salt. Otherwise, follow the same logic as for rollouts.
    • If the user's variation bucket is less than the segment rule's weight divided by 100,000, the rule matches and the result is true. Otherwise, the rule doesn't match and move on to the next rule.
  • If the user's key wasn't in the included and excluded lists, and none of the rules matched, the result is false.
  • Return the potentially negated (depending on clause's negate property) result from above.

Percentage rollouts

You can associate rules with percentage rollouts, which provides flexibility when you assign users to variation buckets.

Rules are defined as:

  • a subset of a list of weighted variations, and
  • a name of the attribute by which to assign the user to a variation bucket.

Weighted variations are a subset of the variation index and a non-negative integer between 0 and 100,000 acting as that variation's weight.

To assign a user to a variation bucket and calculate the final variation:

  1. Get the user's attribute name from the rollout to assign the user. If this is not set, use the user's key.

  2. Get the user's attribute value.

  3. Get the user's optional secondary identifier.

  4. Compute the variation bucket for user.

    1. Concatenate the flag's key, the flag's salt, the user's attribute value and, if applicable, the secondary identifier. Concatenate them with periods, .. If there is a seed present, concatenate seed and the user's attribute value instead.
    2. Copy the first 15 characters of the SHA1 of the above.
    3. Convert the resulting base 16 integer to a base 10 integer.
    4. Divide the resulting base 10 integer by 0xFFFFFFFFFFFFFFF (1152921504606846975). The result of this division is the user's variation bucket number.
  5. Iterate over the rollout's weighted variations.

    1. Starting at 0, keep adding the weighted variation's weight divided by 100,000 to the sum.
    2. When a user's variation bucket is less than the above sum, return the weighted variation's variation index.

Fallthrough

Fallthrough is the last step in flag evaluation. If the evaluation process is made here, it means that all prerequisite checks passed and none of the targets or rules matched the user.

Fallthrough is defined as a single variation of a rollout. This is similar to how rules are defined, except without iterating through any IDs or clauses. If fallthrough is a variation index, then return the corresponding variation value with the FALLTHROUGH evaluation reason. Otherwise, go through the same rollouts steps to arrive at the variation index, and again, return the corresponding value and the FALLTHROUGH evaluation reason.