🚦 Feature Flags
Como o hash determinístico distribui % de tráfego entre sistemas
O feature flag não roteia — ele toma a decisão. O proxy (YARP) executa o roteamento. São duas responsabilidades separadas.
Fluxo de decisão do hash
Como o % é calculado
O hash transforma qualquer GUID em um número de 0 a 99. A decisão é feita comparando esse número com o threshold.
// Passo a passo do hash
var userId = Guid.NewGuid();
// 1. GUID → 16 bytes
var bytes = userId.ToByteArray();
// ex: [0x55, 0x0e, 0x84, 0x00, ...]
// 2. SHA256 → 32 bytes (determinístico em qualquer máquina)
var hash = SHA256.HashData(bytes);
// ex: [0x3A, 0xFF, 0xF9, 0x3A, 0xBB, ...]
// 3. Primeiros 4 bytes → uint (sem sinal: nunca negativo,
// sem o edge case Math.Abs(int.MinValue) → OverflowException)
var raw = BitConverter.ToUInt32(hash, 0);
// ex: 989.462.330
// 4. Módulo → 0..99
var hashValue = (int)(raw % 100);
// ex: 30
// 5. Decisão
bool goesToNew = hashValue < thresholdPercent; // 30 < 20 → false → Legacy
// ⚠️ ToUInt32 % 100 e Math.Abs(ToInt32) % 100 NÃO são equivalentes:
// divergem em ~metade dos hashes (bit de sinal). Trocar a fórmula no
// meio do rollout re-bucketiza metade dos usuários — quebra o sticky.
// Escolha uma fórmula antes do primeiro % e congele.
O operador % (módulo)
O módulo retorna o resto da divisão. Qualquer número não-negativo dividido por 100 tem resto entre 0 e 99. Em C#, % de int negativo dá resto negativo (-7 % 100 == -7) — por isso o código lê os bytes como uint (sem sinal, sem Math.Abs, sem o edge case de Math.Abs(int.MinValue) estourar).
| Expressão | Divisão | Resto (resultado) | Abaixo de 20? |
|---|---|---|---|
7 % 100 | 7 ÷ 100 = 0 resto | 7 | ✓ New System |
150 % 100 | 150 ÷ 100 = 1 resto | 50 | ✗ Legacy |
989462330 % 100 | 989462330 ÷ 100 = ... resto | 30 | ✗ Legacy |
1000 % 100 | 1000 ÷ 100 = 10 resto | 0 | ✓ New System |
19 % 100 | 19 ÷ 100 = 0 resto | 19 | ✓ New System |
20 % 100 | 20 ÷ 100 = 0 resto | 20 | ✗ Legacy |
Por que SHA256 e não GetHashCode()?
GetHashCode() não é determinístico entre máquinas ou versões do .NET. Dois servidores podem calcular resultados diferentes para o mesmo GUID. O mesmo usuário cairia em sistemas diferentes a cada request.
| Método | Determinístico? | Entre máquinas? | Usar em produção? |
|---|---|---|---|
GetHashCode() | Dentro do processo | Não | Não |
SHA256.HashData() | Sempre | Sim | Sim |
| LaunchDarkly / Azure AppConfig | Sempre | Sim | Recomendado |
❓ Por que o hash garante sticky behavior sem cookie ou sessão?
SHA256 é uma função pura — o mesmo input sempre produz o mesmo output. userId A sempre vai gerar o mesmo hashValue, então sempre vai pro mesmo sistema, sem precisar armazenar nada no cliente ou no servidor. É determinismo matemático.