Se han escrito muchos artículos sobre la actualización de sistemas sobre la marcha sin detenerlos (implementación de tiempo de inactividad cero) y muchos aspectos de este enfoque son bastante obvios. En mi opinión, la parte más difícil de la implementación en este caso es actualizar los almacenes de datos si su contrato (esquema) ha cambiado. Es este aspecto el que me gustaría considerar en este artículo.
Cualquiera que sea la base de datos, con un esquema de datos explícito como relacional o con uno arbitrario como NoSQL, el esquema de datos todavía está presente, incluso a nivel de aplicación. Los datos leídos de la base de datos deben ser entendibles para el cliente, incluso si el repositorio en sí no impone ninguna restricción en su estructura.
Supongamos que un sistema con una estructura de datos específica y terabytes de datos en la base de datos ya se está ejecutando en producción. En la nueva versión del sistema, necesitamos cambiar ligeramente la estructura para implementar nuevas funciones o mejorar el rendimiento. Considere qué cambios en el circuito pueden ocurrir:
- Agregar un nuevo campo
- Borrado de campo
- Renombrar campo
- Cambios de tipo de campo
- Transferencia de un campo a otra estructura de datos (por ejemplo, en el caso de desnormalización)
Agregar un nuevo campo así como agregar cualquier otro objeto de base de datos es un cambio compatible con versiones anteriores y no requiere ningún paso adicional en términos de implementación de implementación de tiempo de inactividad cero (con una advertencia: si este nuevo campo u objeto no depende funcionalmente de otros ya almacenados en la base de datos) datos). Simplemente aplique los cambios a la base de datos sobre la marcha y luego implemente una nueva versión del código que use los nuevos objetos de la base de datos.
Eliminar un campo o cualquier otro objeto de la base de datos no es un cambio compatible con versiones anteriores, pero el enfoque para su implementación es muy simple: los objetos innecesarios de la base de datos deben eliminarse solo después de que la nueva versión del sistema esté completamente bloqueada.
Los otros tres tipos de cambios son más complejos en términos de proporcionar una implementación de tiempo de inactividad cero. En general, todos se pueden realizar copiando datos a otros campos / entidades y eliminando los "antiguos" después de una migración de datos exitosa: para cambiar el nombre, puede copiar datos del campo antiguo a un campo con un nombre nuevo, luego eliminar el campo antiguo, cambiar el tipo de datos se puede hacer junto con renombrar, etc. De una forma u otra, durante un período de tiempo, la base de datos debe admitir contratos antiguos y nuevos. Hay al menos dos formas de hacer tales cambios sobre la marcha:
Si la base de datos admite desencadenantes
- Cree disparadores que copien datos del lugar anterior al nuevo en cualquier cambio / adición y configúrelos en producción.
- Aplique una utilidad de conversión de datos que haga lo mismo, pero para todos los registros de la base de datos. Como los disparadores ya están instalados, la utilidad no puede hacer nada más complicado que una simple actualización "ficticia" de cada registro (tabla ACTUALIZAR SET campo = campo ...). Un punto muy importante aquí es que la acción de leer datos del lugar antiguo y escribir en el nuevo debe ser atómica y protegida de los cambios perdidos. Dependiendo de la estructura de la base de datos, puede usar el bloqueo pesimista mediante SELECT FOR UPDATE o sus análogos, u optimista si la tabla tiene un campo con una versión de registro.
- Una vez que la utilidad finaliza su trabajo (dependiendo de la cantidad de datos y la complejidad de la actualización, el tiempo de ejecución puede tardar días), ya es posible instalar una nueva versión del sistema que admita el nuevo esquema de datos. En este punto, todos los registros en la base de datos que existían en el momento en que se lanzó la utilidad se convertirán con éxito, y todos los nuevos que aparecieron durante su operación también se convertirán por disparadores.
- Elimine los desencadenantes y todos los campos (u otros objetos de la base de datos) que ya no sean necesarios.
Si no es posible usar disparadores (como es el caso con muchas soluciones NoSQL)

- Cree e implemente una nueva versión de la aplicación (versión temporal 1 en la figura), que siempre lee del campo anterior, pero al escribir en este campo, actualiza tanto el lugar antiguo como el nuevo correspondiente (en la figura "C" - la antigua, "H" - nuevo) Zadeplit esta versión a todos los nodos en los que se ejecutan las instancias de la aplicación.
- Aplique una utilidad que copie datos de la ubicación anterior a la nueva. Al igual que con los desencadenantes, debe tomar medidas para evitar cambios perdidos.
- Cree y, una vez completada la utilidad, instale otra versión de la aplicación (versión temporal 2), que lee los datos de un nuevo campo, pero aún escribe en dos lugares. Este paso es necesario, ya que durante la actualización secuencial de cada uno de los nodos todavía habrá una brecha cuando las instancias de la versión anterior de la aplicación que lee el campo antiguo funcionen simultáneamente con el nuevo.
- Cree y, al final del barrido completo de la versión anterior, implemente la versión final, que ya no interactúa con el campo anterior.
- Eliminar viejos campos.
El segundo enfoque requiere la creación e instalación de tres versiones diferentes de la aplicación, lo que puede ser muy incómodo y engorroso. En su lugar, puede usar la función de alternancia: para poner la lógica de las tres versiones en una sola, pero cambie el modo según el parámetro de configuración, que idealmente podría cambiarse sobre la marcha. Por lo tanto, en lugar de instalar cada versión posterior, será suficiente cambiar el valor del parámetro (y reiniciar el servicio si no se proporciona la actualización sobre la marcha). Después de completar con éxito la instalación de la versión final, todo el código relacionado con garantizar la migración de datos debe eliminarse por completo de la rama de trabajo, incluso si se "activa" en la producción hasta la próxima actualización del sistema.
Es fácil notar que garantizar un tiempo de inactividad cero al actualizar el sistema es un procedimiento engorroso y frágil, por lo que tiene sentido molestarse con él solo si existe un requisito correspondiente de la empresa. Pero incluso si los requisitos para la disponibilidad del sistema son bastante bajos (por ejemplo, 99% por año y la ventana para la actualización planificada del sistema es de 24 horas), la conversión de datos requerida para instalar la nueva versión aún puede demorar más. Por lo tanto, debe estar preparado de antemano para el uso de tales soluciones si planea almacenar grandes cantidades de datos.
Un enfoque alternativo puede ser el rechazo intencional de cambios retrocompatibles en el esquema de la base de datos, pero, desafortunadamente, en la práctica no siempre es posible, ya que a menudo la forma más efectiva de mejorar el rendimiento del acceso a los datos es reestructurar el esquema.