¿Puede una migración sin tiempo de inactividad ser reversible?
Planifique una migración sin tiempo de inactividad con enrutamiento Strangler, escrituras dobles, conciliación y un cambio reversible.

Una migración sin tiempo de inactividad solo es posible si el sistema antiguo sigue siendo un destino válido hasta que el nuevo demuestre que puede asumir el trabajo. Parece obvio, pero muchos planes destruyen esa opción. Copian datos, despliegan el reemplazo, programan mantenimiento y llaman «cambio» a la interrupción. La interrupción no es una ley natural. Aparece al unir cuatro acciones distintas: cambiar la ruta, cambiar quién escribe, trasladar la autoridad sobre los datos y eliminar la ruta antigua.
Un diseño más seguro separa esas acciones y hace que cada una sea observable y reversible. Las solicitudes pasan por un punto de enrutamiento. Las escrituras llevan identidades estables y pueden reproducirse. La carga histórica tiene una posición definida en el flujo de cambios. La conciliación compara significado empresarial, no formas de bytes. El último cambio de ruta se convierte en una pequeña modificación de configuración, no en un salto al vacío.
Esto no significa que los usuarios nunca verán un error. Un sistema puede seguir disponible mientras una solicitud falla por los mismos motivos que antes. La promesa es más concreta y útil: la migración no exige un periodo en el que el servicio rechace todo el trabajo, y los operadores pueden devolver el tráfico sin reconstruir la base de datos de ayer.
La disponibilidad depende del enrutamiento
La aplicación sigue disponible cuando cada solicitud tiene un destino válido durante toda la migración. Replicar datos ayuda, pero no basta. Si los clientes se conectan directamente al servidor que va a sustituirse, la decisión de ruta vive en cientos de clientes y el equipo no controla el cambio.
Coloque un punto de decisión propio delante de ambas implementaciones. Puede ser una puerta de enlace API, un proxy inverso, una regla del balanceador, la asignación de un consumidor o un adaptador dentro del proceso existente. La tecnología importa menos que el contrato: los operadores deben poder cambiar el destino de una parte concreta sin desplegar de nuevo todos los clientes.
Elija esa parte según un límite empresarial estable. Enrute la consulta de cuentas aparte de sus cambios, la exportación de facturas aparte de su creación, o un inquilino aparte del resto. No corte por el controlador más fácil de reemplazar. Una ruta que mezcla lecturas, escrituras, tareas programadas y callbacks puede mostrar un canario verde mientras un camino no observado sigue alterando datos antiguos.
Un registro mínimo de ruta debe ser tan sencillo que pueda revisarse durante un incidente:
{
"capability": "invoice.read",
"cohort": "tenant-042",
"destination": "new",
"fallback": "old",
"revision": 17,
"changed_by": "change-1842"
}
capability nombra el comportamiento, no una URL. cohort limita la exposición. fallback indica adónde puede enviar el router una solicitud si el lado nuevo no está sano. La revisión hace visibles los cambios simultáneos y la referencia explica por qué se movió la ruta. Guarde el registro con historial de auditoría y haga que el router conserve la última configuración válida si desaparece el plano de control.
Las comprobaciones de salud deben probar la capacidad enrutada. Un proceso que responde a /health puede carecer de una migración de base, una clave de descifrado o una dependencia accesible. Ejecute una lectura pequeña u operación sintética que recorra la misma cadena que el tráfico real, con datos reservados. Sea prudente con el retorno automático de escrituras. Repetir una lectura en el lado antiguo suele ser inocuo; repetir una escritura aceptada puede crear otro pedido, pago o expediente.
El punto Strangler debe controlar todas las entradas
El enrutamiento Strangler solo funciona si su punto de entrada intercepta todas las formas de acceder a una capacidad. La descripción de Martin Fowler del patrón Strangler Fig insiste en sustituir gradualmente alrededor del sistema antiguo. Los equipos suelen recordar «gradualmente» y olvidar «alrededor». Si una tarea nocturna, un cliente de escritorio, un archivo o una cola lo evita, dos implementaciones actúan sin una política común.
Inventaríe entradas a partir de pruebas de ejecución, no del diagrama. Revise registros de acceso, enlaces de colas, definiciones del planificador, flujos de cortafuegos, lenguaje de control de lotes, llamadores de procedimientos y callbacks salientes que vuelven como trabajo entrante. Los sistemas antiguos suelen exponer la misma operación por HTTP, una transacción de terminal y un archivo importado de noche. Trátelos como una capacidad con varios adaptadores.
El punto debe normalizar identidad y contexto antes de despachar. Ambos lados necesitan el mismo ID de solicitud, actor, inquilino, resultado de autorización, plazo y clave de idempotencia. Si cada implementación los deriva por separado, la conciliación culpará a la lógica empresarial de diferencias creadas en el borde. Conserve el cuerpo original para auditoría cuando la política lo permita, pero entregue a ambos caminos un sobre canónico versionado.
No empiece con enrutamiento porcentual para trabajo con estado. Un canario aleatorio del diez por ciento puede enviar el primer paso al lado nuevo y el siguiente al antiguo. Use una clave determinista como inquilino, cuenta, caso o ID de flujo. El router debe responder igual para esa clave hasta que un operador cambie la regla.
La autenticación es otra entrada. Si el sistema antiguo crea sesiones que el nuevo no valida, la primera solicitud enrutada obliga a iniciar sesión. Valide la sesión en el punto común y pase una afirmación de identidad firmada y breve, o enseñe a ambos lados un formato compartido durante la coexistencia. No migre hashes de contraseñas obligando a restablecerlas salvo que la empresa acepte esa interrupción.
Haga visible la evaluación de ruta en cada traza y registro. Anote revisión, destino elegido, decisión de retorno y clave de cohorte. Si un usuario dice que la factura de ayer difiere de la de hoy, debe saber qué implementación respondió cada vez. Un panel con porcentajes agregados no responde esa pregunta.
Las escrituras dobles necesitan una intención duradera
La forma segura de escribir dos veces registra una intención empresarial duradera y deja que trabajadores independientes la apliquen a cada modelo. La forma insegura hace que el hilo llame a la base A y luego a la B, esperando dos éxitos. Existe una brecha inevitable: el primer commit puede funcionar y el segundo agotar su tiempo, sin que el cliente sepa si reintentar duplicará el trabajo.
Haga que la autoridad actual confirme el cambio empresarial y un evento outbox en la misma transacción local. Un relé publica el evento y un consumidor aplica una proyección idempotente al almacén nuevo. Al principio, el almacén antiguo conserva la autoridad aunque la proyección lleve unos segundos de retraso. Después de mover la ruta, la dirección puede invertirse para mantener el retorno.
El sobre de escritura debe contener información suficiente para rechazar duplicados y detectar desorden:
{
"event_id": "01J7M6R2K8N4T3Q9V5X1",
"aggregate_type": "invoice",
"aggregate_id": "inv-90318",
"aggregate_version": 44,
"operation": "invoice.adjusted",
"occurred_at": "2026-08-14T10:42:31Z",
"payload": {"line_id": "ln-8", "amount_minor": 1250}
}
El consumidor guarda event_id en una tabla de eventos procesados con restricción única. Aplica la versión 44 solo después de la 43, o aparta el evento hasta recibir la versión ausente. La carga usa unidades empresariales, como unidades monetarias menores, y no cadenas formateadas. Una entrega duplicada devuelve el resultado previo sin ejecutar de nuevo la operación.
La documentación de reintentos de Microsoft explica el riesgo: un servicio puede terminar el trabajo y perder la respuesta, por lo que un reintento repite una operación no idempotente. Las migraciones crean esta situación con frecuencia al reiniciar relés y cambiar rutas de red. La clave de idempotencia no es solo comodidad de API. Demuestra que dos entregas representan una intención.
Algunas escrituras no pueden reproducirse con seguridad, sobre todo llamadas a terceros sin idempotencia. Mantenga ese efecto detrás de la autoridad existente durante la coexistencia. Replique el estado resultante, no el comando que lo causó. Enviar la misma orden de pago desde ambos sistemas genera dos pagos.
Observe la edad del outbox, no solo sus filas. Una cola pequeña con un evento bloqueado durante seis horas puede ser peor que una grande que se vacía normalmente. Muestre el evento sin publicar más antiguo, el no aplicado más antiguo por agregado, reintentos, motivo de descarte y salto de versión. Esas señales muestran si los datos para volver están al día.
La carga histórica debe alcanzar el flujo vivo
Una carga histórica termina cuando su instantánea se une al flujo de cambios en una posición conocida. Copiar filas mientras la producción sigue crea un blanco móvil. Si el copiador lee una cuenta antes de una actualización y la escribe después de que el consumidor la aplique, la instantánea antigua puede sobrescribir estado nuevo.
Hay dos patrones correctos. Tome una instantánea coherente ligada a una posición del registro, cárguela y aplique los cambios posteriores. O condicione cada upsert histórico a la versión de origen para que no reemplace una proyección más nueva. La sintaxis de captura cambia por producto, pero no la invariante: todo registro y evento debe tener un orden comparable.
La replicación lógica de PostgreSQL muestra ayuda y límites. Su documentación dice que los cambios siguen a la instantánea inicial en orden del publicador dentro de una suscripción. También advierte que las definiciones de esquema y DDL no se replican en versiones habituales, y que el estado de secuencias ha requerido tratamiento separado antes del cambio. El manual de la versión exacta forma parte del runbook. «La replicación alcanzó el presente» no prueba que el destino acepte escrituras.
Regule la carga según la latencia de producción, no con una cifra fija de filas por segundo. Lea rangos de clave primaria, registre el rango terminado y la posición, y haga reiniciable cada lote. Las transacciones grandes retienen registros, alargan bloqueos y ocultan progreso. Las diminutas desperdician recursos. Mida el efecto y elija un tamaño que termine y se repita dentro de los límites operativos.
Las transformaciones necesitan almacenamiento explícito de fallos. Si un estado antiguo contiene un valor que el enum nuevo rechaza, no lo convierta silenciosamente en UNKNOWN. Guarde clave y versión de origen, versión del transformador, valor bruto y error. El responsable empresarial decidirá si corresponde a un estado existente, necesita uno nuevo o revela corrupción anterior.
Nunca cargue totales derivados sin decidir quién los recalcula. Si el sistema nuevo deriva el saldo de entradas y el antiguo guarda una columna mutable, compare entradas y saldo final, pero no copie esa columna para siempre. De lo contrario sobreviven dos autoridades en el esquema nuevo.
La conciliación compara invariantes
La conciliación debe probar que ambos sistemas hacen las mismas afirmaciones empresariales aunque sus esquemas difieran. Recuentos y checksums detectan datos ausentes, pero fallan como prueba principal tras rediseñar la arquitectura. Un modelo Postgres normalizado no tiene las mismas filas que un registro COBOL, y un cliente TypeScript no serializa campos en el mismo orden que una aplicación de escritorio.
Empiece con invariantes que la empresa ya usa: cada asiento publicado pertenece a una cuenta, débitos y créditos cuadran dentro de un límite, la factura equivale a líneas más impuesto, un caso cerrado tiene evento de cierre y una referencia externa sigue siendo única. Ejecute cada invariante en ambos lados y compare por ID empresarial estable.
Para campos que deben coincidir, normalice solo diferencias sin significado empresarial. Unifique precisión de fechas, Unicode, cadenas vacías y nulos según el comportamiento antiguo, y dinero en unidades menores enteras. No convierta identificadores a minúsculas ni redondee números solo para poner verde el informe. Toda regla puede ocultar un defecto, por eso debe versionarse y revisarse.
Una consulta útil devuelve discrepancias, no un recuento aprobado:
SELECT account_id, old_balance_minor, new_balance_minor,
old_balance_minor - new_balance_minor AS delta_minor
FROM migration_account_balance
WHERE old_balance_minor <> new_balance_minor
ORDER BY ABS(old_balance_minor - new_balance_minor) DESC;
La forma de salida importa: el operador necesita ID empresarial, ambos valores y diferencia. Guarde ID de ejecución, posiciones de origen y destino, versión de consulta, horas, número de discrepancias y una muestra limitada. Un informe sin posiciones no puede reproducirse porque ambas bases siguen cambiando.
Clasifique discrepancias por causa. Una brecha de transporte indica que un evento no llegó. Una de orden indica que llegó pronto o tarde. Un fallo de transformación crea el destino incorrecto desde la fuente correcta. Las diferencias esperadas proceden de cambios deliberados, como quitar espacios finales. Cualquier diferencia sin explicar bloquea ampliar la cohorte aunque sea pequeña.
No exija cero diferencias cuando el tiempo cambia la respuesta. Una oferta que caduca o un inventario vivo pueden variar entre lecturas sucesivas. Congele el reloj relevante, compare posiciones iguales o evalúe una tolerancia aprobada por negocio. «Suficientemente cerca» decidido por migración no es un criterio de aceptación.
Las lecturas sombra revelan el comportamiento
Las lecturas sombra dejan que el sistema nuevo procese solicitudes reales mientras la respuesta antigua llega al usuario. Encuentran defectos semánticos que los controles de datos omiten: orden predeterminado, casos de autorización, redondeo, formato local, registros ausentes y errores distintos. Como se descarta el resultado nuevo, solo son seguras para operaciones sin efectos.
Clone la solicitud normalizada en el punto común y fije un plazo menor que el presupuesto del usuario. Una sombra lenta nunca debe retrasar la respuesta con autoridad. Quite o sustituya secretos innecesarios y marque la solicitud para que el código posterior no envíe correo, cambie cachés, prolongue sesiones ni produzca llamadas facturables.
Compare significado estructurado, no bytes. Ignore ID de traza y marcas temporales generadas. Compare clase de estado, colecciones ordenadas o no según contrato, decisión de autorización, campos seleccionados, categoría de error y totales. Guarde una muestra censurada por nueva firma de diferencia, no cada respuesta. De otro modo, el almacén de comparación será otra copia de datos sensibles.
Ejecutar escrituras sombra y revertir la transacción suele ser inseguro. Llamadas externas, asignación de secuencias, publicación de colas y triggers pueden escapar. Valide la escritura con tráfico grabado en un entorno aislado, o ejecute su lógica pura sobre una instantánea suprimiendo el adaptador de commit. Sea exacto sobre lo probado.
Las comparaciones de rendimiento exigen igual cuidado. Una sombra posterior puede usar una caché caliente; en paralelo añade carga que ningún sistema ve solo. Mida latencia y recursos de ambos, pero no declare ganador sin controlar caché, mezcla de solicitudes y carga adicional.
El criterio útil combina cobertura y comportamiento limpio. Registre qué operaciones, roles, formas de datos y caminos de error se probaron. Diez millones de consultas comunes no demuestran la rara reversión. Enrute una cohorte solo cuando su conjunto real se haya observado o probado deliberadamente.
El cambio es una máquina de estados
Un cambio reversible mueve una capacidad por estados nombrados con transiciones protegidas. Una cita de calendario puede autorizarlo, pero el reloj no decide si es seguro. Los operadores deben ver autoridad, ruta, dirección de replicación, retraso y acción de retorno en una sola página.
Use estados que describan hechos:
old_only: el lado antiguo sirve y escribe; el nuevo puede estar vacío.old_authority: ambos reciben datos actuales; los usuarios siguen en el antiguo.new_canary: una cohorte determinista usa el nuevo; las escrituras antiguas siguen actuales.new_primary: todo el tráfico apto usa el nuevo; el antiguo queda listo para volver.new_only: cerró la ventana de retorno y se desactivaron mutaciones antiguas.
Cada transición necesita condiciones legibles por máquina. Para new_canary pueden exigirse cero discrepancias sin explicar, ningún evento por encima del retraso permitido, cobertura sombra de operaciones críticas y retorno de ruta probado. new_primary añade capacidad libre, propiedad de tareas, soporte preparado y confirmación de que entradas no HTTP respetan la misma autoridad.
Mantenga el comando pequeño e idempotente. Debe actualizar una ruta versionada, no desplegar código, cambiar esquemas, vaciar colas y reiniciar procesos. Si cinco acciones deben ocurrir en un minuto preciso, alguna llegará tarde y el retorno será ambiguo.
Separe detener de volver. Detener congela la expansión mientras ambos lados conservan sus papeles. Volver envía tráfico al antiguo porque falló una condición. Reparar datos corrige el estado tras entender el fallo. Copiar automáticamente el destino hacia el origen durante una alarma puede propagar corrupción antes de diagnosticarla.
Practique la transición inversa en producción antes del cambio amplio. Envíe una cohorte al nuevo, cree y actualice registros representativos, devuélvala al antiguo y confirme que siguen correctos y accesibles. Un documento que nunca movió estado real es solo teoría.
El retorno caduca cuando el sistema antiguo deja de aprender
El sistema antiguo solo sirve para volver mientras recibe todo cambio necesario para retomar la autoridad. Tener servidores encendidos no sirve si la base quedó atrás tras la primera escritura nueva. Defina la ventana mediante datos: qué operaciones se replican atrás, qué retraso se acepta y qué cambios no caben en el modelo antiguo.
La replicación inversa se complica cuando la arquitectura nueva admite estados que el esquema antiguo no expresa. Retrase esas funciones o añada una representación compatible antes del cambio. Si el sistema nuevo permite varios ajustes y el registro antiguo solo un importe, no puede prometer retorno después del segundo ajuste salvo que el camino antiguo pueda conservarlo.
Use una secuencia de expansión y contracción del esquema. Añada campos y lectores compatibles con ambas formas. Rellene la nueva. Cambie escritores. Observe. Quite la antigua al cerrar la ventana. Cambios destructivos, valores enum reutilizados y campos acortados borran el retorno aunque el enrutamiento parezca reversible.
El trabajo programado debe tener un solo dueño. La ruta puede enviar tráfico interactivo al lado nuevo mientras dos planificadores generan extractos o cierran casos. Dé a cada tarea un arrendamiento o indicador regido por el mismo estado y registre quién reclamó cada ejecución. Al volver, transfiera el arrendamiento antes que la ruta si la tarea cambia datos leídos por el camino antiguo.
CodeHero incluye este contrato de coexistencia en la reescritura: el nuevo sistema en Go, Rust o TypeScript se comprueba contra tráfico de producción grabado con un arnés de paridad, y el proyecto se entrega en menos de 30 días. Esa promesa breve no sustituye las condiciones de aceptación ni la política de retorno del cliente; impide ocultarlas en un programa largo.
Defina la condición que termina la reversibilidad. Puede ser el primer estado exclusivo del sistema nuevo, borrar la replicación inversa, un cambio destructivo o el fin del acuerdo para mantener ambos lados. El propietario responsable debe aprobarla. Si nadie puede nombrarla, el equipo la descubrirá durante el incidente cuando ya no pueda volver.
El último cambio de ruta debe ser rutinario
El cambio final es seguro cuando solo modifica la ruta predeterminada de una capacidad cuyas cohortes ya funcionaron en el lado nuevo. Código, datos, tareas, controles, paneles, instrucciones de guardia y retorno ya están en producción. El riesgo restante es la escala, así que capacidad y colas merecen más atención que la corrección funcional.
Antes, capture una decisión con revisión exacta, posiciones de replicación, ejecución de conciliación, excepciones abiertas, aprobador y umbral de retorno. Compruebe que el lado antiguo absorbe toda la carga inmediatamente. Reducirlo antes de cerrar la ventana ahorra poco y convierte una edición reversible en una restauración de capacidad.
Cambie la ruta en etapas que coincidan con el dominio de fallo. Un inquilino revela datos específicos. Una región revela ubicación de dependencias. Un porcentaje sirve solo cuando la afinidad de flujos está garantizada. Espere entre etapas hasta observar la tarea o callback relevante más lento, no un gráfico arbitrario de cinco minutos.
Observe síntomas del usuario: categoría de error, latencia de cola, edad de la cola, invariantes fallidos, rechazos de autorización y contactos de soporte de la cohorte. CPU y memoria pueden estar tranquilas mientras las facturas desaparecen de búsqueda por un consumidor detenido. Vincule cada umbral a una señal y ventana nombradas.
Asigne a un operador el comando y a otro la lectura de condiciones. El segundo debe poder parar sin negociar en el canal del incidente. Registre ambas decisiones automáticamente. Esta separación detecta paneles obsoletos, reglas mal entendidas y el error de considerar el silencio como aprobación.
Cuando salte un umbral, ejecute el retorno probado y preserve pruebas. No improvise una reparación hacia delante mientras se acumulan errores. Cuando el tráfico esté estable en el lado antiguo, congele si hace falta las escrituras destino, registre posiciones y diagnostique. La reversibilidad compra tiempo solo si se usa.
Al cerrar la ventana, retire el mecanismo deliberadamente. Desactive escritores antiguos, revoque credenciales, detenga consumidores inversos, archive pruebas según política y conserve el historial de rutas. Un relé olvidado puede revivir datos obsoletos meses después. Un fallback olvidado puede enviar unas pocas solicitudes a una aplicación sin vigilancia.
La migración termina cuando el sistema antiguo deja de ser necesario, no cuando la primera solicitud llega al nuevo. Hasta entonces, trate estado de ruta, posición de evento y pruebas de conciliación como datos de producción. Si falta alguno, el cambio depende de la memoria, justo cuando la alarma la vuelve menos fiable.
Preguntas frecuentes
¿Puede una migración realizarse realmente sin inactividad?
Sí, si cada solicitud mantiene un destino válido mientras ruta y autoridad de datos cambian por separado. Significa que la migración no impone una caída total, no que toda solicitud tenga éxito garantizado.
¿Qué es el enrutamiento Strangler en una migración?
Coloca un punto de decisión controlado alrededor de implementaciones antigua y nueva, y mueve una capacidad o cohorte cada vez. Falla si tareas, colas, clientes o callbacks lo evitan.
¿Son seguras las escrituras dobles durante una migración?
Dos escrituras directas en el hilo no lo son, pues una puede confirmar y la otra fallar. Confirme cambio y outbox juntos, y aplique después un evento idempotente al otro almacén.
¿Cómo se cargan datos mientras los usuarios siguen escribiendo?
Vincule la instantánea a una posición y aplique los eventos posteriores en orden. Otra opción es condicionar cada upsert a la versión de origen para que datos antiguos no sobrescriban eventos nuevos.
¿Qué debe comparar una conciliación?
Compare invariantes empresariales e identificadores estables, no filas brutas. Incluya ambos valores, diferencia, posiciones, versión y pruebas suficientes para reproducir cada discrepancia.
¿Cuándo son seguras las lecturas sombra?
En operaciones sin efectos, con un plazo propio que nunca retrase al usuario. Márquelas para impedir mensajes, cambios de sesión, eventos o llamadas facturables posteriores.
¿Cuánto tráfico debe recibir un canario?
Empiece con una cohorte determinista y no un porcentaje aleatorio. Debe ser fácil de revertir y rica para cubrir flujos completos, roles, tareas y callbacks.
¿Qué hace reversible un cambio?
El lado antiguo debe estar actualizado, operativo y listo para toda la carga. Se necesitan flujo inverso, esquemas compatibles, un dueño por tarea y un retorno probado con estado real.
¿Cuándo se cierra la ventana de retorno?
En un límite explícito y aprobado, como el primer estado exclusivo nuevo o un cambio destructivo. No la cierre solo porque la nueva ruta lleva unas horas tranquila.
¿Qué ocurre después del cambio final?
Desactive escritores antiguos, revoque credenciales, pare consumidores inversos, preserve pruebas y retire rutas de retorno. La aplicación antigua se retira cuando ningún camino admitido puede escribirle ni devolverle trabajo.