Cosa sono i feature flag: deploy e rilascio in produzione
Riassunto
I feature flag sono condizionali nel codice che separano il deployment dal rilascio in produzione. Permettono rollout progressivi con SLO gate, isolando il blast radius a percentuali di traffico controllate. Esistono quattro tipi: release, experiment, ops e permission. Un kill switch ben progettato valuta localmente in meno di 50ms. Il flag debt, non gestito, diventa un rischio operativo attivo oltre i 500 flag.
Cosa sono i feature flag: deploy e rilascio in produzione
I feature flag sono condizionali nel codice dell'applicazione che controllano quali percorsi di esecuzione vengono attivati per un determinato utente, sessione o ambiente, senza necessità di un nuovo build. Un flag chiamato new_checkout_flow impostato su false per il 99% degli utenti significa che hai deployato quel codice due settimane fa: non lo hai ancora attivato. Il contratto che stabiliscono è preciso: il deployment e il rilascio diventano due eventi separati. Per qualsiasi team che fa continuous deployment a scala, questo contratto è fondamentale.
Deploy e rilascio non sono lo stesso evento
La maggior parte dei team impara questa distinzione dopo un rollout problematico. Una feature viene rilasciata martedì pomeriggio, qualcosa nella trace delle richieste inizia a comportarsi diversamente mercoledì mattina, e quando qualcuno apre un'indagine il diff copre quattro commit e due confini di servizio. Attribuire la causalità in quello scenario è genuinamente difficile.
I feature flag impongono precisione. Quando il codice dietro un flag viene mergiato e deployato, rimane inerte in produzione. Confermi che il deployment è riuscito, osservi le metriche baseline per qualche ora o qualche giorno, e verifichi che nulla sia degradato. Poi attivi il flag per l'1% del traffico. Ora hai una singola variabile attribuibile. Se il tasso di errore sale sul percorso con il flag attivo, non stai debuggando un diff del codice: stai toggling un valore di configurazione.
Questa separazione non è principalmente un argomento di velocità. È un argomento di blast radius. Un rilascio che colpisce l'1% delle sessioni e va storto è recuperabile in minuti. Un rilascio che colpisce il 100% delle sessioni e va storto è un incident grave.
Il meccanismo è diretto. In TypeScript, una valutazione di flag si legge così:
const showNewCheckoutFlow = flagClient.variation(
'new_checkout_flow',
{ userKey: session.userId, custom: { plan: user.plan } },
false // default se il servizio di flag non è raggiungibile
);
if (showNewCheckoutFlow) {
return renderNewFlow(cart);
}
return renderLegacyFlow(cart);Il valore di default false non è una formalità. È il comportamento che ricevono gli utenti se il servizio di valutazione del flag subisce una network partition. Definiscilo intenzionalmente, non per accidente.
I quattro tipi di flag che contano davvero in produzione
Non tutti i feature flag servono lo stesso scopo operativo. Trattarli in modo identico nel codebase e negli strumenti di gestione è un percorso affidabile verso confusione durante gli incident e accumulo di flag debt.
Release flag: presidiano le nuove feature durante lo sviluppo e il rollout. Sono temporanei per definizione: creati quando inizia il lavoro su un feature branch, rimossi una volta che la feature raggiunge il 100% degli utenti e il team ha confermato la stabilità. Release flag senza data di rimozione e senza un owner diventano arredamento permanente nel codebase.
Experiment flag: alimentano A/B test e esperimenti multivariati. Sono legati a identificatori di coorte analitici e il loro ciclo di vita è delimitato dall'esperimento. Quando l'esperimento si conclude, il flag va con esso. L'errore comune è tenere la variante vincente dietro il flag a tempo indefinito, ragionando che la rimozione "non è urgente." Due anni dopo, il flag dell'esperimento fa parte del critical path e nessuno ricorda quale variante è attiva.
Ops flag: sono kill switch e circuit breaker. A differenza dei release flag, sono progettati come infrastruttura permanente. Un flag disable_ml_recommendations che ti permette di bypassare un layer ML lento quando il suo SLO degrada è qualcosa che vuoi disponibile alle 3 di notte senza leggere documentazione. Questi flag devono valutarsi localmente, avere un fallback ben documentato, e devono essere testati regolarmente in condizioni normali.
Permission flag: controllano l'accesso in base al tier utente, al piano account o alla coorte beta. Sono longevi per design. Il rischio di confusione: i permission flag spesso assomigliano ai release flag per un lettore che non conosce la storia. Una naming convention chiara conta più qui che altrove.

Un rollout percentuale senza SLO gate è solo un deployment lento
Qui si fermano la maggior parte delle implementazioni di feature flag. Il team di piattaforma configura un calendario di rollout: 1% lunedì, 5% martedì, 25% mercoledì, 100% venerdì. Lo documentano, lo condividono con gli stakeholder, e lo chiamano progressive delivery.
Ma "progressivo" senza una condizione di validazione a ogni step è rischio differito, non rischio ridotto. Il controllo percentuale gestisce l'esposizione. Non valida la sicurezza.
Ciò che rende uno staged rollout operativamente significativo è lo SLO gate tra ogni fase. Prima di avanzare dal 5% al 25%, qualcosa deve rispondere: il tasso di errore sul percorso con il flag attivo rientra nel budget SLO? La latenza p99 si mantiene nella stessa fascia del gruppo di controllo? Il budget degli errori sta bruciando più velocemente della baseline?
Se nessuna di queste domande è strumentata, il calendario di rollout è una timeline, non un loop di validazione.
Una configurazione che regge in produzione: definisci due finestre di valutazione SLO. Una finestra breve (15 minuti) intercetta i failure rapidi: una query al database problematica, un mismatch di schema, una regressione in un critical path. Una finestra più lunga (24 ore o un ciclo di traffico completo) intercetta la degradazione graduale: memory leak, pressione sulla cache, edge case in segmenti di traffico a bassa frequenza. Richiedi che entrambe le finestre mostrino verde prima di qualsiasi avanzamento. Se una finestra viene violata, blocca il rollout e sveglia l'on-call.
Il percentuale è un selettore. La finestra SLO è il gate. Entrambi sono necessari.
Kill switch: progettalo prima di averne bisogno
Il kill switch non è un fallback. È una decisione di design di prima classe che dovrebbe esistere prima che venga scritta la prima riga di codice della feature.
Un kill switch progettato sotto pressione è un kill switch con ipotesi non esaminate. Lo stai testando per la prima volta durante un incident attivo, in una sessione di terminale aperta da una notifica PagerDuty, con cinque persone che guardano un thread Slack. Questo è il momento peggiore possibile per scoprire che il tuo ops flag fa targeting degli utenti per session ID e che il tuo servizio di sessione è attualmente degradato.

Tre proprietà non negoziabili per qualsiasi kill switch destinato all'uso in produzione:
Valutazione locale sotto i 50ms: il controllo del flag non può essere una chiamata di rete verso un servizio di valutazione remoto. Se la valutazione dipende da un servizio che potrebbe essere degradato, hai una dipendenza circolare nel tuo percorso di risposta agli incident.
Valore di fallback esplicito: cosa restituisce il flag quando il servizio di flag non è raggiungibile? Deve essere documentato, definito nel codice e testato. "Qualunque cosa sia il default dell'SDK" non è una risposta.
Testato sotto failure di dipendenze: i kill switch devono far parte della tua rotazione di chaos engineering. Validali in scenari in cui l'auth è degradato, in cui il servizio di flag stesso è down, e in cui la latenza di rete verso l'endpoint di valutazione supera i 2 secondi.
I team che eseguono la risposta agli incident in modo pulito sono quelli che si sono esercitati. Il kill switch fa parte del runbook. Rendilo noioso.
Flag debt: il debito tecnico che nessuno mette nel roadmap
I team che adottano i feature flag in modo aggressivo spesso accumulano quello che viene chiamato flag debt: flag che hanno completato il loro scopo ma non sono mai stati rimossi. La feature è stata rilasciata, l'esperimento si è concluso, la beta è finita. Il flag è rimasto.
A 50 flag, questo è un fastidio minore. A 500 flag in un sistema distribuito, è un rischio operativo attivo. Ogni flag è un branch del codice che richiede manutenzione, test e comprensione durante le indagini sugli incident. Uno sviluppatore che cerca di capire un failure alle 2 di notte non vuole tracciare attraverso 40 branch condizionali per trovare quello rilevante.
Un pattern osservato in più team di piattaforma: il middleware di valutazione dei flag appare tra i top 5 stack frame per la latenza p99. La causa nella maggior parte dei casi è flag obsoleti con regole di targeting complesse che valutano decine di condizioni per richiesta, portando il peso di decisioni prese due anni prima che nessuno si è sentito a proprio agio a eliminare.
La contromisura è organizzativa piuttosto che tecnica. Ogni flag creato dovrebbe avere tre attributi: un owner, un tipo (release, experiment, ops, permission) e una data di rimozione prevista. I release flag dovrebbero essere rimossi entro due sprint dal rollout completo. I flag degli esperimenti dovrebbero essere rimossi quando l'esperimento si conclude. L'inventario dei flag dovrebbe essere verificabile su richiesta e visibile nei dashboard di engineering health.
Granularità del targeting: la dimensione che si rompe a scala enterprise
La maggior parte delle piattaforme di feature flag supporta rollout basati su percentuale e targeting di base per attributi utente. Il gap che emerge a scala enterprise è la granularità del targeting: la capacità di esprimere regole di rollout abbastanza specifiche da essere utili senza diventare così complesse da essere non manutenibili.
Una gerarchia di targeting utile per i rollout in produzione a livello di team di piattaforma:
Livello ambiente: produzione, staging, preview. Il primo gate, non l'unico.
Segmento infrastrutturale: data center, availability zone o cluster Kubernetes. Utile per isolare il blast radius geografico.
Account o tenant: per le piattaforme SaaS B2B, fare il rollout per account è spesso più sicuro che per percentuale di utenti, perché puoi osservare il pattern di traffico di un intero account piuttosto che un campione statistico.
Coorte utente: beta user, utenti interni, power user per tier di attività.
Attributo di sessione: utile per gli experiment flag, pericoloso per gli ops flag.
Le piattaforme che lo implementano bene (LaunchDarkly e Statsig sono le più citate dai team SRE che lo fanno a scala) consentono una composizione di regole complesse senza richiedere tempo di engineering per modificare la logica di targeting durante un rollout attivo. Questa capacità di self-service è la differenza tra una pausa del rollout di 30 secondi e un ticket al team di piattaforma.
Cosa continua a trovare il post-mortem
Ogni post-mortem per un incident in produzione legato a una feature pone lo stesso insieme di domande. La feature era gated dietro un flag? Il rollout era progressivo? C'era una condizione di validazione tra le fasi? Il kill switch era stato testato prima dell'incident?
Se tutte e quattro le risposte sono sì, l'incident è un problema di calibrazione: soglie impostate troppo largamente, regole di targeting con un edge case, comportamento di valutazione dell'SDK in caso di network partition non preso in considerazione. Questi sono risolvibili con modifiche alla configurazione e aggiornamenti del runbook.
Se una risposta è no, l'incident è un problema architetturale. I feature flag non sono una comodità di debugging. Sono una decisione di architettura del deployment. Quella decisione esiste prima che la feature venga rilasciata, oppure non esiste affatto.
La domanda che vale la pena rispondere prima del prossimo rilascio: cosa direbbe il post-mortem su questo rollout se qualcosa andasse storto stanotte?