Quant500 Academy

3. Desplegar sin romper: versiones, vuelta atrás y cambios en caliente

Nivel 12: Probar, desplegar y dar el paso

Un despliegue es cambiar una pieza de un sistema que tiene dinero puesto. La diferencia con cualquier otro programa es que aquí el estado no vive solo en tu disco: vive también en el bróker, en forma de órdenes vivas y posiciones abiertas que no se enteran de que has hecho un cambio de versión.

El despliegue no coincide con el mercado abierto

La ventana se define en el mismo calendario que el orquestador del nivel 9 ya conoce, y no basta con que el mercado esté cerrado. Hay que parar el proceso a propósito, comprobar que no queda nada vivo y solo entonces cambiar nada. Ese orden importa: comprobar con el proceso todavía en marcha es comprobar una foto que puede caducar mientras copias ficheros.

from datetime import datetime, timezone

def guardia_de_despliegue(calendario, broker, estado):
    ahora = datetime.now(timezone.utc)
    if calendario.mercado_abierto(ahora):
        raise SystemExit("mercado abierto: despliegue abortado")
    if broker.ordenes_abiertas():
        raise SystemExit("hay ordenes vivas: cancelalas o espera a que venzan")
    if estado.fase != "reposo":
        raise SystemExit("la maquina de estados no esta en reposo")
    return True

Volver atrás en un minuto

El criterio no es "tener copia de seguridad": es que la versión anterior siga instalada y arrancable con un solo comando. La forma más simple que funciona: cada versión en su propio directorio, con su propio entorno, y un enlace llamado actual que apunta a la que está en uso. Desplegar es mover el enlace y arrancar; volver atrás es moverlo al directorio anterior y arrancar. Segundos, no una reconstrucción.

Tres condiciones para que eso sea cierto de verdad:

Y el detalle que casi nadie cumple: la vuelta atrás hay que ensayarla. Un rollback que nunca se ha ejecutado es una intención, no un procedimiento.

El estado tiene que sobrevivir al cambio de versión

Aquí se rompen los despliegues aparentemente inocentes. El estado persistente del nivel 8 lo escribió la versión vieja y lo va a leer la nueva. Si la nueva añade un campo obligatorio, cambia el significado de uno existente o renombra una fase, el arranque falla; o peor, no falla y hace otra cosa. La disciplina es la de cualquier base de datos: el estado lleva número de versión de esquema y el arranque lo comprueba.

ESQUEMA_ESPERADO = 7

def migrar_6_a_7(e):
    e = dict(e)
    e["precio_referencia"] = e.get("precio_decision")
    e["version"] = 7
    return e

MIGRACIONES = {6: migrar_6_a_7}

def cargar_estado(bruto):
    v = bruto.get("version", 0)
    while v < ESQUEMA_ESPERADO:
        if v not in MIGRACIONES:
            raise RuntimeError("no se como migrar el estado desde esa version")
        bruto = MIGRACIONES[v](bruto)
        v = bruto["version"]
    if v > ESQUEMA_ESPERADO:
        raise RuntimeError("estado escrito por una version mas nueva: no arranco")
    return bruto

El caso peligroso es el último: una versión vieja leyendo un estado que escribió una nueva. Ocurre justo cuando vuelves atrás, que es el peor momento posible para descubrirlo. Por eso el ensayo del rollback se hace con el estado de la versión nueva delante, no con uno limpio.

Órdenes vivas mientras despliegas

Tres políticas, y se elige una por escrito antes de necesitarla:

Lo que no es una política es parar el proceso y confiar en que no hubiera nada.

La configuración caliente es la vía rápida al desastre

Bajar un límite de exposición editando un fichero en el servidor a las once de la mañana funciona, y ese es el problema: funciona sin dejar rastro. Nadie sabrá dentro de un mes qué valor estaba activo, el siguiente despliegue lo revertirá en silencio y el análisis del incidente del nivel 11 se hará sobre una configuración que no era la que había. La configuración se trata como código: repositorio, revisión, mismo procedimiento de despliegue, y el sistema registra al arrancar el hash del fichero que ha cargado. Si algún parámetro debe poder cambiarse en caliente, y los interruptores del nivel 11 lo necesitan, ese cambio pasa por una vía que escribe quién, cuándo, valor anterior y valor nuevo.

Knight Capital, 1 de agosto de 2012

Knight Capital era uno de los mayores creadores de mercado de Estados Unidos. Desplegó código nuevo en sus servidores de ejecución y en uno de los ocho la actualización no llegó. El código nuevo reutilizaba una bandera de configuración que en el código antiguo activaba una función de pruebas retirada años antes. Al abrir el mercado, siete servidores hicieron lo previsto y el octavo empezó a ejecutar el comportamiento viejo, comprando y vendiendo sin freno a ritmo de mercado.

En menos de una hora la pérdida antes de impuestos rondó los 440 millones de dólares y dejó a la firma al borde de la quiebra; sobrevivió con una inyección de capital de emergencia y acabó absorbida por otra compañía, y la SEC la sancionó después por deficiencias en sus controles. Fallaron cuatro cosas de este módulo a la vez: un despliegue que no verificó que las ocho máquinas quedaran iguales, una bandera reutilizada en lugar de retirada, código muerto todavía instalado y ningún corte automático por pérdida acumulada.

El resto de Quant500

Lo que estás aprendiendo, aplicado al índice todos los días:

La cartera del modelo, cada semana en tu correo

Cada sábado, su semana en un minuto: cómo fue, lo mejor y lo peor, y qué directivos compraron con su dinero. Y el día que el modelo se rehace, qué entra y qué sale. Es una simulación, no un consejo. Como mucho un correo a la semana, sin publicidad y sin cuenta; te das de baja con un clic. Lo hace una persona: Pol Gracia.

Cartera modelo con resultados simulados, no una recomendación personalizada. Tu correo se usa solo para esto: privacidad.

Detrás de la Academia y del resto de Quant500 hay una sola persona: Pol Gracia Serrahima.