Feature Flags and Progressive Rollouts with Claude Code
This guide explores how to implement feature flags and progressive rollout strategies with Claude Code, enabling safe, controlled feature deployments with real-time monitoring and intelligent rollback capabilities.
Overview
Feature flags and progressive rollouts revolutionize software deployment by:
- Decoupling deployment from release: Deploy code anytime, release features when ready
- Reducing risk: Test features with small user segments before full rollout
- Enabling experimentation: A/B test features and measure impact
- Providing instant rollback: Disable problematic features without redeployment
Why Feature Flags with Claude Code?
- AI-Powered Decision Making: Claude can analyze metrics and recommend rollout strategies
- Intelligent Targeting: Use Claude to identify optimal user segments
- Automated Monitoring: Claude monitors feature performance and detects anomalies
- Smart Rollback: Automatic rollback based on intelligent metric analysis
- Natural Language Control: Manage flags through conversational interfaces
Core Concepts
1. Feature Flag Implementation
// feature-flags/FeatureFlag.ts
export interface FeatureFlag {
id: string;
name: string;
description: string;
enabled: boolean;
rules: TargetingRule[];
variants?: Variant[];
rolloutPercentage?: number;
createdAt: Date;
updatedAt: Date;
}
export interface TargetingRule {
id: string;
attribute: string;
operator: 'equals' | 'contains' | 'regex' | 'in' | 'percentage';
values: string[];
rolloutPercentage?: number;
}
export interface Variant {
id: string;
name: string;
weight: number;
payload?: any;
}
// feature-flags/FeatureFlagService.ts
export class FeatureFlagService {
private flags: Map<string, FeatureFlag> = new Map();
private evaluationCache: LRUCache<string, boolean>;
constructor() {
this.evaluationCache = new LRUCache({ max: 10000, ttl: 60000 });
}
async isEnabled(
flagKey: string,
context: EvaluationContext
): Promise<boolean> {
const cacheKey = `${flagKey}:${JSON.stringify(context)}`;
// Check cache
if (this.evaluationCache.has(cacheKey)) {
return this.evaluationCache.get(cacheKey)!;
}
// Evaluate flag
const flag = await this.getFlag(flagKey);
if (!flag || !flag.enabled) {
return false;
}
const result = await this.evaluateRules(flag, context);
this.evaluationCache.set(cacheKey, result);
// Track evaluation
await this.trackEvaluation(flagKey, context, result);
return result;
}
async getVariant(
flagKey: string,
context: EvaluationContext
): Promise<Variant | null> {
const flag = await this.getFlag(flagKey);
if (!flag || !flag.enabled || !flag.variants) {
return null;
}
// Use consistent hashing for variant assignment
const hash = this.hash(context.userId || context.sessionId);
const normalizedHash = hash / 0xFFFFFFFF;
let accumulator = 0;
for (const variant of flag.variants) {
accumulator += variant.weight;
if (normalizedHash <= accumulator) {
return variant;
}
}
return flag.variants[0]; // Fallback
}
private async evaluateRules(
flag: FeatureFlag,
context: EvaluationContext
): Promise<boolean> {
// Check targeting rules
for (const rule of flag.rules) {
if (await this.evaluateRule(rule, context)) {
return true;
}
}
// Check rollout percentage
if (flag.rolloutPercentage !== undefined) {
const hash = this.hash(context.userId || context.sessionId);
const percentage = (hash / 0xFFFFFFFF) * 100;
return percentage <= flag.rolloutPercentage;
}
return false;
}
private hash(value: string): number {
// MurmurHash3 implementation for consistent hashing
let h = 0;
for (let i = 0; i < value.length; i++) {
h = Math.imul(31, h) + value.charCodeAt(i) | 0;
}
return Math.abs(h);
}
}2. Progressive Rollout Strategy
// rollout/ProgressiveRollout.ts
export class ProgressiveRolloutStrategy {
private stages: RolloutStage[] = [];
private currentStageIndex: number = 0;
private metrics: MetricsCollector;
constructor(
private flagKey: string,
private flagService: FeatureFlagService,
private claudeAnalyzer: ClaudeAnalyzer
) {}
async defineStages(stages: RolloutStageConfig[]): Promise<void> {
this.stages = stages.map(config => ({
...config,
startedAt: null,
completedAt: null,
metrics: {},
status: 'pending'
}));
}
async executeRollout(): Promise<void> {
for (let i = 0; i < this.stages.length; i++) {
this.currentStageIndex = i;
const stage = this.stages[i];
// Start stage
await this.startStage(stage);
// Monitor stage
const success = await this.monitorStage(stage);
if (!success) {
await this.rollback();
throw new Error(`Rollout failed at stage ${i}: ${stage.name}`);
}
// Complete stage
await this.completeStage(stage);
}
}
private async startStage(stage: RolloutStage): Promise<void> {
stage.startedAt = new Date();
stage.status = 'active';
// Update feature flag
await this.flagService.updateFlag(this.flagKey, {
rolloutPercentage: stage.percentage,
rules: stage.targetingRules
});
// Notify monitoring
await this.metrics.recordEvent('rollout.stage.started', {
flagKey: this.flagKey,
stage: stage.name,
percentage: stage.percentage
});
}
private async monitorStage(stage: RolloutStage): Promise<boolean> {
const startTime = Date.now();
while (Date.now() - startTime < stage.duration) {
// Collect metrics
const metrics = await this.collectMetrics(stage);
// Analyze with Claude
const analysis = await this.claudeAnalyzer.analyzeRolloutMetrics({
stage: stage.name,
metrics,
thresholds: stage.successCriteria
});
if (analysis.recommendation === 'rollback') {
return false;
}
if (analysis.recommendation === 'accelerate' && stage.canAccelerate) {
break; // Move to next stage early
}
// Wait before next check
await this.sleep(stage.checkInterval || 60000);
}
return true;
}
private async collectMetrics(stage: RolloutStage): Promise<StageMetrics> {
return {
errorRate: await this.metrics.getErrorRate(stage.startedAt!, new Date()),
latency: await this.metrics.getAverageLatency(stage.startedAt!, new Date()),
throughput: await this.metrics.getThroughput(stage.startedAt!, new Date()),
customMetrics: await this.metrics.getCustomMetrics(
stage.customMetrics || [],
stage.startedAt!,
new Date()
)
};
}
}3. MCP Server Integration
// mcp-feature-flags-server.ts
import { MCPServer } from '@modelcontextprotocol/sdk';
import { FeatureFlagService } from './feature-flags/FeatureFlagService';
import { ProgressiveRolloutStrategy } from './rollout/ProgressiveRollout';
class FeatureFlagsMCPServer extends MCPServer {
private flagService: FeatureFlagService;
private rolloutManager: RolloutManager;
async initialize() {
this.flagService = new FeatureFlagService();
this.rolloutManager = new RolloutManager(this.flagService);
// Register flag management tools
this.registerTool({
name: 'create-feature-flag',
description: 'Create a new feature flag',
parameters: {
type: 'object',
properties: {
key: { type: 'string' },
name: { type: 'string' },
description: { type: 'string' },
enabled: { type: 'boolean', default: false },
rules: { type: 'array', optional: true },
variants: { type: 'array', optional: true }
}
},
execute: async (params) => {
const flag = await this.flagService.createFlag(params);
return { flag };
}
});
// Register evaluation tool
this.registerTool({
name: 'evaluate-flag',
description: 'Evaluate a feature flag for a given context',
parameters: {
type: 'object',
properties: {
flagKey: { type: 'string' },
context: {
type: 'object',
properties: {
userId: { type: 'string', optional: true },
sessionId: { type: 'string', optional: true },
attributes: { type: 'object', optional: true }
}
}
}
},
execute: async ({ flagKey, context }) => {
const enabled = await this.flagService.isEnabled(flagKey, context);
const variant = await this.flagService.getVariant(flagKey, context);
return { enabled, variant };
}
});
// Register rollout tool
this.registerTool({
name: 'start-progressive-rollout',
description: 'Start a progressive rollout for a feature flag',
parameters: {
type: 'object',
properties: {
flagKey: { type: 'string' },
stages: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
percentage: { type: 'number' },
duration: { type: 'number' },
targetingRules: { type: 'array', optional: true },
successCriteria: { type: 'object' }
}
}
}
}
},
execute: async ({ flagKey, stages }) => {
const rollout = await this.rolloutManager.createRollout(flagKey, stages);
await rollout.start();
return { rolloutId: rollout.id, status: 'started' };
}
});
// Register Claude analysis tool
this.registerTool({
name: 'analyze-feature-impact',
description: 'Use Claude to analyze feature flag impact',
parameters: {
type: 'object',
properties: {
flagKey: { type: 'string' },
timeRange: {
type: 'object',
properties: {
start: { type: 'string' },
end: { type: 'string' }
}
}
}
},
execute: async ({ flagKey, timeRange }) => {
const analysis = await this.analyzeFeatureImpact(flagKey, timeRange);
return { analysis };
}
});
}
private async analyzeFeatureImpact(
flagKey: string,
timeRange: { start: string; end: string }
): Promise<FeatureImpactAnalysis> {
// Collect metrics
const metrics = await this.collectFeatureMetrics(flagKey, timeRange);
// Use Claude to analyze
const prompt = `Analyze the impact of feature flag "${flagKey}" based on these metrics:
${JSON.stringify(metrics, null, 2)}
Please provide:
1. Overall impact assessment (positive/negative/neutral)
2. Key performance indicators affected
3. User experience impact
4. Recommendations for full rollout or rollback
5. Suggested improvements`;
const analysis = await this.claudeClient.analyze(prompt);
return {
flagKey,
timeRange,
metrics,
assessment: analysis.assessment,
recommendations: analysis.recommendations,
suggestedActions: analysis.actions
};
}
}Advanced Patterns
1. Canary Deployment with Feature Flags
// deployment/CanaryDeployment.ts
export class CanaryDeploymentManager {
private deployments: Map<string, CanaryDeployment> = new Map();
async createCanaryDeployment(
config: CanaryConfig
): Promise<CanaryDeployment> {
const deployment = new CanaryDeployment(config);
// Create feature flags for canary control
await this.createCanaryFlags(deployment);
// Start deployment
await deployment.start();
this.deployments.set(deployment.id, deployment);
return deployment;
}
private async createCanaryFlags(deployment: CanaryDeployment): Promise<void> {
// Traffic routing flag
await this.flagService.createFlag({
key: `canary.${deployment.id}.traffic`,
name: `Canary Traffic - ${deployment.name}`,
enabled: true,
rolloutPercentage: deployment.initialPercentage,
rules: [{
id: 'canary-users',
attribute: 'canaryGroup',
operator: 'equals',
values: [deployment.id]
}]
});
// Feature toggles for canary-specific features
for (const feature of deployment.features) {
await this.flagService.createFlag({
key: `canary.${deployment.id}.feature.${feature}`,
name: `Canary Feature - ${feature}`,
enabled: true,
rules: [{
id: 'canary-only',
attribute: 'deploymentVersion',
operator: 'equals',
values: [deployment.version]
}]
});
}
}
}
export class CanaryDeployment {
private metrics: CanaryMetrics;
private healthChecker: HealthChecker;
constructor(private config: CanaryConfig) {}
async start(): Promise<void> {
// Start health monitoring
this.healthChecker.startMonitoring({
interval: this.config.healthCheckInterval,
endpoints: this.config.canaryEndpoints
});
// Progressive traffic increase
await this.progressiveTrafficIncrease();
}
private async progressiveTrafficIncrease(): Promise<void> {
let currentPercentage = this.config.initialPercentage;
while (currentPercentage < 100) {
// Monitor health
const health = await this.checkCanaryHealth();
if (!health.isHealthy) {
await this.rollback();
throw new Error(`Canary unhealthy: ${health.reason}`);
}
// Increase traffic
currentPercentage = Math.min(
currentPercentage + this.config.incrementPercentage,
100
);
await this.updateTrafficPercentage(currentPercentage);
// Wait before next increase
await this.sleep(this.config.incrementInterval);
}
// Canary successful - promote to production
await this.promote();
}
private async checkCanaryHealth(): Promise<HealthStatus> {
const metrics = await this.metrics.collect();
// Compare canary vs stable metrics
const comparison = {
errorRateDiff: metrics.canary.errorRate - metrics.stable.errorRate,
latencyDiff: metrics.canary.avgLatency - metrics.stable.avgLatency,
throughputRatio: metrics.canary.throughput / metrics.stable.throughput
};
// Check thresholds
if (comparison.errorRateDiff > this.config.errorRateThreshold) {
return { isHealthy: false, reason: 'Error rate exceeded threshold' };
}
if (comparison.latencyDiff > this.config.latencyThreshold) {
return { isHealthy: false, reason: 'Latency degradation detected' };
}
if (comparison.throughputRatio < this.config.throughputThreshold) {
return { isHealthy: false, reason: 'Throughput below threshold' };
}
return { isHealthy: true };
}
}2. A/B Testing Framework
// experiments/ABTestingFramework.ts
export class ABTestingFramework {
private experiments: Map<string, Experiment> = new Map();
private analyticsCollector: AnalyticsCollector;
async createExperiment(config: ExperimentConfig): Promise<Experiment> {
const experiment = new Experiment(config);
// Create feature flag with variants
await this.flagService.createFlag({
key: `experiment.${experiment.id}`,
name: experiment.name,
enabled: true,
variants: config.variants.map((v, index) => ({
id: v.id,
name: v.name,
weight: v.allocation,
payload: v.configuration
}))
});
this.experiments.set(experiment.id, experiment);
return experiment;
}
async getVariant(
experimentId: string,
context: EvaluationContext
): Promise<ExperimentVariant> {
const experiment = this.experiments.get(experimentId);
if (!experiment || !experiment.isActive()) {
return experiment?.control || null;
}
// Get variant from feature flag
const variant = await this.flagService.getVariant(
`experiment.${experimentId}`,
context
);
if (!variant) {
return experiment.control;
}
// Track assignment
await this.analyticsCollector.trackAssignment({
experimentId,
variantId: variant.id,
userId: context.userId,
timestamp: new Date()
});
return {
id: variant.id,
name: variant.name,
configuration: variant.payload
};
}
async analyzeResults(experimentId: string): Promise<ExperimentResults> {
const experiment = this.experiments.get(experimentId);
if (!experiment) {
throw new Error(`Experiment ${experimentId} not found`);
}
// Collect metrics for each variant
const variantMetrics = await Promise.all(
experiment.variants.map(async (variant) => {
const metrics = await this.analyticsCollector.getMetrics({
experimentId,
variantId: variant.id,
metrics: experiment.successMetrics,
startDate: experiment.startDate,
endDate: experiment.endDate || new Date()
});
return { variant, metrics };
})
);
// Statistical analysis
const analysis = await this.performStatisticalAnalysis(variantMetrics);
// Claude interpretation
const interpretation = await this.getClaudeInterpretation(
experiment,
variantMetrics,
analysis
);
return {
experiment,
variantMetrics,
analysis,
interpretation,
recommendation: interpretation.recommendation
};
}
private async performStatisticalAnalysis(
variantMetrics: VariantMetrics[]
): Promise<StatisticalAnalysis> {
// Implement statistical tests (t-test, chi-square, etc.)
// Calculate confidence intervals, p-values, effect sizes
return {
significantDifferences: [],
confidenceIntervals: {},
effectSizes: {},
sampleSizes: {}
};
}
private async getClaudeInterpretation(
experiment: Experiment,
metrics: VariantMetrics[],
analysis: StatisticalAnalysis
): Promise<ExperimentInterpretation> {
const prompt = `Analyze this A/B test experiment:
Experiment: ${experiment.name}
Hypothesis: ${experiment.hypothesis}
Variant Metrics:
${JSON.stringify(metrics, null, 2)}
Statistical Analysis:
${JSON.stringify(analysis, null, 2)}
Please provide:
1. Key findings and insights
2. Statistical significance interpretation
3. Business impact assessment
4. Recommendation (implement winner/continue testing/abandon)
5. Suggested next steps`;
const response = await this.claudeClient.analyze(prompt);
return {
findings: response.findings,
businessImpact: response.businessImpact,
recommendation: response.recommendation,
nextSteps: response.nextSteps
};
}
}3. Intelligent Rollback System
// rollback/IntelligentRollback.ts
export class IntelligentRollbackSystem {
private monitors: Map<string, FeatureMonitor> = new Map();
private rollbackHistory: RollbackEvent[] = [];
async monitorFeature(
flagKey: string,
config: MonitoringConfig
): Promise<void> {
const monitor = new FeatureMonitor(flagKey, config);
monitor.on('anomaly', async (anomaly) => {
await this.handleAnomaly(flagKey, anomaly);
});
monitor.start();
this.monitors.set(flagKey, monitor);
}
private async handleAnomaly(
flagKey: string,
anomaly: Anomaly
): Promise<void> {
// Get Claude's assessment
const assessment = await this.getClaudeAssessment(flagKey, anomaly);
if (assessment.severity === 'critical') {
// Immediate rollback
await this.executeRollback(flagKey, assessment.reason);
} else if (assessment.severity === 'warning') {
// Gradual rollback or alert
if (assessment.recommendedAction === 'gradual-rollback') {
await this.executeGradualRollback(flagKey, assessment);
} else {
await this.alertTeam(flagKey, anomaly, assessment);
}
}
}
private async getClaudeAssessment(
flagKey: string,
anomaly: Anomaly
): Promise<AnomalyAssessment> {
const context = await this.gatherContext(flagKey, anomaly);
const prompt = `Assess this anomaly for feature flag "${flagKey}":
Anomaly Details:
${JSON.stringify(anomaly, null, 2)}
Historical Context:
${JSON.stringify(context, null, 2)}
Please provide:
1. Severity assessment (critical/warning/info)
2. Root cause analysis
3. Recommended action (immediate-rollback/gradual-rollback/monitor/ignore)
4. Reasoning for recommendation`;
const response = await this.claudeClient.analyze(prompt);
return {
severity: response.severity,
reason: response.rootCause,
recommendedAction: response.action,
confidence: response.confidence
};
}
private async executeRollback(
flagKey: string,
reason: string
): Promise<void> {
// Disable feature flag
await this.flagService.updateFlag(flagKey, { enabled: false });
// Record rollback event
const event: RollbackEvent = {
flagKey,
timestamp: new Date(),
reason,
automatic: true,
metrics: await this.captureMetrics(flagKey)
};
this.rollbackHistory.push(event);
// Notify stakeholders
await this.notificationService.send({
type: 'rollback',
severity: 'critical',
flag: flagKey,
reason,
action: 'Feature automatically disabled due to critical anomaly'
});
}
private async executeGradualRollback(
flagKey: string,
assessment: AnomalyAssessment
): Promise<void> {
const currentPercentage = await this.flagService.getRolloutPercentage(flagKey);
let targetPercentage = currentPercentage;
// Gradually reduce percentage
const reductionSteps = [50, 25, 10, 5, 0];
for (const step of reductionSteps) {
if (step >= currentPercentage) continue;
targetPercentage = step;
await this.flagService.updateFlag(flagKey, {
rolloutPercentage: targetPercentage
});
// Monitor for improvement
await this.sleep(assessment.monitoringInterval || 300000); // 5 minutes
const improved = await this.checkImprovement(flagKey, anomaly);
if (improved) {
break; // Stop reduction if metrics improve
}
}
}
}4. Real-time Configuration Updates
// realtime/RealtimeConfigUpdates.ts
export class RealtimeConfigurationSystem {
private websocketServer: WebSocketServer;
private configCache: Map<string, ConfigSnapshot> = new Map();
private subscribers: Map<string, Set<WebSocket>> = new Map();
async initialize(): Promise<void> {
this.websocketServer = new WebSocketServer({ port: 8080 });
this.websocketServer.on('connection', (ws) => {
ws.on('message', async (message) => {
const { type, payload } = JSON.parse(message.toString());
switch (type) {
case 'subscribe':
await this.handleSubscribe(ws, payload);
break;
case 'unsubscribe':
await this.handleUnsubscribe(ws, payload);
break;
}
});
});
// Listen for flag changes
this.flagService.on('flag-updated', async (event) => {
await this.broadcastUpdate(event.flagKey, event.changes);
});
}
private async handleSubscribe(
ws: WebSocket,
payload: { flags: string[]; context: EvaluationContext }
): Promise<void> {
// Store subscription
for (const flagKey of payload.flags) {
if (!this.subscribers.has(flagKey)) {
this.subscribers.set(flagKey, new Set());
}
this.subscribers.get(flagKey)!.add(ws);
}
// Send initial configuration
const config = await this.evaluateFlags(payload.flags, payload.context);
ws.send(JSON.stringify({
type: 'initial-config',
payload: config
}));
// Set up context-aware updates
ws.on('context-update', async (newContext) => {
const updatedConfig = await this.evaluateFlags(
payload.flags,
newContext
);
ws.send(JSON.stringify({
type: 'config-update',
payload: updatedConfig
}));
});
}
private async broadcastUpdate(
flagKey: string,
changes: FlagChanges
): Promise<void> {
const subscribers = this.subscribers.get(flagKey);
if (!subscribers) return;
// Notify all subscribers
for (const ws of subscribers) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'flag-update',
payload: {
flagKey,
changes,
timestamp: new Date()
}
}));
}
}
}
}
// Client SDK
export class FeatureFlagClient {
private ws: WebSocket;
private flags: Map<string, boolean> = new Map();
private variants: Map<string, Variant> = new Map();
private callbacks: Map<string, Set<Function>> = new Map();
async connect(url: string, context: EvaluationContext): Promise<void> {
this.ws = new WebSocket(url);
this.ws.on('open', () => {
this.subscribe(Array.from(this.callbacks.keys()), context);
});
this.ws.on('message', (data) => {
const { type, payload } = JSON.parse(data.toString());
switch (type) {
case 'initial-config':
case 'config-update':
this.updateLocalFlags(payload);
break;
case 'flag-update':
this.handleFlagUpdate(payload);
break;
}
});
}
isEnabled(flagKey: string): boolean {
return this.flags.get(flagKey) || false;
}
getVariant(flagKey: string): Variant | null {
return this.variants.get(flagKey) || null;
}
onChange(flagKey: string, callback: (enabled: boolean) => void): void {
if (!this.callbacks.has(flagKey)) {
this.callbacks.set(flagKey, new Set());
}
this.callbacks.get(flagKey)!.add(callback);
}
private updateLocalFlags(config: FlagConfiguration): void {
for (const [key, value] of Object.entries(config.flags)) {
const oldValue = this.flags.get(key);
this.flags.set(key, value.enabled);
if (value.variant) {
this.variants.set(key, value.variant);
}
// Notify callbacks if value changed
if (oldValue !== value.enabled) {
const callbacks = this.callbacks.get(key);
if (callbacks) {
callbacks.forEach(cb => cb(value.enabled));
}
}
}
}
}Testing Strategies
1. Feature Flag Testing
// tests/FeatureFlagTesting.ts
import { FeatureFlagTestHarness } from '../testing/FeatureFlagTestHarness';
describe('Feature Flag Integration', () => {
let harness: FeatureFlagTestHarness;
beforeEach(() => {
harness = new FeatureFlagTestHarness();
});
it('should respect targeting rules', async () => {
// Create flag with targeting
await harness.createFlag({
key: 'premium-feature',
rules: [{
attribute: 'plan',
operator: 'equals',
values: ['premium', 'enterprise']
}]
});
// Test different contexts
expect(await harness.evaluate('premium-feature', {
userId: 'user1',
attributes: { plan: 'free' }
})).toBe(false);
expect(await harness.evaluate('premium-feature', {
userId: 'user2',
attributes: { plan: 'premium' }
})).toBe(true);
});
it('should handle percentage rollouts consistently', async () => {
await harness.createFlag({
key: 'new-feature',
rolloutPercentage: 50
});
// Test consistency
const userId = 'consistent-user';
const firstEval = await harness.evaluate('new-feature', { userId });
// Same user should always get same result
for (let i = 0; i < 10; i++) {
const result = await harness.evaluate('new-feature', { userId });
expect(result).toBe(firstEval);
}
});
it('should support variant distribution', async () => {
await harness.createFlag({
key: 'experiment',
variants: [
{ id: 'control', name: 'Control', weight: 0.5 },
{ id: 'variant-a', name: 'Variant A', weight: 0.3 },
{ id: 'variant-b', name: 'Variant B', weight: 0.2 }
]
});
// Test distribution
const distribution = await harness.testVariantDistribution(
'experiment',
10000
);
expect(distribution['control']).toBeCloseTo(0.5, 1);
expect(distribution['variant-a']).toBeCloseTo(0.3, 1);
expect(distribution['variant-b']).toBeCloseTo(0.2, 1);
});
});2. Rollout Testing
// tests/RolloutTesting.ts
describe('Progressive Rollout', () => {
let rolloutSimulator: RolloutSimulator;
beforeEach(() => {
rolloutSimulator = new RolloutSimulator();
});
it('should handle rollback on error spike', async () => {
const rollout = await rolloutSimulator.createRollout({
stages: [
{ percentage: 10, duration: 60000, errorThreshold: 0.05 },
{ percentage: 50, duration: 60000, errorThreshold: 0.05 },
{ percentage: 100, duration: 60000, errorThreshold: 0.05 }
]
});
// Simulate error spike in stage 2
rolloutSimulator.on('stage-started', (stage) => {
if (stage.index === 1) {
rolloutSimulator.injectErrors({ rate: 0.1 }); // 10% error rate
}
});
await expect(rollout.execute()).rejects.toThrow(/Rollout failed/);
expect(rollout.getCurrentPercentage()).toBe(0); // Should rollback
});
it('should accelerate on positive metrics', async () => {
const rollout = await rolloutSimulator.createRollout({
stages: [
{
percentage: 10,
duration: 300000, // 5 minutes
canAccelerate: true,
accelerationCriteria: {
errorRate: { max: 0.01 },
latency: { p99: 100 },
customMetric: { min: 0.95 }
}
}
]
});
// Simulate excellent metrics
rolloutSimulator.setMetrics({
errorRate: 0.005,
latency: { p99: 50 },
customMetric: 0.98
});
const startTime = Date.now();
await rollout.execute();
const duration = Date.now() - startTime;
// Should complete faster than planned duration
expect(duration).toBeLessThan(300000);
});
});Monitoring and Analytics
1. Feature Flag Analytics
// analytics/FeatureFlagAnalytics.ts
export class FeatureFlagAnalytics {
private metricsCollector: MetricsCollector;
private eventStore: EventStore;
async trackEvaluation(
flagKey: string,
context: EvaluationContext,
result: boolean,
variant?: Variant
): Promise<void> {
const event: EvaluationEvent = {
type: 'flag-evaluation',
flagKey,
userId: context.userId,
sessionId: context.sessionId,
result,
variant: variant?.id,
context: context.attributes,
timestamp: new Date()
};
await this.eventStore.store(event);
// Update metrics
this.metricsCollector.increment(`flag.${flagKey}.evaluations`);
this.metricsCollector.increment(`flag.${flagKey}.${result ? 'enabled' : 'disabled'}`);
if (variant) {
this.metricsCollector.increment(`flag.${flagKey}.variant.${variant.id}`);
}
}
async generateReport(
flagKey: string,
timeRange: TimeRange
): Promise<FlagAnalyticsReport> {
const events = await this.eventStore.query({
type: 'flag-evaluation',
flagKey,
timeRange
});
const report: FlagAnalyticsReport = {
flagKey,
timeRange,
totalEvaluations: events.length,
uniqueUsers: new Set(events.map(e => e.userId)).size,
enabledPercentage: events.filter(e => e.result).length / events.length,
variantDistribution: this.calculateVariantDistribution(events),
evaluationTrend: this.calculateTrend(events),
userSegments: await this.analyzeUserSegments(events)
};
// Get Claude insights
report.insights = await this.getClaudeInsights(report);
return report;
}
private async getClaudeInsights(
report: FlagAnalyticsReport
): Promise<AnalyticsInsights> {
const prompt = `Analyze this feature flag usage report:
${JSON.stringify(report, null, 2)}
Please provide:
1. Usage patterns and trends
2. Adoption rate analysis
3. User segment insights
4. Recommendations for optimization
5. Potential issues or concerns`;
const response = await this.claudeClient.analyze(prompt);
return {
patterns: response.patterns,
adoptionAnalysis: response.adoption,
segmentInsights: response.segments,
recommendations: response.recommendations,
concerns: response.concerns
};
}
}2. Performance Monitoring
// monitoring/PerformanceMonitoring.ts
export class FeatureFlagPerformanceMonitor {
private performanceObserver: PerformanceObserver;
async monitorFlagPerformance(): Promise<void> {
// Track evaluation latency
this.performanceObserver = new PerformanceObserver((entries) => {
for (const entry of entries.getEntries()) {
if (entry.name.startsWith('flag-evaluation:')) {
const flagKey = entry.name.split(':')[1];
this.metricsCollector.histogram(
`flag.${flagKey}.evaluation.duration`,
entry.duration
);
// Alert on slow evaluations
if (entry.duration > 50) { // 50ms threshold
this.alerting.warn({
type: 'slow-flag-evaluation',
flagKey,
duration: entry.duration,
threshold: 50
});
}
}
}
});
this.performanceObserver.observe({ entryTypes: ['measure'] });
}
async measureFlagEvaluation<T>(
flagKey: string,
evaluation: () => Promise<T>
): Promise<T> {
const startMark = `flag-start:${flagKey}:${Date.now()}`;
const endMark = `flag-end:${flagKey}:${Date.now()}`;
performance.mark(startMark);
try {
const result = await evaluation();
performance.mark(endMark);
performance.measure(`flag-evaluation:${flagKey}`, startMark, endMark);
return result;
} catch (error) {
performance.mark(endMark);
performance.measure(`flag-evaluation-error:${flagKey}`, startMark, endMark);
throw error;
}
}
}Best Practices
-
Flag Naming Convention
- Use hierarchical naming:
product.feature.subfeature - Include purpose:
experiment.checkout.one-click - Avoid generic names
- Use hierarchical naming:
-
Lifecycle Management
- Set expiration dates for temporary flags
- Clean up obsolete flags regularly
- Document flag purpose and ownership
-
Testing Strategy
- Test all flag variations
- Include flag states in integration tests
- Use flag overrides for testing
-
Monitoring and Alerts
- Monitor flag evaluation performance
- Track business metrics per flag state
- Set up anomaly detection
-
Progressive Rollout
- Start with internal users
- Monitor key metrics at each stage
- Have clear rollback criteria
Common Pitfalls
- Flag Debt: Accumulating unused flags
- Complex Dependencies: Flags depending on other flags
- Missing Context: Not tracking enough data for decisions
- Poor Performance: Not caching flag evaluations
- Inconsistent Experience: Users seeing different states
References
- Feature Flag Best Practices
- Progressive Delivery Guide
- Canary Deployments
- A/B Testing Statistics
- Microsoft Feature Management