BookinglyTech News
Infraestructura

Blue-green no te salva de una migración destructiva en Postgres

El despliegue pasó a verde en 90 segundos, pero una migración que eliminó la columna status dejó todas las peticiones a /orders fallando. El rollback no existía: la base de datos no se revierte como el código.

3 min de lecturaDev.to0 vistas

El despliegue pasó a verde en noventa segundos. Poco después, la tasa de error en azul llegó al 100% y toda petición a /orders devolvía column "orders.status_v2" does not exist. Dos pilas de aplicación sanas y ningún rollback funcional. La causa no fue el enrutador: fue una migración que eliminó la columna status antes de que el código antiguo dejara de usarla.

Blue-green da dos copias del código y un enrutador que salta entre ellas. No da dos copias de la base de datos. No se puede bifurcar un clúster Postgres vivo, con escrituras continuas, y fusionar las ramas después. Ambos entornos apuntan al mismo host y al mismo esquema. En cuanto termina migrate, blue ejecuta código nuevo contra un esquema nuevo y green ejecuta código viejo contra ese mismo esquema nuevo. El intercambio es atómico; el cambio de esquema, no.

La migración era ALTER TABLE orders DROP COLUMN status; y luego ALTER TABLE orders ADD COLUMN status_v2 text NOT NULL DEFAULT 'pending';. El orden importa. Green seguía haciendo SELECT status. Entre el DROP y el momento en que green deja de servir, cada consulta de green falla, porque sigue sirviendo durante el drenaje del enrutador, las peticiones en vuelo y los reintentos. El plan de rollback decía "volver a blue". Blue ya estaba roto por una migración que se ejecutó antes del salto.

El rollback no es simétrico

El rollback de código es cambiar un puntero. El artefacto antiguo sigue ahí y funciona mientras los datos encajen. El de esquema no es simétrico. Lo contrario de DROP COLUMN es ADD COLUMN, pero los valores ya no están. No se puede deshacer un DROP, ni un TRUNCATE, ni un UPDATE que reescribió filas en su sitio. La base de datos tiene un solo estado, avanza hacia adelante y no tiene una versión anterior a la que apuntar.

Hay un tercer caso, peor: la migración falla a medias. Postgres ejecuta la mayor parte del DDL en una transacción, así que un ALTER TABLE fallido dentro de BEGIN revierte limpio. Pero un archivo con varias sentencias, algunas CONCURRENTLY o con commits explícitos entre pasos, deja un esquema que no es ni el viejo ni el nuevo.

Expandir, contratar, bandera

El camino seguro tiene tres versiones, no dos. Primero, expandir: añadir la columna nueva como nullable y escribir en ambas desde la aplicación. El código viejo lee la vieja; el nuevo escribe las dos. Si haces rollback, no rompe nada. Segundo, rellenado por lotes, fuera del despliegue, con una consulta que puedas detener y reanudar sin repetir trabajo. Tercero, cambiar las lecturas a la columna nueva detrás de una bandera. La bandera es el rollback. Si las lecturas fallan, giras la bandera, no el despliegue. Solo después de un ciclo completo de tráfico se contrae: se elimina la columna vieja en una versión aburrida donde el código ya no la referencia.

Si la migración ya está a medias, para el despliegue. No hagas rollback de la aplicación ni lances una segunda migración para "arreglar" la primera. Mira qué ha quedado de verdad. En Postgres, consulta information_schema.columns en vez de fiarte de la tabla de migraciones. Comprueba si la migración tiene un lock con pg_stat_activity y pg_locks: un ACCESS EXCLUSIVE bloqueado por una lectura larga puede reventar tu siguiente despliegue. Luego elige una dirección. Si la forma vieja sigue existiendo, desactiva el camino nuevo con la bandera y deja el esquema como está. Si ya no está, solo queda avanzar: despliega código que case con el esquema actual y parchea las consultas que fallan hoy.

La base de datos no es un destino de despliegue. Es una dependencia compartida de un solo estado, y cada migración es un cambio en datos de producción. Ensaya la migración sobre una copia y cronometra cuánto dura el lock.