Amba

Segment Targeting

Override remote-config values per segment and roll them out to a percentage of users.

A remote config key has a default value and an ordered list of conditions. Each condition is either a segment override (segment_id matches) or a percentage rollout (percentage of users, deterministically hashed by user id). The server evaluates them in order and returns the first match to the SDK as a flat key → value map.

Condition shape

type Condition = { segment_id: string; value: unknown } | { percentage: number; value: unknown }; // 0–100

Conditions live in remote_configs.conditions (JSONB array). They're evaluated in declaration order; the first match wins. If no condition matches, the SDK sees default_value.

Segment override example

Enable a new paywall only for users in the power_users segment:

POST /admin/config
 
{
  "key": "feature_paywall_v2",
  "value": false,
  "value_type": "boolean",
  "description": "New paywall flow",
  "conditions": [
    { "segment_id": "seg_power_users", "value": true }
  ]
}
  • Users in seg_power_users: true
  • Everyone else: false

Percentage rollout example

Ramp a feature to 25% of users:

POST /admin/config
 
{
  "key": "feature_onboarding_v2",
  "value": false,
  "value_type": "boolean",
  "conditions": [
    { "percentage": 25, "value": true }
  ]
}

Rollout is deterministic per user — the same user always resolves to the same bucket, so toggling into the cohort isn't random from request to request. A user who is "in at 25%" is also in at 50% — ramping up doesn't churn the cohort.

Combining overrides and rollout

Segment conditions are evaluated before percentage. Use this to guarantee certain cohorts while still rolling out to everyone else:

{
  "key": "feature_new_ui",
  "value": false,
  "conditions": [
    { "segment_id": "seg_internal_testers", "value": true },
    { "percentage": 10, "value": true }
  ]
}
  • Internal testers: always true.
  • Everyone else: true for a deterministic 10%, else false.

Multi-value overrides

Different cohorts, different values. value_type must match across conditions:

{
  "key": "weekly_goal_count",
  "value_type": "number",
  "value": 5,
  "conditions": [
    { "segment_id": "seg_new_users", "value": 3 },
    { "segment_id": "seg_power_users", "value": 10 }
  ]
}

From the SDK

The SDK only sees the resolved value — the conditions are server-side:

const goal = ((await client.config.get('weekly_goal_count')) as number | undefined) ?? 5;

Membership changes every 15 minutes (segment re-evaluation cadence), so a user who newly enters a segment may not see the override for up to 15 minutes. For faster rollouts, use percentage gates (deterministic, no re-evaluation latency).

Ramp-up playbook

  1. Ship the new code path behind a key that defaults to false.
  2. Verify internally by adding your own user to a seg_internal segment with value: true.
  3. Ramp — set percentage: 5, then 10, 25, 50, 100 over days / weeks.
  4. Graduate the key once you're at 100% and the code path is safe — either delete the key or leave it as a permanent flag.

Invalidation

Any mutation to a config row bumps the singleton config_versions.version_hash, which is returned as the ETag on GET /client/config. The SDK sends If-None-Match on subsequent fetches — a 304 tells it the cached payload is still valid.

Critically, version_hash includes conditions in its input, so adding / changing segment gates invalidates the cache immediately. Clients will re-fetch on their next TTL tick.

Next

On this page