Knightmare: um alerta de DevOps (2014)
(dougseven.com)- A Knight Capital Group, um dos maiores pilares da negociação de ações nos EUA, foi levada à beira da falência em 1º de agosto de 2012 após uma falha de implantação do SMARS causar uma perda de 460 milhões de dólares em 45 minutos
- O incidente começou durante a adaptação ao Retail Liquidity Program da NYSE, quando um sinalizador do código Power Peg, sem uso havia 8 anos, foi reutilizado em uma nova funcionalidade
- O novo código foi implantado em apenas 7 dos 8 servidores, e quando o servidor restante recebeu uma nova ordem RLP, a funcionalidade inativa do Power Peg voltou a ser executada
- O Power Peg continuou roteando ordens-filhas sem rastrear o volume executado da ordem-pai, e a Knight teve de descobrir a causa em produção sem um kill switch nem um procedimento de resposta documentado
- Implantação é tão importante quanto escrever e testar código, e implantações que dependem de processos manuais se tornam um risco operacional fatal sem automação, repetibilidade e validação
A empresa de trading de alta velocidade que ruiu em 45 minutos
- A Knight Capital Group era uma empresa americana de serviços financeiros que atuava com market making, execução eletrônica e vendas institucionais/trading
- Em 2012, a Knight era a maior trader de ações dos EUA, com cerca de 17% de participação de mercado tanto na NYSE quanto na NASDAQ
- O Electronic Trading Group (ETG) da Knight administrava em média mais de 3,3 bilhões de negociações por dia e negociava mais de 21 bilhões de dólares diariamente
- Em 31 de julho de 2012, a Knight possuía aproximadamente 365 milhões de dólares em caixa e equivalentes de caixa
Atualização do SMARS para atender ao RLP
- A NYSE planejava iniciar o Retail Liquidity Program em 1º de agosto de 2012
- A Knight atualizou o SMARS, um roteador algorítmico automático de alta velocidade que enviava ordens ao mercado, para acompanhar essa mudança
- O SMARS era um sistema que recebia uma “ordem-pai” da plataforma de negociação e a executava dividindo-a em uma ou mais “ordens-filhas”
- Quanto maior a ordem-pai, mais ordens-filhas eram geradas
- Essa atualização pretendia substituir um código do Power Peg que não era usado havia 8 anos
- O novo código reutilizou o sinalizador antigo que ativava o Power Peg para a nova funcionalidade
- O código em si foi testado de forma suficiente e teve seu funcionamento normal confirmado
Um servidor ficou de fora na implantação manual
- De 27 a 31 de julho de 2012, a Knight fez a implantação manual do novo software em um número limitado de servidores por dia, com 8 máquinas no total como alvo
- Segundo documentos da SEC, um técnico não copiou o novo código para um dos 8 servidores do SMARS
- Não havia um procedimento em que um segundo técnico revisasse essa implantação, nem um procedimento documentado exigindo essa revisão
- Como resultado, no 8º servidor o código do Power Peg não foi removido e o novo código do RLP também não foi adicionado
Como um código morto voltou à vida
- Em 1º de agosto de 2012, às 9h30 da manhã (horário da costa leste dos EUA), com a abertura do mercado, a Knight começou a processar ordens de clientes corretoras-distribuidoras para o Retail Liquidity Program
- Os 7 servidores implantados corretamente processaram as ordens normalmente
- As ordens enviadas ao 8º servidor reativaram o antigo código do Power Peg por meio do sinalizador reutilizado
- Originalmente, o Power Peg contava quantas ações haviam sido compradas ou vendidas em relação à ordem-pai quando as ordens-filhas eram executadas, e parava de roteá-las quando a ordem-pai fosse satisfeita
- Em 2005, a Knight moveu a funcionalidade de rastreamento acumulado para uma etapa anterior da execução do código, e o rastreamento agregado dentro do Power Peg havia sido removido
- Quando o sinalizador do Power Peg foi ativado no 8º servidor, o Power Peg passou a rotear ordens-filhas para os mercados de execução, mas sem rastrear a quantidade de ações em relação à ordem-pai, funcionando na prática como uma repetição infinita
Os sinais antes da abertura e o descontrole após 9h30
- O sistema da Knight começou a enviar emails automáticos às 8h01 daquela manhã
- Eles eram gerados quando o SMARS processava ordens destinadas ao pregão pré-abertura
- Os emails mencionavam o SMARS e identificavam o erro como “Power Peg disabled”
- Entre 8h01 e 9h30, 97 desses emails foram enviados a funcionários da Knight
- Como esses emails não tinham sido projetados como alertas do sistema, eles não foram verificados imediatamente
- Logo após a abertura do mercado às 9h30, várias pessoas em Wall Street perceberam que algo anormal estava acontecendo
- Às 9h31 já estava claro que havia um problema grave, e às 9h32 aumentava a dúvida sobre por que aquilo não parava
- Nos primeiros 45 minutos, as execuções da Knight representaram mais de 50% do volume em alguns papéis e empurraram o preço de certas ações em mais de 10%
- Em reação às negociações equivocadas, outras ações perderam valor
Ausência de kill switch e resposta equivocada
- A Knight não tinha um kill switch para interromper imediatamente o sistema problemático
- Também não havia um procedimento de resposta documentado, então a causa teve de ser diagnosticada em produção, em um ambiente que negociava 8 milhões de ações por minuto
- Sem conseguir encontrar a causa, a Knight removeu o novo código dos servidores que haviam sido implantados corretamente
- Essa medida acabou removendo o código que funcionava e deixando o código com problema
- Depois disso, ordens-pai adicionais passaram a ativar o código do Power Peg não em apenas um servidor, mas em todos os servidores, ampliando ainda mais o problema
- A Knight só conseguiu parar o sistema depois de 45 minutos
O tamanho da perda e o desfecho da empresa
- Nos primeiros 45 minutos após a abertura do mercado, o código Power Peg processou 212 ordens-pai
- O SMARS enviou milhões de ordens-filhas ao mercado e, como resultado, ocorreram 4 milhões de negociações em 154 papéis, com mais de 397 milhões de ações negociadas
- A Knight assumiu uma posição líquida comprada de cerca de 3,5 bilhões de dólares em 80 papéis e uma posição líquida vendida de cerca de 3,15 bilhões de dólares em 74 papéis
- A Knight Capital Group realizou uma perda de 460 milhões de dólares em apenas 45 minutos
- Como na época ela possuía 365 milhões de dólares em caixa e equivalentes de caixa, a Knight passou de maior trader de ações dos EUA e importante market maker a uma empresa na prática insolvente
- Para cobrir a perda, precisava levantar capital em 48 horas, e conseguiu uma rodada de 400 milhões de dólares com cerca de 6 investidores
- Depois disso, a Knight Capital Group foi adquirida pela Getco LLC em dezembro de 2012, e a empresa combinada passou a se chamar KCG Holdings
As lições para DevOps e Continuous Delivery
- Não basta apenas criar e testar um bom software
- Para entregar valor ao cliente, o software precisa ser implantado corretamente no mercado
- A causa do incidente não estava apenas em um engenheiro que implantou o SMARS, mas no fato de o processo da Knight não suportar o risco ao qual estava exposto
- Implantações que dependem de pessoas lendo e seguindo instruções carregam uma possibilidade inerente de erro
- Falhas podem acontecer nas próprias instruções, na interpretação das instruções ou na execução delas
- A implantação deve ser o mais automatizada e repetível possível, reduzindo a chance de erro humano
- Se a Knight tivesse um sistema de implantação automatizada com automação de configuração, implantação e testes, o erro que causou o Knightmare poderia ter sido evitado
- Entre os princípios de Continuous Delivery, há dois que se aplicam a este caso
- O release de software deve ser um processo repetível e confiável
- Deve-se automatizar o máximo possível dentro de limites razoáveis
1 comentários
Opiniões no Hacker News
Não sei bem como a implantação automática teria resolvido esse problema. Pelo contrário, é bem possível que ela tivesse ampliado o impacto e as consequências
Trocar “um desenvolvedor esqueceu de subir o código para um servidor” por “o agente de implantação teve um erro ao baixar o novo binário/código para o servidor, e esse erro não apareceu por causa de um bug no agente” resulta no mesmo modo de falha. O impacto teria se espalhado mais rápido
A responsabilidade aqui é do desenvolvedor. Porque ele escreveu o código de uma forma sem compatibilidade retroativa
Tanto o mercado quanto a Knight sabiam que havia um problema grave, mas tentaram vários hotfixes durante 45 minutos antes de interromper as negociações. É possível que não houvesse kill switch, ou que ninguém tivesse autoridade para acioná-lo porque, se fosse pressionado na hora errada, o custo de oportunidade seria de algo como US$ 500 mil
Na época eu trabalhava em uma concorrente da Knight e nós também frequentemente colocávamos bugs horríveis em produção, mas, em retrospectivas, era difícil imaginar que a mesma coisa pudesse acontecer conosco. Havia vários sistemas automáticos que bloqueavam operações individuais, e traders seniores ou responsáveis por operações podiam fazer o kill switch ser acionado com uma conversa de 60 segundos, sem precisar temer as consequências
Na prática, poderíamos até ter ganhado mais com a perda de US$ 400 milhões da Knight, mas nosso sistema de risco julgou aquilo “bom demais para ser verdade” e continuou desligando a estratégia, reduzindo o lucro
É preciso rever a parte em que “um técnico da Knight não copiou o novo código para um dos 8 servidores SMARS”. Claro que um pipeline de CI/CD também poderia falhar no meio e implantar só em alguns servidores, mas acho a probabilidade baixa
Mesmo que isso acontecesse, com um Ansible Playbook ele teria parado no momento dessa falha de transferência, o Playbook inteiro teria falhado e não chegaria à última etapa, que era reiniciar o serviço
Isso foi erro humano, e é exatamente por isso que a automação existe
Além disso, a parte em que “um segundo técnico não revisou a implantação, e ninguém percebeu que o código Power Peg não havia sido removido do 8º servidor nem que o novo código RLP havia sido adicionado” também poderia ter sido evitada por CI/CD. Um “Pull Request” para o repositório de código do Ansible teria impedido o primeiro técnico de fazer merge em master/main sem revisão. Afinal, master/main deveria estar protegido
Tenho certeza de que DevOps baseado em CI/CD teria resolvido esse problema 100%
Charity Majors falou bastante sobre isso na Euruko. Ferramentas de implantação não deveriam ser apenas scripts bash embrulhados em um casaco; elas precisam ter pessoal e testes suficientes, e ser automatizadas até o fim sempre que possível
Um processo de implantação próximo de uma arquitetura imutável, ferramentas que monitorem rollouts com falha/parados/incompletos e a capacidade de voltar rapidamente ao último estado bom criam camadas de defesa e facilitam o caminho de ação quando as coisas dão errado. Isso não teria tornado esse problema impossível, mas teria dificultado sua ocorrência
Um runbook manual vira, toda vez que se faz uma operação em servidores, um jogo de adivinhação do tipo “fiz a etapa 12 e acho que também fiz a 13, então agora é a 14”. Quando uma tarefa feita um milhão de vezes é interrompida no meio, o cérebro humano muitas vezes não consegue distinguir de forma confiável a execução atual de uma falsa lembrança vinda da execução anterior
Se não houver intertravamentos que impeçam pular etapas, é uma aposta a cada vez. E o esforço para criar esses intertravamentos já representa uma parte considerável do custo da automação
“É um mistério por que um código que estava morto havia 8 anos continuava na base de código, mas esse não é o ponto principal”, disseram, mas isso parece ser exatamente o ponto principal
Parece que deixaram ali, por 8 anos, um código não usado, e só tentaram removê-lo quando quiseram reutilizar a flag. Se, 8 anos antes, tivessem feito a coisa certa e removido o código não usado, a história teria sido completamente diferente. Nem a rotina antiga teria ressuscitado, nem haveria servidores fora de controle
Talvez a Knight Capital não usasse controle de versão e mantivesse o código “por via das dúvidas”. Mas já vi desenvolvedores relutando em apagar código mesmo em repositórios totalmente versionados, e isso é realmente surpreendente. Se precisar de novo, dá para restaurar pelo controle de versão. Se precisar de novo mas tiver esquecido que ele está lá, também não encontraria o caminho de código morto. Deixá-lo na árvore de fontes é dívida pura
Kevlin Henney fez uma excelente palestra no GOTO sobre confiabilidade de software e aborda esse ponto usando a Knight Capital como exemplo. Na verdade, ele também cita este post de blog https://youtu.be/IiGXq3yY70o?si=hZ9HB2dlfj0vHvNK&t=463
“Não existe código verdadeiramente morto. Basta uma pequena suposição, uma mudança em uma suposição, e de repente ele deixa de ser código morto e vira código zumbi. Um apocalipse zumbi ressuscitado custa dinheiro”
git loge talvez usargit blameMuitas vezes não sabem filtrar o histórico do git. Não conhecem
git pickaxenem padrões de exclusão, e nem sequer pensam que é possível procurarfoono log do git excluindo um diretório específico, como emgit log -G'int.*foo\(' -- ':(exclude)directory'Em vez disso, sabem usar
grepna árvore de código atual. Então acham que, se não apagarem, poderão encontrá-lo de novo com umgrepadequado. Se for apagado, talvez não saibam como encontrá-lo no histórico do gitAté certo ponto, dá para entender. Código dentro do log do git não aparece em muitas ferramentas. Por exemplo, o editor não o sugere no autocomplete, e ele não aparece na documentação da biblioteca
Se você realmente acredita que aquele código será usado de novo, é até defensável, em certa medida, deixá-lo na árvore para que acompanhe refatorações e seja descoberto quando necessário. Mas, em casos em que claramente não haveria reutilização, como na Knight Capital, é difícil defender isso
Mesmo uma atualização que apenas “remove código antigo” pode parecer difícil para alguém que vê qualquer mudança como um risco de quebrar algo. Para ser justo, toda mudança é um risco, mas deixar código antigo também é um risco
Pelo menos agora dá para apontar este caso como um exemplo claro desse risco
O que eu realmente não entendo toda vez que vejo essa história é terem reutilizado uma flag existente em vez de criar uma nova. Por que fizeram isso?
git rebaseEspero que a maioria das organizações tenha processos para impedir que isso aconteça perto da branch principal. Mas eu mesmo já estraguei acidentalmente uma tabela de banco de dados de produção em uma organização pequena, então não dá para dizer que um
git rebaseacidental seja impossívelHavia outro problema: usavam um banco de dados com apenas 256 colunas. Quando precisavam de uma nova coluna, simplesmente reutilizavam uma coluna antiga que “não estava sendo usada na época”
Se me lembro bem, internamente isso também era reconhecido em geral como uma “má ideia”, mas ninguém priorizava limpar código antigo nem estabelecer práticas melhores
Nenhum sistema de implantação contínua que eu já tenha usado teria evitado esse bug específico
Era um rollout gradual, mas havia no código um bug lógico em que, se uma instalação falhasse durante essa etapa de rollout gradual, a empresa iria à falência
Para evitar isso, seria preciso verificar em tempo de execução se a versão do software, por exemplo o SHA do git, bate, e também adicionar injeção de falhas aos testes que chamam a infraestrutura de rollout de software
Era um verdadeiro Velho Oeste. Também é importante notar que, desde então, os sistemas de trading mudaram muito
Quando comecei a trabalhar nessa área, em 2009, a confiabilidade dos sistemas em bancos, corretoras e bolsas era bem ruim. Era comum ter de confirmar por telefone qual havia sido a quantidade executada
Lembro quando a bolsa italiana estava fazendo rollout de um sistema. Em certo momento, faziam “testes” em um lugar que misturava produção e UAT e, se me lembro bem, após o fechamento do pregão apenas mudavam o IP da conexão de envio de ordens para testar a próxima release. O ambiente de UAT tinha tantos bugs e estava em sua maior parte meio morto, então não dava para testar lá
Melhor nem falar daquelas planilhas Excel com código VBA que até o ChatGPT xingaria, usadas para precificar produtos com volumes cheios de zeros
Hoje é muito diferente. Em parte graças a incidentes como esse. A maior parte foi automatizada, e a atitude de cowboy diminuiu muito
Há kill switches obrigatórios, várias camadas de monitoramento de risco/atividade de trading, monitoramento do lado da bolsa, e muitas lições aprendidas a duras penas foram realmente incorporadas aos sistemas. Isso também explica por que as pessoas tendem a subestimar ingenuamente a dificuldade de criar um bom sistema de trading. A estratégia ficou mais inteligente, mas o ponto central costuma ser como não morrer por algo fora das condições normais
Literalmente todo mundo em finanças quantitativas conhece a Knight Capital. Existe até a expressão “pulling a knight capital”. Ou seja, tomar atalhos mesmo em sistemas mission-critical capazes de levar a empresa à falência em um instante, e depois pagar o preço por isso
O sistema da nossa equipe tem papel crítico em centenas de milhões de dólares de receita por dia. Se o sistema ficar fora do ar por tempo suficiente, essa receita desaparece. Aqui, “tempo suficiente” significa pelo menos algumas horas e, dentro desse período, em geral conseguimos restaurar o estado normal sem grande impacto externo
Também temos processos manuais, mas, antes de iniciar qualquer processo manual, documentamos o procedimento de rollback e monitoramos o deploy. Também separamos deploy de código de deploy de funcionalidades, e o deploy de funcionalidades é feito gradualmente por trás de feature flags
Exigimos uma nova feature flag para novas funcionalidades ou mudanças de código. É doloroso e lento, mas nos ajudou a evitar situações perigosas e pânico, e também reduziu bastante a carga de operações e de plantão
Para dar muito errado de verdade, seria preciso passar por vários “filtros de defeitos”: deixar passar, na revisão de código, uma mudança de comportamento sem feature flag; deixar passar também nos testes manuais/ambiente de desenvolvimento; o deploy falhar; o rollback falhar ou ser feito errado; não haver monitoramento para alertar que o problema ainda não foi corrigido; não escalar a tempo para níveis superiores; e passar tempo suficiente para perder a capacidade de cumprir o SLA
Para mudanças manuais mais arriscadas, também é possível exigir que duas pessoas façam a alteração juntas. Uma pessoa aponta, por chamada de vídeo, o que está sendo alterado, e a outra valida
Se você lida com um sistema cujo SLA é medido em minutos e cujas mudanças são irreversíveis, precisa conhecer formas reais de monitorar e fazer rollback em poucos minutos. Se for uma tarefa nova e manual, confira quatro vezes e faça outra pessoa acompanhar ao lado. Caso contrário, é questão de tempo até vários problemas se acumularem em sequência e chegarem a um ponto irrecuperável. Por mais excelentes e inteligentes que as pessoas sejam, se elas precisarem fazer mudanças manualmente ou iniciar mudanças, erros sempre vão acontecer, e essa probabilidade de erro precisa estar embutida no processo de gestão de mudanças
No comércio em geral ou em B2B, em muitos casos o cliente pode tentar fazer a mesma compra um pouco depois. Não é “agora ou nunca”
Eu mesmo já tentei de novo comprar algo que queria quando o vendedor estava fora do ar, quando os servidores caíram por causa de um novo anúncio e de uma grande demanda, ou quando havia algum problema de manutenção bancária
Textos relacionados:
Knightmare: A DevOps Cautionary Tale (2014) - https://news.ycombinator.com/item?id=22250847 - fevereiro de 2020, 33 comentários
Knightmare: A DevOps Cautionary Tale (2014) - https://news.ycombinator.com/item?id=8994701 - fevereiro de 2015, 85 comentários
Knightmare: A DevOps Cautionary Tale - https://news.ycombinator.com/item?id=7652036 - abril de 2014, 60 comentários
Além disso:
The $440M software error at Knight Capital (2019) - https://news.ycombinator.com/item?id=31239033 - maio de 2022, 172 comentários
Bugs in trading software cost Knight Capital $440M - https://news.ycombinator.com/item?id=4329495 - agosto de 2012, 1 comentário
Knight Capital Says Trading Glitch Cost It $440 Million - https://news.ycombinator.com/item?id=4329101 - agosto de 2012, 90 comentários
Haveria outros?
Nanex ~ 03-Aug-2012 ~ The Knightmare Explaned - https://news.ycombinator.com/item?id=4337359 - sem comentários
O problema real, aceitando aqui uma formulação do tipo “verdadeiro escocês”, foi o uso de uma combinação não testada de configuração e release binário
Configuração e binário podem ser lançados de forma coordenada ao mesmo tempo, e isso impediria esse tipo de problema. É claro que houve outros erros, mas, sem essa condição, esse problema não poderia ter acontecido
A parte que diz “por que um código morto havia permanecido na base de código por 8 anos é um mistério, mas esse não é o ponto” talvez não seja o pior erro da história, mas também não dá para dizer que não é o ponto
Se a funcionalidade morta tivesse sido removida preventivamente, o software teria ficado mais simples e mais bem compreendido, e a possibilidade de sair do controle também teria diminuído. Avançar sem parar, sem esse tipo de manutenção, é um risco, calculado ou não
Fico muito feliz por não escrever código que roteia automaticamente milhões de dólares sem intervenção humana
Parece escrever código que pilota um jumbo. Quem iria querer assumir essa responsabilidade?
Vejo isso como algo adequado para certo tipo de pessoa que ama processos, testes, simuladores e redundância. O código que pilota o avião em si é só 1% da engenharia nesse contexto
Basta resolver, uma por uma, as preocupações que surgem naturalmente; quanto mais razoavelmente ansioso você for, melhor para o negócio. Pela minha experiência no setor financeiro, o problema da Knight foi 10% técnico e 90% alguém parecido com um CTO confundindo ousadia com imprudência. Não só naquele dia ou naquela semana, mas de forma geral
Imagino que este incidente também tenha contribuído um pouco para isso