Ir al contenido

Blog ·

Proyectar, nunca sustituir

Una vista de progreso sobre migraciones, copias, restauraciones y reinstalaciones, sin volverse un segundo registro. Dos fallos fueron del todo silenciosos.

Por Provibr

Un panel de hosting acumula trabajo de larga duración. Una migración entre dos paneles de control, una copia de seguridad, una restauración hacia una máquina nueva, un contenedor que se sustituye. Cada uno creó su propia tabla, su propio vocabulario de estados y su propia pantalla, y eso funciona hasta que alguien hace la pregunta evidente: qué se está ejecutando ahora mismo en mi cuenta, y si algo de ello está atascado.

La respuesta tentadora es un sistema de tareas. Una tabla, una lista de estados, todo lo demás escribe dentro. La respuesta tentadora es también el camino hacia dos fuentes de verdad sobre el mismo hecho, y hacia una conversación de soporte un año después en la que la tarea dice que falló y la tabla de copias dice que el archivo está en el almacenamiento.

Por eso la vista de operaciones es una proyección. Lee las filas existentes y escribe un resumen encima. Nunca va nada en sentido contrario: ni una sola función del módulo de espejo toca las ejecuciones de copia, las de restauración, las de migración ni las reinstalaciones. Esas tablas siguen siendo la verdad, y al resumen se le permite estar desfasado, equivocado o ausente sin que se rompa nada más allá.

Un espejo por tabla, y una prueba que lee el esquema

Cuatro tablas se apoyan en esta costura, y cada una tiene exactamente una función que la proyecta.

Tabla de origenColumna de estadoTipo de operación
backup_runsstatusbackup.run
backup_restore_runsstatusbackup.restore
migration_runsstatusmigration.run
container_reinstall_intentsstatecontainer.reinstall

La última fila es el motivo de que los nombres de columna estén escritos como texto y no se den por supuestos. Esa tabla llama a la columna state, y un espejo que lea status no recibe nada y cae sin quejarse en la rama de fallo.

Esa tabla no es documentación. Una prueba deduce del esquema de la base de datos cuáles de esas tablas tienen columna de estado, exige una entrada aquí por cada una, exige que la función nombrada exista de verdad y exige, y esta es la parte que hace el trabajo real, que todo archivo que escriba una de esas columnas nombre también su espejo. Una quinta tabla que llegue a la costura sin proyección es así una prueba en rojo en vez de un cajón que deja de moverse en silencio.

Tiene que ser así de estricto porque los escritores están repartidos: una ejecución de copia cambia de estado en seis puntos de un mismo archivo, tres de ellos en el camino que procesa las respuestas de la reconciliación que corre de noche. Dejar que cada escritor invoque la proyección por su cuenta significa que el sexto se olvida, y olvidarse es invisible. La fila es correcta. Solo que la pantalla no se actualiza nunca.

El sumidero que existía dos veces

El reflejo vive en el paquete que posee las escrituras en base de datos, porque tanto una acción de servidor de la aplicación web como un componente de la pasarela llaman a las mismas funciones. Ese paquete no puede publicar un evento en vivo: depende de la base de datos y de código compartido y de nada más, y el bus de mensajes es un objeto distinto a cada lado.

Así que expone un sumidero. Cada aplicación registra uno, y el paquete no sabe quién lo rellenó. Si nadie lo hace, la escritura en base de datos sigue adelante y no sale ningún evento en vivo, y el cajón se actualiza en la siguiente carga de página. Eso es degradación honesta, y la función nunca lanza a propósito, porque un registro ausente no puede tumbar una copia de seguridad.

El sumidero era una variable de módulo, y eso estaba mal de una manera que no producía ningún error. Next ejecuta su archivo de instrumentación como un paquete propio, separado de los procesos de ruta. La variable existía por tanto dos veces: el registro cayó en un ejemplar y la función emisora leyó el otro. Las filas eran correctas, los estados avanzaban, el cajón se quedaba quieto, y no había una sola línea en ningún registro.

Esto no se encontró razonando. Se encontró poniendo un oyente en el bus de mensajes y contando: llegó un evento donde debían estar tres. La reparación es una ranura en globalThis detrás de un Symbol.for, que sobrevive a una frontera de empaquetado dentro de un mismo proceso, con la misma forma y el mismo motivo que la conexión compartida de base de datos y la de Redis.

Dos lecciones que valen más que el fallo. Un fallo sin línea de error exige un instrumento fuera del código sospechoso. Y «este módulo tiene un ejemplar» es una suposición sobre un empaquetador, no una propiedad del lenguaje.

La misma ronda encontró un segundo caso silencioso. Terminar una operación y bloquearla dejaban ambas intacta la columna de etapa, así que una copia perfectamente exitosa se quedaba para siempre en «archivando». No fallaba nada; simplemente el último paso no llegaba nunca.

Cancelar es una petición, y una sola escritura es la prueba

En la cancelación es donde una proyección tiene más tentación de mentir. El cajón conoce la operación, la operación tiene un botón de cancelar, y la implementación más barata pone una marca y cierra la hoja.

Por eso cada tipo declara si cancelar tiene siquiera sentido. Un vzdump en marcha no lo podemos cancelar: la orden está en el agente, la tarea en el hipervisor, y nadie sabe qué representa medio archivo en el almacenamiento del cliente. Una restauración es igual, con un motivo añadido: la fila de restauración mantiene una reserva sobre el identificador de destino mientras está abierta, y cerrarla antes de tiempo libera ese identificador mientras quizá esté aterrizando un huésped encima. Ambas se declaran no admitidas, y no hay camino que lo esquive.

Donde cancelar sí es posible, sigue decidiendo la fila. Una migración se puede cancelar mientras está planificada y ya no cuando un programa auxiliar está creando servidores en el anfitrión de otra persona. Una reinstalación de contenedor se puede cancelar hasta la vista previa y ya no cuando el agente ha empezado a sustituir.

Cuando funciona, la prueba de que no ha pasado nada es una única escritura condicional: poner la orden en cola como cancelada allí donde su estado siga siendo pendiente. Si ya se envió, esa instrucción toca cero filas y la petición se queda honestamente en «solicitada». Consultar y luego escribir serían dos instrucciones y por tanto dos ocasiones de colarse en medio.

Y entonces la frase importa. El evento dice que la orden se retiró de la cola antes de que el agente la recibiera, y no que se canceló. No se interrumpió nada, porque nada había empezado. Esa fila va al registro general de eventos y no al progreso de la operación, porque el progreso se limpia a los noventa días y «una persona intervino en la infraestructura» no es progreso.

Cuatro bloqueos, y ninguno es un fallo

Cuatro situaciones ponen una operación en necesita atención en vez de fallida.

Un vzdump informó OK y el archivo no está en el índice. Un intento de copia agotó su tiempo y no sabemos si el archivo existe. Una restauración no se puede releer o el huésped resultante no pudo registrarse. Una reinstalación de contenedor se quedó parada después de que el contenedor antiguo ya no estuviera.

Llamar fallida a cualquiera de ellas sería una mentira con consecuencia. Fallida invita al cliente a repetirlo, y repetir una copia son otras decenas de gigabytes en el almacenamiento de alguien para un archivo que quizá ya está ahí. Repetir una reinstalación es un segundo contenedor. El bucle de reconciliación sigue mirando hasta que el índice se pronuncia, y mientras tanto la operación está visiblemente atascada, que es la cantidad correcta de alarma.

El bloqueo también indica de quién es. Tres de ellos son nuestros; una orden que falló de verdad en el lado del cliente lo dice.

Un ensayo termina en la planificación, y eso no es un hueco

Una migración en modo ensayo tiene cinco pasos; una real tiene ocho, y los tres adicionales son escribir y recontar. La lista de etapas tiene las seis, así que un ensayo exitoso muestra sus dos últimas como no hechas.

Eso parece un error en una barra de progreso y es la verdad: un ensayo no escribe y no recuenta. Las alternativas eran un segundo tipo, que duplica nueve entradas de catálogo en nueve idiomas para el mismo trabajo, o una lista de etapas que varía por fila, con lo que el cajón y la página de detalle discreparían sobre cuántos pasos hay. Mostrar la verdad salía más barato que ambas.

Lo que dejaríamos escrito

  1. Una proyección proyecta. Si su capa de resumen puede escribir de vuelta en el origen, es un segundo registro y ahora tiene dos.
  2. Ponga la costura en una tabla que una prueba pueda leer, derivada del esquema, y hágale comprobar que todo escritor de una columna de estado nombra su espejo. Una invocación olvidada no da error.
  3. No dé por hecho que un módulo tiene un ejemplar. Un armazón que ejecuta parte de su código como paquete aparte le dará dos, y el síntoma es el silencio.
  4. Mida fuera del sospechoso. Un oyente en el bus encontró en minutos lo que leer no encontró en absoluto.
  5. Diga lo que ocurrió de verdad. «Retirada de la cola antes de que el agente la recibiera» es más largo que «cancelada» y es la única versión cierta.
  6. Reserve fallida para el fallo. Los desenlaces desconocidos necesitan estado propio, o la gente repetirá operaciones caras que quizá ya salieron bien.

Para que quede claro qué es esto: los tipos descritos aquí están detrás de interruptores de despliegue apagados por defecto, así que las migraciones y las copias de cliente no se pueden comprar hoy. Varias cosas quedan sin probar. El evento de progreso en vivo se midió solo para migraciones. El lado de la pasarela del registro del sumidero no se ejerció con una pasarela realmente en marcha. La cadena del agente se reprodujo con SQL. Y cancelar una reinstalación de contenedor desde la pantalla de operaciones nunca se condujo en un navegador.

Del trabajo de plataforma en Provibr. Por qué una barra de progreso se niega a estimar una hora de fin está en una barra de progreso que se niega a adivinar, y por qué una migración que se saltó en silencio es peor que una que falló está en la migración que nunca se ejecutó.

Empiece con un host y un agente

Cree una cuenta, instale el agente con un solo comando y conecte su primer host de Proxmox. El periodo de prueba dura 14 días y no pide ningún dato de pago.