1 puntos por GN⁺ 2023-09-11 | 1 comentarios | Compartir por WhatsApp
  • Knight Capital Group, uno de los grandes actores del trading de acciones en EE. UU., quedó al borde de la quiebra el 1 de agosto de 2012 tras una falla de despliegue de SMARS que le provocó una pérdida de 460 millones de dólares en solo 45 minutos
  • El incidente comenzó durante la adaptación al Retail Liquidity Program de la NYSE, cuando se reutilizó para una nueva función la bandera de un código de Power Peg que no se había usado en 8 años
  • El nuevo código se desplegó solo en 7 de 8 servidores, y cuando el servidor restante recibió nuevas órdenes RLP, volvió a ejecutarse la función inactiva de Power Peg
  • Power Peg siguió enrutando órdenes hijas sin rastrear el volumen ejecutado de la orden padre, y Knight tuvo que buscar la causa en producción sin un kill switch ni procedimientos de respuesta documentados
  • El despliegue es tan importante como escribir y probar código, y depender de procesos manuales sin automatización, repetibilidad y verificación puede convertirse en un riesgo operativo fatal

La firma de trading de alta frecuencia que colapsó en 45 minutos

  • Knight Capital Group era una empresa estadounidense de servicios financieros dedicada a market making, ejecución electrónica y ventas y trading institucionales
  • En 2012, Knight era el mayor trader de acciones en EE. UU., con cerca de 17% de participación de mercado tanto en NYSE como en NASDAQ
  • El Electronic Trading Group (ETG) de Knight gestionaba en promedio más de 3.3 mil millones de operaciones al día y movía más de 21 mil millones de dólares diarios
  • Al 31 de julio de 2012, Knight tenía aproximadamente 365 millones de dólares en efectivo y equivalentes de efectivo

Actualización de SMARS para responder al RLP

  • La NYSE tenía previsto lanzar el Retail Liquidity Program el 1 de agosto de 2012
  • Knight actualizó SMARS, un enrutador algorítmico automático de alta velocidad que enviaba órdenes al mercado, para adaptarse a ello
  • SMARS era un sistema que recibía “órdenes padre” de la plataforma de trading y las ejecutaba dividiéndolas en una o más “órdenes hijas”
    • Cuanto más grande era la orden padre, más órdenes hijas se generaban
  • Esta actualización buscaba reemplazar el código de Power Peg, que no se había usado en 8 años
  • El nuevo código reutilizó la bandera existente que activaba Power Peg para la nueva función
  • El código en sí había sido probado lo suficiente y se había confirmado que funcionaba correctamente

El servidor que quedó fuera en un despliegue manual

  • Entre el 27 y el 31 de julio de 2012, Knight hizo un despliegue manual del nuevo software a un número limitado de servidores por día, y el total de destino era de 8 servidores
  • Según documentos de la SEC, un técnico no copió el nuevo código a uno de los 8 servidores de SMARS
  • No existía un proceso en el que un segundo técnico revisara ese despliegue, ni tampoco un procedimiento documentado que exigiera esa revisión
  • Como resultado, en el octavo servidor no se eliminó el código de Power Peg ni se añadió el nuevo código de RLP

Cómo volvió a la vida el código muerto

  • El 1 de agosto de 2012 a las 9:30 a. m. (hora del este de EE. UU.), cuando abrió el mercado, Knight empezó a procesar órdenes de clientes broker-dealer para el Retail Liquidity Program
  • Los 7 servidores desplegados correctamente procesaron las órdenes con normalidad
  • Las órdenes que llegaron al octavo servidor reactivaron el antiguo código de Power Peg a través de la bandera reutilizada
  • Originalmente, Power Peg contaba cuántas acciones se compraban o vendían en relación con la orden padre a medida que se ejecutaban las órdenes hijas, y detenía el enrutamiento de órdenes hijas cuando la orden padre quedaba satisfecha
  • En 2005, Knight movió la función de seguimiento acumulado a una etapa más temprana de la ejecución del código, y el seguimiento agregado dentro de Power Peg había sido eliminado
  • Cuando la bandera de Power Peg se activó en el octavo servidor, Power Peg enrutó órdenes hijas a los mercados de ejecución, pero como no rastreaba la cantidad de acciones frente a la orden padre, en la práctica operó como una repetición sin fin

Las señales previas a la apertura y el descontrol después de las 9:30

  • Ese día, el sistema de Knight empezó a enviar correos automáticos desde las 8:01 a. m.
    • Ocurría cuando SMARS procesaba órdenes destinadas al premarket
    • Los correos mencionaban a SMARS e identificaban el error como “Power Peg disabled”
    • Entre las 8:01 y las 9:30 se enviaron 97 de estos correos a empleados de Knight
  • Como estos correos no estaban diseñados como alertas del sistema, no se revisaron de inmediato
  • Justo después de la apertura a las 9:30, varias personas en Wall Street notaron que algo anormal estaba ocurriendo
  • A las 9:31 ya estaba claro que ocurría algo grave, y a las 9:32 crecían las dudas sobre por qué no se detenía
  • Durante los primeros 45 minutos, las ejecuciones de Knight representaron más del 50% del volumen en algunos valores y empujaron el precio de ciertas acciones más de 10%
  • Otras acciones perdieron valor como reacción a las operaciones erróneas

Ausencia de kill switch y respuesta equivocada

  • Knight no tenía un kill switch para detener de inmediato el sistema problemático
  • Tampoco había un procedimiento de respuesta documentado, así que tuvo que diagnosticar la causa en un entorno de producción donde se negociaban 8 millones de acciones por minuto
  • Sin encontrar la causa, Knight retiró el nuevo código de los servidores que sí habían sido desplegados correctamente
  • Esa medida eliminó el código que funcionaba y dejó intacto el código defectuoso
  • Después, órdenes padre adicionales activaron el código de Power Peg no en un solo servidor, sino en todos, agravando aún más el problema
  • Knight no logró detener el sistema hasta 45 minutos después

El tamaño de la pérdida y el final de la empresa

  • Durante los primeros 45 minutos tras la apertura del mercado, el código de Power Peg recibió y procesó 212 órdenes padre
  • SMARS envió millones de órdenes hijas al mercado y, como resultado, se generaron 4 millones de operaciones y más de 397 millones de acciones negociadas en 154 valores
  • Knight terminó con una posición neta compradora de unos 3,500 millones de dólares en 80 valores y una posición neta vendedora de unos 3,150 millones de dólares en 74 valores
  • Knight Capital Group materializó una pérdida de 460 millones de dólares en solo 45 minutos
  • Como en ese momento tenía 365 millones de dólares en efectivo y equivalentes de efectivo, Knight pasó de ser el mayor trader de acciones de EE. UU. y un importante market maker a quedar en estado de insolvencia
  • Para cubrir la pérdida, necesitaba conseguir capital en 48 horas, y Knight logró obtener una inversión de 400 millones de dólares de alrededor de 6 inversionistas
  • Más tarde, en diciembre de 2012, Knight Capital Group fue adquirida por Getco LLC, y la empresa fusionada pasó a llamarse KCG Holdings

Lecciones para DevOps y Continuous Delivery

  • No basta con crear y probar buen software
  • Para entregar valor al cliente, el software debe desplegarse correctamente en el mercado
  • La causa del incidente no estuvo solo en un ingeniero que desplegó SMARS, sino en que los procesos de Knight no podían absorber el riesgo al que estaban expuestos
  • Los despliegues que dependen de que una persona lea y siga instrucciones incorporan la posibilidad de error
    • Los errores pueden ocurrir en las instrucciones mismas, en su interpretación o en su ejecución
  • El despliegue debe ser, en la medida de lo posible, automatizado y repetible, reduciendo la probabilidad de error humano
  • Si el sistema de despliegue automatizado hubiera incluido automatización de configuración, despliegue y pruebas, se podría haber evitado el error que provocó Knightmare
  • Entre los principios de Continuous Delivery que aplican a este caso, hay dos especialmente relevantes
    • La liberación de software debe ser un proceso repetible y confiable
    • Debe automatizarse tanto como sea razonablemente posible

1 comentarios

 
GN⁺ 2023-09-11
Opiniones de Hacker News
  • No estoy muy seguro de cómo el despliegue automático habría resuelto este problema. Más bien, probablemente habría aumentado el impacto y las consecuencias del problema.
    Cambiar “un desarrollador olvidó subir el código a un servidor” por “el agente de despliegue tuvo un error al descargar un nuevo binario/código al servidor, y por un bug del agente ese error no salió a la luz” produce el mismo modo de falla. El impacto se habría propagado más rápido.
    Aquí la responsabilidad recae en el desarrollador. Porque escribió el código de una forma sin compatibilidad hacia atrás.

    • La responsabilidad recae por completo en el equipo de gestión de riesgos.
      El mercado y Knight sabían que había un problema grave, pero intentaron varios hotfixes durante 45 minutos antes de detener las operaciones. Es posible que no hubiera un kill switch, o que nadie tuviera la autoridad para activarlo porque, si se presionaba en el momento equivocado, el costo de oportunidad rondaba los 500 mil dólares.
      En ese entonces trabajaba para un competidor de Knight y, aunque nosotros también desplegábamos bugs terribles en producción con frecuencia, en los análisis post mortem era difícil imaginar que nos pudiera pasar lo mismo. Teníamos varios sistemas automáticos para bloquear operaciones individuales, y un trader senior o alguien de operaciones podía hacer que se accionara el kill switch con una conversación de 60 segundos, sin tener que temer las consecuencias.
      De hecho, podríamos haber ganado más con la pérdida de 400 millones de dólares de Knight, pero nuestro sistema de riesgo decidió que era “demasiado bueno para ser creíble” y siguió apagando la estrategia, así que nuestras ganancias se redujeron.
    • CI/CD habría resuelto este problema al 100%.
      Hay que volver a mirar la parte que dice: “uno de los técnicos de Knight no copió el código nuevo a uno de los 8 servidores SMARS”. Claro que un pipeline de CI/CD también puede fallar a mitad de camino y desplegarse solo en algunos servidores, pero creo que la probabilidad es baja.
      Incluso si eso hubiera pasado, con un Ansible Playbook se habría detenido en el momento de esa falla de transferencia, todo el Playbook habría fallado y no habría llegado al último paso, que era reiniciar el servicio.
      Esto fue un error humano, y precisamente por eso existe la automatización.
      Además, la parte que dice que “un segundo técnico no revisó el despliegue, y nadie sabía que en el octavo servidor no se había eliminado el código de Power Peg ni se había agregado el nuevo código de RLP” también podría haber sido evitada por CI/CD. Un “Pull Request” al repositorio de código de Ansible habría impedido que el primer técnico hiciera merge a master/main sin revisión. Porque master/main debería estar protegido.
      Estoy convencido de que DevOps basado en CI/CD habría resuelto este problema al 100%.
    • La responsabilidad podría ser de quien tomó la decisión de reutilizar una bandera existente. Quien haya hecho desarrollo de software lo sabe: esa decisión no necesariamente, ni por lo general, la toma un desarrollador.
    • Yo lo veo como un problema de no haber invertido lo suficiente en el proceso de despliegue. Como referencia, me gano la vida manteniendo herramientas open source de despliegue.
      Charity Majors habló mucho sobre esto en Euruko. Las herramientas de despliegue no deberían ser apenas scripts de bash envueltos en un abrigo; deben tener suficiente personal y pruebas, y estar automatizadas hasta donde sea posible.
      Un proceso de despliegue cercano a una arquitectura inmutable, herramientas que vigilen rollouts fallidos/detenidos/incompletos y la capacidad de volver rápido a un estado anterior sano crean capas de defensa y facilitan el camino de acción cuando algo sale mal. Esto no habría vuelto imposible el problema, pero sí habría hecho más difícil que ocurriera.
    • El objetivo de la automatización es reducir con el tiempo los casos límite no identificados.
      Un procedimiento manual termina siendo un juego de adivinanzas cada vez que se trabaja en servidores: “hice el paso 12, creo que también hice el 13, así que ahora sigue el 14”. Cuando una tarea que el cerebro humano hizo un millón de veces se interrumpe a la mitad, muchas veces no puede distinguir de forma confiable entre esta ejecución y un recuerdo falso generado a partir de la vez anterior.
      Si no hay interlocks que impidan saltarse pasos, cada vez es una apuesta. Y el esfuerzo de crear esos interlocks ya representa una parte considerable del costo de automatizar.
  • “Que un código que llevaba 8 años muerto siguiera en la base de código es un misterio, pero ese no es el punto”, dijeron, pero eso parece ser exactamente el punto
    Parece que dejaron ahí durante 8 años código que no usaban, y recién intentaron quitarlo cuando quisieron reutilizar la flag. Si hace 8 años lo hubieran manejado correctamente y hubieran eliminado el código sin uso, la historia habría sido completamente distinta. No habría revivido una rutina vieja ni habría habido servidores haciendo lo que se les antojaba
    Es posible que Knight Capital no usara control de versiones y por eso conservara el código “por si acaso”. Pero incluso en repositorios totalmente versionados he visto desarrolladores que no quieren borrar código, y de verdad sorprende. Si lo necesitas de nuevo, lo recuperas desde el control de versiones. Si lo vuelves a necesitar pero olvidaste que estaba ahí, tampoco habrías encontrado esa ruta de código muerto. Dejarlo en el árbol de código fuente es deuda pura
    Kevlin Henney dio una excelente charla en GOTO sobre confiabilidad del software, y trata este punto usando a Knight Capital como ejemplo. De hecho, también cita este post de blog https://youtu.be/IiGXq3yY70o?si=hZ9HB2dlfj0vHvNK&t=463
    “No existe el código verdaderamente muerto. Con una pequeña suposición, un pequeño cambio en una suposición, de pronto ya no es código muerto sino código zombi. Un apocalipsis zombi revivido cuesta dinero”

    • Parece que muchos desarrolladores solo conocen lo básico de git. Hacen commit de los cambios, ven el historial con git log y quizá saben usar git blame
      Muchas veces no saben cómo filtrar el historial de git. No conocen git pickaxe ni los patrones de exclusión, y ni siquiera se les ocurre que pueden buscar foo en el log de git excluyendo un directorio específico, como en git log -G'int.*foo\(' -- ':(exclude)directory'
      En cambio, sí saben hacer grep en el árbol de código actual. Por eso piensan que, si no lo borran, podrán volver a encontrarlo con un grep adecuado. Si se borra, quizá no sepan cómo encontrarlo en el historial de git
      Hasta cierto punto se entiende. El código dentro del log de git no es visible para muchas herramientas. Por ejemplo, el editor no lo sugiere con autocompletado y tampoco aparece en la documentación de la librería
      Si realmente crees que ese código se volverá a usar, hasta cierto punto se puede defender dejarlo en el árbol para que acompañe los refactorings y se descubra cuando haga falta. Pero en casos como Knight Capital, donde claramente no se iba a volver a usar, es difícil defenderlo
    • Escuché muchas veces “si funciona, no lo toques”, sobre todo de managers que no saben de qué hablan
      Incluso una actualización que simplemente “elimina código viejo” puede parecer difícil para alguien que ve cualquier cambio como un riesgo de romper algo. Para ser justos, todo cambio implica riesgo, pero dejar código viejo también es un riesgo
      Al menos ahora se puede señalar este caso como un ejemplo claro de ese riesgo
    • Que dejaran código muerto en sí me sorprende menos. Ha pasado en casi todas las empresas en las que trabajé
      Lo que de verdad no entiendo cada vez que veo esta historia es que hayan reutilizado una flag existente en vez de crear una nueva. ¿Por qué hicieron eso?
    • El control de versiones tampoco es infalible. Todo el historial del código puede desaparecer para siempre con un solo git rebase
      Espero que la mayoría de las organizaciones tengan procesos para evitar que algo así ocurra cerca de la rama principal. Pero yo también he arruinado por accidente una tabla de base de datos de producción en una organización pequeña, así que no diría que un git rebase accidental sea algo imposible
    • Escribí en otro hilo hermano sobre mi experiencia trabajando en Knight justo después del incidente
      Había otro problema: usaban una base de datos con solo 256 columnas. Cuando necesitaban una nueva columna, simplemente reutilizaban una columna vieja que “no se usaba en ese momento”
      Si mal no recuerdo, internamente en general se reconocía que era una “mala idea”, pero nadie priorizaba limpiar el código viejo ni establecer mejores prácticas
  • Ningún sistema de despliegue continuo que yo haya visto habría evitado este bug en particular
    Estaban haciendo un rollout gradual, pero el código tenía un bug lógico en el que, si fallaba una sola instalación durante esa etapa de rollout gradual, la empresa quebraba
    Para evitarlo, habría hecho una verificación en tiempo de ejecución de que la versión del software, por ejemplo el SHA de git, coincidiera, y también habría agregado inyección de fallas a las pruebas que invocan la infraestructura de rollout de software

    • Un sistema de despliegue continuo bien hecho no permitiría que la configuración y el código quedaran desalineados. Antes había una configuración que activaba Power Peg, pero ahora activaba otra cosa, y también había un cambio de código que interpretaba esa flag de manera distinta
  • Era un verdadero Lejano Oeste. También es importante señalar que desde entonces los sistemas de trading cambiaron mucho
    Cuando empecé a trabajar en este campo, en 2009, la confiabilidad de los sistemas en bancos, brokers y bolsas era bastante desastrosa. Era frecuente tener que confirmar por teléfono cuál había sido la cantidad ejecutada
    Recuerdo cuando la bolsa italiana estaba desplegando un sistema. En algún momento hacían “pruebas” en algo que mezclaba producción y UAT, y, si no recuerdo mal, después del cierre del mercado solo cambiaban la IP de la conexión de envío de órdenes para probar la siguiente release. El entorno UAT tenía tantos bugs y estaba en su mayoría medio muerto que no se podía probar ahí
    Ni hablemos de las hojas de cálculo de Excel con código VBA que hasta ChatGPT insultaría, usadas para valuar productos con volúmenes llenos de ceros
    Hoy es muy distinto. En parte gracias a incidentes como este. La mayor parte está automatizada y hay mucho menos espíritu de cowboy
    Hay kill switches obligatorios, múltiples capas de monitoreo de riesgo/actividad de trading, monitoreo del lado de la bolsa, y muchísimas lecciones aprendidas a la mala realmente incorporadas en los sistemas. Por eso también la gente suele subestimar ingenuamente lo difícil que es construir un buen sistema de trading. Las estrategias se volvieron más inteligentes, sí, pero lo central suele ser cómo no morir por algo fuera de las condiciones normales

  • Literalmente todo el mundo en finanzas cuantitativas conoce a Knight Capital. Incluso existe la expresión “pulling a knight capital”. Es decir, tomar atajos incluso en sistemas mission-critical que pueden llevar a la empresa a la quiebra en un instante, y pagar el precio por ello

    • De hecho, también se usa en el material de onboarding de nuestra empresa
  • El sistema de nuestro equipo cumple un rol crítico en ingresos de cientos de millones de dólares al día. Si el sistema permanece caído el tiempo suficiente, esos ingresos desaparecen. Y por “tiempo suficiente” me refiero a al menos unas horas; por lo general, dentro de ese lapso se puede volver al estado normal sin demasiado impacto externo.
    Nosotros también tenemos procesos manuales, pero para cualquier proceso manual documentamos el procedimiento de rollback antes de empezar y monitoreamos el despliegue. También separamos el despliegue de código del despliegue de funcionalidades, y el despliegue de funcionalidades lo hacemos gradualmente detrás de feature flags.
    Para una funcionalidad nueva o un cambio de código exigimos un feature flag nuevo. Es doloroso y lento, pero nos permitió evitar situaciones riesgosas y pánico, y redujo mucho la carga operativa y de guardias on-call.
    Para que algo salga realmente muy mal, tendría que atravesar varios “filtros de defectos”: que en la revisión de código se pase por alto un cambio de comportamiento sin feature flag, que también se pase por alto en las pruebas manuales/de entorno de desarrollo, que falle el despliegue, que falle el rollback o se haga mal, que no haya monitoreo que avise que el problema todavía no se corrigió, que no se escale a tiempo a un nivel superior y que pase suficiente tiempo como para perder la capacidad de cumplir el SLA.
    Para cambios manuales más riesgosos, también se puede hacer que dos personas los realicen juntas. Una señala por videollamada qué se está cambiando y la otra lo valida.
    Si trabajas con un sistema cuyo SLA se mide en minutos y cuyos cambios no son reversibles, tienes que conocer una forma real de monitorear y hacer rollback en cuestión de minutos. Si es una tarea nueva y manual, hay que revisarla cuatro veces y hacer que otra persona observe al lado. De lo contrario, es cuestión de tiempo que varios problemas se encadenen y llegue un momento en que ya no se puedan corregir. Por más brillante e inteligente que sea la gente, si una persona tiene que hacer cambios manualmente o iniciar un cambio, los errores siempre ocurren, y esa probabilidad de error debe estar incorporada al proceso de gestión de cambios.

    • ¿De verdad desaparecen esos ingresos? ¿O simplemente se generan más tarde?
      En el comercio general o B2B, en muchos casos el cliente puede intentar hacer la misma compra un poco después. No es “ahora o nunca”.
      Yo también he vuelto a intentar comprar algo que quería cuando el vendedor estaba caído, cuando los servidores colapsaron por un nuevo anuncio y una gran demanda, o cuando hubo problemas por mantenimiento bancario.
  • Artículos relacionados:
    Knightmare: A DevOps Cautionary Tale (2014) - https://news.ycombinator.com/item?id=22250847 - febrero de 2020, 33 comentarios
    Knightmare: A DevOps Cautionary Tale (2014) - https://news.ycombinator.com/item?id=8994701 - febrero de 2015, 85 comentarios
    Knightmare: A DevOps Cautionary Tale - https://news.ycombinator.com/item?id=7652036 - abril de 2014, 60 comentarios
    Además:
    The $440M software error at Knight Capital (2019) - https://news.ycombinator.com/item?id=31239033 - mayo de 2022, 172 comentarios
    Bugs in trading software cost Knight Capital $440M - https://news.ycombinator.com/item?id=4329495 - agosto de 2012, 1 comentario
    Knight Capital Says Trading Glitch Cost It $440 Million - https://news.ycombinator.com/item?id=4329101 - agosto de 2012, 90 comentarios
    ¿Habrá otros?

  • El verdadero problema, si se me permite una formulación del tipo “verdadero escocés”, fue usar una combinación no probada de configuración y release binario.
    La configuración y los binarios pueden desplegarse de manera coordinada al mismo tiempo, y eso permite evitar este tipo de problemas. Por supuesto, hubo otros errores, pero sin esa condición este problema no habría podido ocurrir.

  • La parte que dice “es un misterio por qué un código que llevaba 8 años muerto seguía en el codebase, pero ese no es el punto” quizá no sea el peor error de la historia, pero tampoco se puede decir que no sea el punto.
    Si hubieran eliminado proactivamente esa funcionalidad muerta, habrían tenido un software más simple y mejor comprendido, y con menos posibilidades de descontrolarse. Seguir avanzando sin cesar, sin este tipo de mantenimiento, es un riesgo, sea calculado o no.

  • Me alegra mucho no escribir código que enruta automáticamente millones de dólares sin intervención humana.
    Es como escribir el código que hace volar un jumbo jet. ¿Quién querría asumir esa responsabilidad?

    • Esa responsabilidad en sí está bien, pero realmente tendría que ser mi responsabilidad. Es decir, aunque mi jefe quiera lanzar mañana, debería tener la autoridad para decir “no lanzamos hasta que se arregle XYZ”, incluso si construir XYZ toma dos años más.
    • Si se hace bien, no da miedo. Y hacer las cosas bien puede parecer algo tremendamente aburrido.
      Creo que es un trabajo adecuado para cierto tipo de persona que ama los procesos, las pruebas, los simuladores y la redundancia. El código que hace volar el avión en sí apenas representa el 1% de la ingeniería.
    • Al principio genera ansiedad, pero con buenos controles y monitoreo se vuelve rutina.
      Basta con ir resolviendo una por una las preocupaciones que surgen naturalmente, y cuanto más razonablemente ansioso seas, mejor para el negocio. Por mi experiencia en el sector financiero, diría que el problema de Knight fue 10% un problema técnico y 90% un problema de alguien parecido a un CTO que confundió audacia con temeridad. No solo ese día o esa semana, sino en general.
    • No sé si todas las empresas son así, pero normalmente, cuando el software coloca órdenes en una bolsa, muchas personas monitorean de cerca lo que está ocurriendo.
      Supongo que este incidente también tuvo algo que ver con eso.
    • Me alegra mucho no haber desperdiciado mi vida trabajando en finanzas.