Experimentation functions
FeatureQL provides deterministic functions for A/B testing, feature flags, and gradual rollouts that ensure consistent assignment across distributed systems.
HASH01()
HASH01 provides consistent entity assignment to experiment variants using stable hashing. It returns a deterministic value between 0 and 1 based on the input string.
Syntax
HASH01(key: VARCHAR) -> DOUBLE
Parameters
key
: String to hash, typically entity ID concatenated with a salt
Returns
Double precision value between 0.0 and 1.0
Usage patterns
The function enables multiple experimentation patterns:
- Holdout groups: Reserve a percentage of users for control
- Feature toggles: Combine with business logic for targeted rollouts
- Statistical exposure: Assign users to experiment variants
- Multi variant tests: Chain conditions for A/B/C/n tests
Key principles:
- Always use a salt to ensure independent assignment across experiments
- The same input always produces the same output (deterministic)
- Distribution is uniform across the 0 to 1 range
GRADUAL_ROLLOUT()
GRADUAL_ROLLOUT enables controlled, time based feature deployment with deterministic assignment. Unlike simple percentage rollouts, it smoothly transitions from 0% to 100% coverage over a defined time window.
Syntax
GRADUAL_ROLLOUT(
key: VARCHAR,
time_ref: TIMESTAMP,
time_0pc: TIMESTAMP,
time_100pc: TIMESTAMP,
power: DOUBLE = 1.0,
chunks: BIGINT = 10
) -> BOOLEAN
Parameters
key
: Stable identifier (entity ID + salt for independence)time_ref
: Current time to evaluate (typically NOW() or event timestamp)time_0pc
: Start time when 0% are enabledtime_100pc
: End time when 100% are enabledpower
: Shape of rollout curve (1.0 = linear, 2.0 = quadratic, etc.)chunks
: Number of discrete steps in the rollout
Returns
Boolean indicating whether the entity is enabled at the given time
Linear rollout example
Quadratic rollout example
Using power = 2.0
creates a slower initial rollout that accelerates over time:
Rollout characteristics
The function guarantees:
- Monotonic progression: Once an entity is enabled, it stays enabled
- Deterministic assignment: Same inputs always yield same results
- Chunk based transitions: Entities move in groups for smoother metrics
- Customizable velocity: Power parameter controls rollout acceleration
Best practices
Salt management
-- Use unique salts per experiment/rollout
CONST EXPERIMENT_A_SALT := 'exp_a_2024_q1'
CONST ROLLOUT_FEATURE_X_SALT := 'feature_x_rollout_v2'
-- Combine entity ID with salt
HASH01(USER_ID::VARCHAR || EXPERIMENT_A_SALT)
Combining conditions
-- Layer multiple criteria for precise targeting
EXPOSED :=
NOT IN_HOLDOUT -- Not in global holdout
AND IS_ELIGIBLE -- Meets business criteria
AND IN_EXPERIMENT_POPULATION -- Statistically assigned (for experiments)
AND IN_ROLLED_OUT_POPULATION -- Time based rollout active (for minimizing risk of bugs at start)
Rollout strategies
Conservative rollout (power > 1):
- Start slow, accelerate later
- Good for high risk features
- More time to detect issues early
Aggressive rollout (power < 1):
- Start fast, decelerate later
- Good for low risk features
- Quickly reaches significant coverage
Stepped rollout (small number of chunks):
- Same % exposure for long periods of time
- Peak of new bugs at discrete points in time
- Better for continuous monitoring
Common patterns
Multi variant experiment
WITH
HASH_VALUE := HASH01(USER_ID::VARCHAR || 'experiment_v3'),
SELECT
VARIANT := CASE
WHEN HASH_VALUE < 0.22 THEN 'variant_a'
WHEN HASH_VALUE < 0.44 THEN 'variant_b'
WHEN HASH_VALUE < 0.66 THEN 'variant_c'
ELSE 'control'
END
Nested experiments
-- Run sub experiment within main experiment population
IN_MAIN_EXPERIMENT := HASH01(USER_ID::VARCHAR || 'main') < 0.5
IN_SUB_VARIANT :=
IN_MAIN_EXPERIMENT
AND HASH01(USER_ID::VARCHAR || 'sub') < 0.2
Sequenced experiments
-- Run experiment on the same population as phase 1 + New users (keep the same hashing key)
IN_EXPERIMENT_PHASE1 := HASH01(USER_ID::VARCHAR || 'experiment_v3') < 0.2
IN_EXPERIMENT_PHASE2 := HASH01(USER_ID::VARCHAR || 'experiment_v3') < 0.4