Lograr la implementación de tiempo de inactividad cero

40

Estoy tratando de lograr implementaciones de tiempo de inactividad cero para poder implementar menos durante las horas libres y más durante las horas "más lentas", o en cualquier momento, en teoría.

Mi configuración actual, algo simplificada:

  • Servidor web A (aplicación .NET)
  • Servidor web B (aplicación .NET)
  • Servidor de bases de datos (SQL Server)

Mi proceso de implementación actual:

  1. "Detener" los sitios en el servidor web A y B
  2. Actualice el esquema de la base de datos para la versión de la aplicación que se está implementando
  3. Actualizar el servidor web A
  4. Actualizar el servidor web B
  5. Vuelva a poner todo en línea

Problema actual

Esto lleva a una pequeña cantidad de tiempo de inactividad cada mes, aproximadamente 30 minutos. Hago esto durante las horas libres, por lo que no es un gran problema, pero es algo de lo que me gustaría escapar.

Además, no hay forma de volver realmente 'atrás'. Por lo general, no hago scripts de DB de reversión, solo scripts de actualización.

Aprovechando el equilibrador de carga

Me encantaría poder actualizar un servidor web a la vez. Saque el servidor web A del equilibrador de carga, actualícelo, vuelva a ponerlo en línea y luego repita para el servidor web B.

El problema es la base de datos. Cada versión de mi software deberá ejecutarse en una versión diferente de la base de datos, por lo que estoy "atascado".

Solución posible

Una solución actual que estoy considerando es adoptar las siguientes reglas:

  • Nunca elimine una tabla de base de datos.
  • Nunca elimine una columna de base de datos.
  • Nunca cambie el nombre de una columna de base de datos.
  • Nunca reordene una columna.
  • Todos los procedimientos almacenados deben ser versionados.
    • Significado: 'spFindAllThings' se convertirá en 'spFindAllThings_2' cuando se edite.
    • Luego se convierte en 'spFindAllThings_3' cuando se edita nuevamente.
    • La misma regla se aplica a las vistas.

Si bien, esto parece un poco extremo, creo que resuelve el problema. Cada versión de la aplicación llegará a la base de datos de manera ininterrumpida. El código espera ciertos resultados de las vistas / procedimientos almacenados, y esto mantiene válido ese 'contrato'. El problema es que simplemente parece descuidado. Sé que puedo limpiar viejos procedimientos almacenados después de que la aplicación se implemente por un tiempo, pero se siente sucia. Además, depende de que todos los desarrolladores sigan estas reglas, lo que sucederá principalmente, pero imagino que alguien cometerá un error.

Finalmente: mi pregunta

  • ¿Es esto descuidado o hacky?
  • ¿Alguien más lo está haciendo de esta manera?
  • ¿Cómo están resolviendo este problema otras personas?
MattW
fuente
2
¿Dónde está tu plan de retroceso? ¿Cómo se prueba que todo funciona y no hay regresiones?
Deer Hunter
3
No necesita "nunca": "solo" necesita asegurarse de que cada dos versiones adyacentes puedan ejecutarse simultáneamente. Esto restringe sus rutas de actualización, pero no tan severamente como nunca poder alterar significativamente el esquema de base de datos.
Joachim Sauer
Gracias Joachim ... Me gusta hablar en términos absolutos para que la idea básica sea clara, pero tienes razón, podríamos tener una política de compatibilidad con versiones anteriores de N, en cuyo punto podemos eliminar objetos innecesarios de base de datos.
MattW
2
Querrá tener un plan de reversión en su lugar. Un día lo necesitarás.
Thorbjørn Ravn Andersen
1
En mi experiencia, para la mayoría de los sitios web, su posible solución es peor que el problema que resuelve. La complejidad que agregará será más costosa de lo que puede anticipar ahora. Quizás muchas veces más tiempo / esfuerzo para hacer cambios y agregar funciones. Yo sólo lo consideraría para los sitios web, que absolutamente no puede ningún tener tiempo de inactividad, jamás .
MGOwen

Respuestas:

14

Este es un enfoque muy pragmático para las actualizaciones de software respaldadas por bases de datos. Fue descrito por Martin Fowler y Pramod Sadalage en 2003 y posteriormente escrito en Refactoring Databases: Evolutionary Database Design .

Puedo ver lo que quieres decir cuando dices que parece descuidado, pero cuando se hace intencionalmente y con previsión, y tomándote el tiempo para refactorizar las estructuras no utilizadas de la base de código y la base de datos cuando ya no se usan, es mucho más robusto que Soluciones más simples basadas en scripts de actualización y reversión.

Mike Partridge
fuente
5

El "tiempo de inactividad cero" es solo una de las muchas razones posibles para este tipo de enfoque. Mantener un modelo de datos compatible con versiones anteriores de esta manera le ayuda a lidiar con muchos problemas diferentes:

  • Si tiene muchos paquetes de software accediendo a su base de datos, no tendrá que verificarlos todos si un cambio de esquema los afecta (en organizaciones más grandes con múltiples equipos que escriben programas que acceden a la misma base de datos, los cambios de esquema pueden ser muy difíciles)

  • si es necesario, puede consultar una versión anterior de uno de sus programas y probablemente se ejecutará contra una base de datos más nueva (siempre y cuando no espere que el programa anterior maneje correctamente las columnas más nuevas)

  • Importar / exportar datos archivados a la versión actual de la base de datos es mucho más fácil

Aquí hay una regla adicional para su lista

  • cada nueva columna debe ser NULLable o proporcionar un valor predeterminado significativo

(esto asegura que incluso los programas más antiguos que no conocen las nuevas columnas no rompan nada cuando crean nuevos registros en su base de datos).

Por supuesto, este enfoque tiene un inconveniente real: la calidad de su modelo de datos puede degradarse con el tiempo. Y si tiene control total sobre todas las aplicaciones que acceden a su base de datos, y puede refactorizar todas esas aplicaciones fácilmente cuando, por ejemplo, va a cambiar el nombre de una columna, entonces podría considerar refactorizar las cosas de una manera más limpia.

Doc Brown
fuente
4

Varía de una implementación a otra.

Claro, nunca podría eliminar una tabla o una columna. Nunca podría cambiar nada que rompiera la compatibilidad de la interfaz. Siempre puedes agregar una capa de abstracción. Pero luego tienes que versionar esa abstracción y la versión el versionado.

La pregunta que debe hacerse es si cada versión cambia el esquema de tal manera que no sea compatible con versiones anteriores.

Si muy pocas versiones cambian el esquema de esa manera, entonces el problema de la base de datos es mudo. Simplemente realice una implementación continua de los servidores de aplicaciones.

Las dos cosas que he visto que ayudan más con la implementación de tiempo de inactividad mínimo son:

  1. Esfuércese por la compatibilidad con versiones anteriores, al menos dentro de una sola versión. No siempre lo logrará, pero puedo apostar que puede lograrlo en el 90% o más de sus lanzamientos, especialmente si cada lanzamiento es pequeño.
  2. Tener un script de base de datos previo y posterior al lanzamiento. Esto le permite manejar los cambios de nombre o de interfaz al crear su nuevo objeto antes de que se implemente el código de su aplicación, y luego soltar el antiguo después de que se implemente el código de la aplicación. Si agrega una nueva columna no anulable, puede agregarla como anulable en su secuencia de comandos de prelanzamiento con un activador que llena un valor predeterminado. Luego, en su publicación posterior, puede soltar el gatillo.

Esperemos que el resto de sus implementaciones se puedan guardar para ventanas de mantenimiento.

Otras ideas que pueden ayudar a lidiar con las pocas implementaciones que requieren tiempo de inactividad:

  • ¿Puedes construir compatibilidad con versiones anteriores en tu código? Por ejemplo, ¿hay alguna forma de que su código pueda soportar múltiples tipos de conjuntos de resultados? Si necesita cambiar una columna de int a double, el código de su aplicación podría leerlo como una cadena y analizarlo. Es un poco hacky, pero si es un código temporal para pasar por el proceso de lanzamiento, puede que no sea el fin del mundo.
  • Los procedimientos almacenados pueden ayudar a aislar el código de su aplicación de los cambios de esquema. Esto solo puede llegar tan lejos, pero ayuda un poco.
Brandon
fuente
2

Potencialmente, podría hacerlo así por un poco de esfuerzo adicional.

  1. Haga una copia de seguridad de la base de datos realizando una exportación
  2. Importe la copia de seguridad pero cámbiele el nombre con una versión de lanzamiento, por ejemplo, myDb_2_1
  3. Ejecutar versión de base de datos en myDB_2_1
  4. "Detenga" el grupo de aplicaciones en el servidor web A o sáquelo del equilibrador de carga
  5. Actualice el servidor web A, ejecute pruebas posteriores a la implementación y retrotraiga si es necesario
  6. La sesión sangra el servidor web B y vuelve a poner el servidor web A en bucle
  7. Actualice el servidor web B y luego vuelva a colocarlo en el equilibrador de carga

Naturalmente, las actualizaciones web necesitarían nuevas entradas de configuración para apuntar al nuevo esquema de Db. La cuestión es que si está haciendo lanzamientos una vez al mes y es un equipo pequeño, ¿cuántos cambios de base de datos está haciendo realmente que no son compatibles con versiones anteriores? Si puede controlar eso mediante la prueba, podría obtener una implementación automatizada sin tiempo de inactividad o, en el peor de los casos, solo 5 minutos de tiempo de inactividad.

LeoLambrettra
fuente
1
¿Qué sucede si (cuando) la aplicación en el Servidor A escribe en su DB después de que almacenó la copia de seguridad, pero antes de detener el Servidor A? Siempre hay una "ventana de vulnerabilidad". Estas escrituras se perderán, lo que puede no ser aceptable.
sleske