FeatureSignals

Progressive Rollout Tutorial

Learn how to safely release a new feature by gradually increasing the percentage of users who see it — from 0% to 100% — while monitoring for errors and regressions at every step.

What you'll build

In this tutorial, you'll implement a progressive rollout for a new search algorithm. You'll start at 0%, ramp to 5% (canary), then 25%, 50%, 75%, and finally 100% — all without deploying code. Each step is gated by monitoring checks.

How Percentage Rollouts Work

FeatureSignals uses consistent hashing for percentage rollouts. When you set a flag to roll out at 25%, exactly 25% of your users receive the enabled variation — and the same users consistently receive it throughout the rollout. The hash is computed from a user attribute you specify (typically user_id or email).

This means a user who sees the new feature at 10% will still see it at 50% and 100%. They're never "flipped back" to the old behavior as the percentage increases — unless you explicitly change the stickiness attribute.

Step 1: Create the Feature Flag

Create a boolean flag that will control the new search algorithm. You can do this via the dashboard or the API.

Create flag via APIBash
1
2
3
4
5
6
7
8
9
10
curl -X POST https://api.featuresignals.com/v1/projects/{projectID}/flags \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "new-search-algorithm",
    "name": "New Search Algorithm",
    "type": "boolean",
    "defaultValue": false,
    "toggleCategory": "release"
  }'

Step 2: Instrument Your Code

Wrap the new search algorithm behind the feature flag. Here are examples in several languages:

Node.jsTypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { FeatureSignalsClient } from '@featuresignals/node';


const client = new FeatureSignalsClient(process.env.FS_API_KEY!, {
  envKey: 'production',
});


await client.waitForReady();


async function search(query: string, user: { id: string }) {
  const useNewAlgorithm = client.boolVariation(
    'new-search-algorithm',
    { key: user.id },
    false,
  );


  if (useNewAlgorithm) {
    return newSearchAlgorithm(query);
  }
  return legacySearchAlgorithm(query);
}
GoGo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package search


import (
    fs "github.com/featuresignals/sdk-go"
)


var client *fs.Client


func init() {
    client = fs.NewClient(
        os.Getenv("FS_API_KEY"),
        "production",
    )
    <-client.Ready()
}


func Search(ctx context.Context, query string, userID string) ([]Result, error) {
    useNew := client.BoolVariation(
        "new-search-algorithm",
        fs.NewContext(userID),
        false,
    )


    if useNew {
        return newSearchAlgorithm(ctx, query)
    }
    return legacySearchAlgorithm(ctx, query)
}
PythonPython
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from featuresignals import FeatureSignalsClient, ClientOptions, EvalContext
import os


client = FeatureSignalsClient(
    os.environ["FS_API_KEY"],
    ClientOptions(env_key="production"),
)
client.wait_for_ready()


def search(query: str, user_id: str) -> list[dict]:
    use_new = client.bool_variation(
        "new-search-algorithm",
        EvalContext(key=user_id),
        False,
    )


    if use_new:
        return new_search_algorithm(query)
    return legacy_search_algorithm(query)

Step 3: Start at 0% (Dark Launch)

Deploy the instrumented code with the flag set to 0%in production. This is a "dark launch" — the new code path is deployed but no users hit it yet. Monitor your application for any unrelated regressions from the code deploy.

Info

Always deploy the flag-wrapped code first with 0% rollout. This decouples the code deployment from the feature release and lets you verify the wrapping code itself doesn't introduce issues.

Step 4: Canary at 5%

Enable the flag for 5% of users via the FeatureSignals dashboard:

  1. Open the new-search-algorithm flag
  2. Select the production environment
  3. Set Rollout Percentage to 5%
  4. Set Stickiness Attribute to user_id
  5. Click Save

Monitor for at least 30 minutes at 5%. Watch for:

  • Increased error rates on the search endpoint
  • Increased p95/p99 latency
  • Unexpected log patterns or warnings
  • User-reported issues via support channels

Step 5: Ramp Through Checkpoints

Gradually increase the percentage, monitoring at each checkpoint before proceeding. Here's a recommended ramp schedule:

StagePercentageMinimum WaitCheck
Canary5%30 minError rates, latency
Early Ramp25%1 hourError budget, conversion
Half Rollout50%2 hoursA/B metrics comparison
Late Ramp75%4 hoursInfra load, DB query perf
Full Rollout100%N/ARemove flag after 1 week

Have a rollback plan

At any stage, you can instantly set the percentage back to 0% if something goes wrong. FeatureSignals propagates the change to all SDKs within your configured polling interval (default: 30 seconds). No code deploy required.

Step 6: Automate Ramp Checks (Optional)

For teams practicing continuous delivery, you can automate the ramp using the FeatureSignals API and your CI/CD pipeline:

Automated ramp scriptBash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/bin/bash
# ramp-flag.sh — progressively increase rollout percentage


API_KEY="$FS_API_KEY"
FLAG_KEY="new-search-algorithm"
ENV_KEY="production"
BASE_URL="https://api.featuresignals.com"


ramp() {
  local percentage=$1
  echo "Ramping $FLAG_KEY to $percentage%..."


  curl -X PATCH \
    "$BASE_URL/v1/flags/by-key/$FLAG_KEY/environments/$ENV_KEY" \
    -H "Authorization: Bearer $API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"rolloutPercentage\": $percentage, \"enabled\": true}"


  echo "Waiting for propagation..."
  sleep 60  # Allow SDKs to pick up the change
}


# Check error rate before proceeding
check_errors() {
  # Replace with your monitoring tool's API
  error_rate=$(curl -s "$MONITORING_URL/api/errors?service=search" | jq '.rate')
  if (( $(echo "$error_rate > 0.01" | bc -l) )); then
    echo "ERROR: Error rate too high ($error_rate). Aborting!"
    ramp 0  # Rollback to 0%
    exit 1
  fi
  echo "Error rate OK: $error_rate"
}


# Ramp sequence
ramp 5   && sleep 1800 && check_errors  # 30 min at 5%
ramp 25  && sleep 3600 && check_errors  # 1 hour at 25%
ramp 50  && sleep 7200 && check_errors  # 2 hours at 50%
ramp 75  && sleep 14400 && check_errors # 4 hours at 75%
ramp 100


echo "Full rollout complete!"

Step 7: Clean Up the Flag

After the flag has been at 100% for at least a week with no issues, it's time to remove the flag from your codebase. The AI Janitor can help detect flags ready for cleanup:

  1. Navigate to AI Janitor in the FeatureSignals dashboard
  2. The Janitor will flag new-search-algorithm as a stale candidate (100% rollout, no recent toggles)
  3. Click Generate PR to automatically remove the flag from your codebase, keeping the new search algorithm path
After cleanup — no more flagTypeScript
1
2
3
4
// The cleaned-up code — flag check removed, new algorithm is the default
async function search(query: string) {
  return newSearchAlgorithm(query);
}

Next Steps