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
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.
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:
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);
}
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)
}
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
Step 4: Canary at 5%
Enable the flag for 5% of users via the FeatureSignals dashboard:
- Open the new-search-algorithm flag
- Select the production environment
- Set Rollout Percentage to 5%
- Set Stickiness Attribute to
user_id - 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:
| Stage | Percentage | Minimum Wait | Check |
|---|---|---|---|
| Canary | 5% | 30 min | Error rates, latency |
| Early Ramp | 25% | 1 hour | Error budget, conversion |
| Half Rollout | 50% | 2 hours | A/B metrics comparison |
| Late Ramp | 75% | 4 hours | Infra load, DB query perf |
| Full Rollout | 100% | N/A | Remove flag after 1 week |
Have a rollback plan
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:
#!/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:
- Navigate to AI Janitor in the FeatureSignals dashboard
- The Janitor will flag
new-search-algorithmas a stale candidate (100% rollout, no recent toggles) - Click Generate PR to automatically remove the flag from your codebase, keeping the new search algorithm path
// The cleaned-up code — flag check removed, new algorithm is the default
async function search(query: string) {
return newSearchAlgorithm(query);
}