Déployer un vendredi soir ? Avec les Feature Flags, c’est possible. Voici comment nous avons éliminé 90% du stress de déploiement.
Le problème traditionnel
Déploiement = Release
git push → CI/CD → Deploy prod → 🤞
Si bug : rollback complet
→ redéploy entier
→ 15-30 minutes downtime
Résultat :
- Déploiements le mardi matin uniquement
- Freeze 2 jours avant weekend
- Stress maximum
Avec Feature Flags
git push → CI/CD → Deploy prod (feature OFF)
→ Test interne (feature ON pour admins)
→ Rollout 5% users
→ 100% users
Si bug : Toggle flag OFF (instantané)
Résultat :
- Deploy n’importe quand
- Rollback < 1 seconde
- Zero stress
Types de Feature Flags
1. Release Flags (temporaires)
// Nouvelle feature en développement
if (featureFlags.isEnabled('new-checkout-flow')) {
return <NewCheckoutFlow />;
}
return <OldCheckoutFlow />;
// Supprimer le flag après rollout complet (2-4 semaines)
Usage : Deploy code incomplet en prod.
2. Ops Flags (permanents)
// Kill switch en cas d'incident
if (featureFlags.isEnabled('enable-recommendations')) {
return await fetchRecommendations(); // Coûteux
}
return []; // Fallback si service recommendations down
Usage : Circuit breakers, kill switches.
3. Experiment Flags (A/B testing)
// A/B test sur pricing
const variant = featureFlags.getVariant('pricing-test');
if (variant === 'premium') {
return <PremiumPricing />;
} else if (variant === 'standard') {
return <StandardPricing />;
}
Usage : Optimisation produit.
4. Permission Flags
// Features par tier abonnement
if (user.tier === 'premium' && featureFlags.isEnabled('advanced-analytics')) {
return <AdvancedAnalytics />;
}
Usage : Monétisation, beta testing.
Architecture Feature Flags
Setup simple (localStorage)
// feature-flags.ts
export class FeatureFlags {
private flags: Record<string, boolean> = {
'new-dashboard': false,
'dark-mode': true,
};
isEnabled(flag: string): boolean {
// Override local pour dev
const localOverride = localStorage.getItem(`flag:${flag}`);
if (localOverride !== null) {
return localOverride === 'true';
}
return this.flags[flag] ?? false;
}
}
export const featureFlags = new FeatureFlags();
Pros : Simple, zero dépendance Cons : Pas de rollout progressif, pas de targeting
Setup avancé (LaunchDarkly, Unleash)
// feature-flags.ts
import { LDClient } from 'launchdarkly-js-client-sdk';
export class FeatureFlags {
private client: LDClient;
async init() {
this.client = LDClient.initialize('sdk-key', {
key: user.id,
email: user.email,
custom: {
tier: user.subscriptionTier,
country: user.country
}
});
await this.client.waitForInitialization();
}
isEnabled(flag: string): boolean {
return this.client.variation(flag, false);
}
getVariant(flag: string): string {
return this.client.variation(flag, 'control');
}
}
Pros : Rollout progressif, targeting, analytics Cons : Coût (LaunchDarkly : $50+/user/mois)
Setup open-source (Unleash)
// Unleash : Self-hosted, gratuit
import { UnleashClient } from 'unleash-proxy-client';
const unleash = new UnleashClient({
url: 'https://unleash.mycompany.com/proxy',
clientKey: 'proxy-secret',
appName: 'my-app',
});
await unleash.start();
if (unleash.isEnabled('new-feature')) {
// Feature code
}
Pros : Gratuit, open-source, self-hosted Cons : Infrastructure à maintenir
Rollout progressif : La stratégie
Étape 1 : Admins only (Jour 0)
featureFlags.configure('new-dashboard', {
enabled: true,
targeting: {
userIds: ['admin1', 'admin2']
}
});
Objectif : Valider la feature en prod réel.
Étape 2 : Équipe interne (Jour 1-2)
targeting: {
custom: {
email: { endsWith: '@mycompany.com' }
}
}
Objectif : Dogfooding, récolter feedback.
Étape 3 : Beta users (Jour 3-5)
targeting: {
custom: {
betaTester: true
}
},
rollout: 100 // 100% des beta testers
Objectif : Valider sur early adopters.
Étape 4 : Canary (Jour 6-10)
rollout: 5 // 5% de tous les users
Objectif : Détecter bugs avec vraie charge.
Monitoring critique :
- Error rate < 0.1%
- Latency p95 < baseline +10%
- Conversions maintenues
Étape 5 : Rollout progressif (Jour 11-20)
Jour 11 : 10%
Jour 13 : 25%
Jour 15 : 50%
Jour 17 : 75%
Jour 20 : 100%
Critère d’avancement : Toutes métriques OK pendant 48h.
Étape 6 : Cleanup (Jour 30)
// Supprimer le flag du code
- if (featureFlags.isEnabled('new-dashboard')) {
- return <NewDashboard />;
- }
- return <OldDashboard />;
+ return <NewDashboard />;
Important : Ne pas accumuler de dette technique.
Rollback instantané
Incident détecté
12:34 : Deploy new-payment-flow (10% users)
12:42 : Spike errors +500%
12:43 : Toggle flag OFF (via dashboard)
12:43:05 : Errors retour à la normale
MTTR : 5 secondes (vs 15-30min rollback traditionnel)
Automated rollback
// Monitoring auto-rollback
const metrics = await getMetrics('new-payment-flow');
if (metrics.errorRate > 0.5) { // > 0.5%
await featureFlags.disable('new-payment-flow');
await alert.page(oncall, 'Auto-rollback triggered');
}
Patterns avancés
Pattern 1 : Rampe de montée automatique
// Rollout automatique si métriques OK
const rolloutSchedule = [
{ day: 1, percentage: 5 },
{ day: 3, percentage: 25 },
{ day: 5, percentage: 50 },
{ day: 7, percentage: 100 },
];
for (const stage of rolloutSchedule) {
await wait(stage.day);
const metrics = await getMetrics();
if (metrics.errorRate < threshold) {
await featureFlags.setRollout('feature', stage.percentage);
} else {
await featureFlags.disable('feature');
break;
}
}
Pattern 2 : Targeting géographique
// Rollout par région
featureFlags.configure('new-feature', {
targeting: {
custom: {
country: { in: ['FR', 'BE', 'CH'] } // DACH d'abord
}
}
});
// 1 semaine plus tard : élargir
country: { in: ['FR', 'BE', 'CH', 'DE', 'AT', 'NL'] }
Pattern 3 : Ring deployment
// Rings : Dev → Staging → Prod
const rings = [
{ env: 'dev', users: ['all'] },
{ env: 'staging', users: ['internal'] },
{ env: 'prod', users: ['beta'] },
{ env: 'prod', rollout: 10 },
{ env: 'prod', rollout: 100 },
];
Métriques et monitoring
Dashboard Feature Flags
┌─────────────────────────────────────┐
│ Feature: new-checkout-flow │
│ Status: 🟡 Rolling out (25%) │
├─────────────────────────────────────┤
│ Error rate: 0.12% (↓ vs baseline) │
│ Latency p95: 245ms (↑ +12ms) │
│ Conversion: 3.2% (↑ +0.3%) │
│ │
│ [Increase to 50%] [Rollback] │
└─────────────────────────────────────┘
Alertes automatiques
# Prometheus alert
- alert: FeatureFlagHighErrorRate
expr: |
feature_flag_error_rate{flag="new-feature"} > 0.5
for: 5m
annotations:
summary: "Feature flag {{ $labels.flag }} error rate high"
action: "Consider rolling back via dashboard"
Analytics impact business
-- Mesurer impact conversion
SELECT
CASE
WHEN feature_flag = 'new-checkout'
THEN 'new'
ELSE 'old'
END AS variant,
COUNT(*) as sessions,
SUM(converted) as conversions,
SUM(converted) / COUNT(*) as conversion_rate
FROM sessions
WHERE date > NOW() - INTERVAL '7 days'
GROUP BY variant;
Erreurs à éviter
Erreur 1 : Trop de flags
// ❌ Flags qui durent 6+ mois
if (featureFlags.isEnabled('old-feature-from-2024')) {
// Debt technique
}
Solution : Cleanup automatique après 30 jours.
Erreur 2 : Nested flags
// ❌ Flags imbriqués = cauchemar
if (featureFlags.isEnabled('feature-a')) {
if (featureFlags.isEnabled('feature-b')) {
if (featureFlags.isEnabled('feature-c')) {
// Quelle combinaison est testée ???
}
}
}
Solution : 1 flag = 1 feature indépendante.
Erreur 3 : Pas de fallback
// ❌ Si service Feature Flags down = app crash
const isEnabled = await featureFlags.isEnabled('feature');
// ✅ Fallback si service down
const isEnabled = await featureFlags
.isEnabled('feature')
.catch(() => false); // Safe default
Erreur 4 : Feature Flags en database
-- ❌ Query DB pour chaque check
SELECT enabled FROM feature_flags WHERE name = 'feature';
Solution : Cache en mémoire, refresh périodique.
Coûts et ROI
Solutions Feature Flags
LaunchDarkly (SaaS) :
- $50-$100/utilisateur/mois
- Toutes features
- Support 24/7
Unleash (Open Source) :
- Gratuit (self-hosted)
- Infrastructure : $50-$200/mois
- Maintenance : 0.2 ETP
Custom (fait maison) :
- Dev : 2-3 semaines
- Maintenance : 0.1 ETP
ROI mesuré
Avant Feature Flags :
- Incidents/mois : 8
- MTTR moyen : 25 minutes
- Coût incidents : ~$50k/an
Après Feature Flags :
- Incidents/mois : 2
- MTTR moyen : 30 secondes
- Économie : ~$35k/an
ROI : Payé en 3 mois (même avec LaunchDarkly).
Getting started
Semaine 1 : Setup
# Installer Unleash (Docker)
docker run -d -p 4242:4242 unleashorg/unleash-server
# Ou LaunchDarkly
npm install launchdarkly-js-client-sdk
Semaine 2 : Premier flag
// Feature simple, low-risk
if (featureFlags.isEnabled('new-footer')) {
return <NewFooter />;
}
return <OldFooter />;
Semaine 3-4 : Rollout
Jour 1 : Admins
Jour 3 : Interne
Jour 5 : 5% users
Jour 7 : 25% users
Jour 10 : 100% users
Jour 15 : Cleanup flag
Semaine 5+ : Généraliser
Systématiser pour toutes nouvelles features.
Conclusion
Les Feature Flags transforment le déploiement :
Avant :
- Deploy = stress
- Rollback = 15-30min
- Testing en prod = impossible
Après :
- Deploy = routine
- Rollback = 1 seconde
- Testing en prod = safe
Investissement minimal, impact maximum.
Commencez petit :
- Setup Unleash (gratuit)
- Tester sur 1 feature low-risk
- Mesurer l’impact
- Généraliser
Et vous, déjà des Feature Flags en prod ?