git - populares - tags for likes 2017
Migraciones de base de datos en un sistema de ramificación complejo. (5)
En nuestro flujo de trabajo de desarrollo actual, hemos introducido migraciones de base de datos (utilizando Ruckusing ) para mantener sincronizados los esquemas de db de nuestros desarrolladores. Funciona muy bien, su uso es bastante sencillo, pero ahora hemos cambiado a git porque VCS nos enfrentamos al siguiente problema en nuestro sistema de control de versiones de bases de datos.
Al revisar una rama que ha estado en desarrollo durante algún tiempo, puede suceder que el esquema de la base de datos se haya desviado mucho del esquema en la rama de la que vengo. Esto causa conflictos en la base de datos en algunos casos. Lógicamente, parece que necesitamos ejecutar migraciones dependiendo de la rama en la que estuvimos anteriormente, pero eso puede ser complejo realmente rápido y seguramente se encontrará con problemas con algunas personas. Y que yo sepa, ¿no existe un sistema de migración de base de datos que sea compatible con sucursales?
La complejidad adicional se produce cuando se cambia a una rama de características, es posible que tengamos que ejecutar algunas migraciones mientras que otras no ... técnicamente, esto parece imposible usando nuestros scripts actuales de dbmigration, ¿existen alternativas sensatas? ¿Hay alguna forma preferida de trabajar con migraciones de base de datos en un sistema de desarrollo muy activo y ramificado?
Creo que toda la idea de migraciones incrementales es bastante mala, realmente. En un entorno complejo como el tuyo, realmente no funciona. Podrías hacerlo funcionar para patrones de ramas simples, pero para cualquier cosa complicada, será una pesadilla.
El sistema con el que estoy trabajando ahora tiene un enfoque diferente: no tenemos capacidad para realizar migraciones incrementales, sino solo para reconstruir la base de datos desde una línea de base. Durante el desarrollo inicial, esa línea de base fue una base de datos vacía, y durante el mantenimiento, es una copia de la base de datos en vivo (restaurada desde un volcado). Solo tenemos una pila de scripts SQL y XML que aplicamos a la línea de base para obtener un sistema actual (migraciones, esencialmente, pero no están diseñadas para ejecutarse de manera incremental). La actualización o el cambio de sucursales es muy simple: destruya la base de datos, cargue un volcado para establecer la línea de base, ejecute los scripts.
Este proceso no es tan rápido como ejecutar algunas migraciones, pero es lo suficientemente rápido. Lleva el tiempo suficiente para que pueda ir a tomar una taza de café, pero no lo suficiente para almorzar.
La gran ventaja es que comenzar por entibiar la base de datos significa que el proceso es completamente independiente de la historia, por lo que no es necesario saber o preocuparse por cruzar sucursales, retroceder en el tiempo o cualquier otra cosa.
Cuando toma un lanzamiento en vivo, obviamente hace las cosas de manera ligeramente diferente: no destruye la base de datos ni carga un volcado, porque el sistema ya está en la línea de base (la línea de base se define como el estado del sistema en vivo). Usted acaba de ejecutar los scripts. Y después de eso, haga un nuevo volcado para usarlo como una nueva línea de base para el desarrollo.
Esto es algo en lo que he estado trabajando últimamente. Para mí, el problema no ha sido que el esquema de la base de datos haya divergido per se, sino que git no puede fusionarlos. Las ramas de características que tocan el esquema de la base de datos siempre dan miedo.
La solución en la que he estado pensando es que, en lugar de tener migraciones lineales, tienen migraciones que dependen de otras migraciones. Obtiene un buen gráfico de dependencia de sus migraciones, que es bastante fácil de linealizar (clasificación topológica). Simplemente haga un seguimiento de las migraciones nombradas en su base de datos y, en el orden correcto, ejecute las actualizaciones que aún no están actualizadas.
Por ejemplo, addCustomerSalt
depende de initialSchema
, y la dirección separateAddress
depende de la person
.
El único problema que esto no resuelve es que si la rama A depende de la actualización Z que se creó en la rama B, pero tal vez en ese caso, ¿debería cambiarse a un ancestro común?
Estoy en una situación similar en la que trabajo en un sitio web en vivo y en varias sucursales de desarrollo en las que necesito cambiar el esquema de la base de datos.
Lo resolví escribiendo un post-checkout y un gancho post-merge que se puede usar muy bien con git. Almaceno todas mis migraciones en forma de archivos SQL en un directorio separado y las confino junto con el código PHP modificado. Cada vez que realizo un
git checkout
o un
git merge
git llamará automáticamente a las migraciones ascendentes y descendentes apropiadas. Ver mi implementación en Github .
Como una solicitud especial (para aquellos de ustedes que no quieran seguir el enlace de github) alguna explicación más:
Considere el siguiente escenario. Tienes dos ramas:
- master - que contiene el sitio web que está actualmente en línea
- característica - que contiene una nueva característica sin terminar
Para que la nueva función funcione correctamente, necesita cambiar el esquema de la base de datos. El flujo de trabajo es el siguiente:
Cuando, en la rama de características, cambia el código que necesita un cambio del esquema de la base de datos, también confirma dos nuevos archivos SQL en el directorio de migraciones, por ejemplo:
-
20151120130200-extra-field-up.sql
(que contiene todas las consultas SQL para migrar hacia arriba) -
20151120130200-extra-field-down.sql
(que contiene todas las consultas SQL para migrar hacia abajo)
-
- Cuando realice un pago para dominar, el gancho git posterior a la recepción:
- encuentre todos los scripts * -down.sql en las confirmaciones de
<new HEAD>..<old HEAD>
- Ejecutar esos scripts con la base de datos local.
- encuentre todos los scripts * -up.sql en las confirmaciones de
<old HEAD>..<new HEAD>
- Ejecutar esos scripts con la base de datos local.
- encuentre todos los scripts * -down.sql en las confirmaciones de
- Cuando combina la rama de la característica en el maestro, el enlace posterior a la fusión:
- encuentre todos los scripts * -up.sql en las confirmaciones de
master..feature
- Ejecutar esos scripts con la base de datos local.
- encuentre todos los scripts * -up.sql en las confirmaciones de
Instalar
Simplemente copie el archivo post-checkout y / o post-merge en el directorio .git / hooks de su propio repositorio git. Puede editar la sección de configuración de esos archivos. Ver los archivos en sí para una explicación.
Uso
El nombramiento de los archivos SQL de migración es crucial. Deberían terminar con up.sql
o down.sql
. El resto del nombre depende completamente de ti. Sin embargo, si tiene un solo compromiso con múltiples migraciones ascendentes y / o múltiples migraciones descendentes, el orden en el que se realizan depende del orden lexicográfico. Los archivos de migración que están dentro de diferentes confirmaciones, siempre se llamarán en el mismo orden (inverso) que las confirmaciones.
No es un requisito que tenga tanto una migración ascendente como una actualización descendente, ni es un requisito que las migraciones ascendentes y descendentes reciban un nombre similar.
Realmente no estaría de acuerdo con que las migraciones incrementales estén podridas. En mi opinión, tener un conjunto de scripts de cosecha propia sería un enfoque peor que tener una herramienta real para un trabajo de este tipo facilitará el seguimiento de esos cambios. He tenido que lidiar con una situación similar, antes, así que espero poder compartir algunas de las ideas.
Según mi experiencia, los esquemas RDBMS y las ramas no se mezclan muy bien. Dependiendo de su ramificación, los esquemas probablemente deberían ser al menos algo similares, en cuyo caso las migraciones no deberían diferir demasiado. O podría haber entendido mal todo el alcance del problema. Si, por ejemplo, intenta mantener un código específico del cliente en una sucursal, tal vez debería considerar una forma de modularlo en su lugar. Hicimos algo como esto, con reglas que establecían que los cambios en el esquema específico del cliente y el código solo podían depender de la base del código común, no al revés. También establecemos la prioridad entre los conjuntos de cambios del módulo en función del módulo y la fecha, por lo que para la mayoría de las partes sabíamos el orden en que se aplicarían los cambios. YMMV, por supuesto, pero es difícil dar detalles, sin conocer su configuración actual.
En mi antigua empresa, utilizamos con éxito una herramienta llamada Liquibase , que suena similar a lo que estás usando. Básicamente, es una herramienta para tomar un esquema de base de datos y todos los datos de un estado conocido a otro estado conocido. El mismo conjunto de cambios se aplica solo una vez, ya que liquibase mantiene un registro de cambios, con sumas de comprobación. Los registros de cambios se escriben en un formato XML específico. Puedo recomendar encarecidamente probarlo, si necesita alternativas.
De todos modos, la forma en que manejamos el código del cliente y las sucursales, era tener un DB / esquema específico para una rama determinada. De esta manera, podría tener el esquema y los datos del punto de bifurcación, y solo migrar la diferencia a la situación actual. No deshicimos los cambios, incluso si liquibase en teoría podría apoyar esto, ya que consideramos que era demasiado engorroso y propenso a errores. Dado que liquibase mantiene su propio estado, la migración siempre fue tan fácil como tomar el estado actual en una rama determinada y aplicar todo. Solo se aplicaron nuevos conjuntos de cambios, lo que deja el esquema en buen estado.
Utilizamos mercurial , que se distribuye, como git, por lo que la configuración fue bastante similar. También teníamos DBs locales específicos para desarrolladores en las computadoras portátiles de desarrollo y varios entornos, tanto para diferentes clientes como para fases (desarrollo, integración, producción), por lo que el modelo se sometió a una prueba real y funcionó sorprendentemente bien. Tuvimos algunos conflictos en los conjuntos de cambios, pero en su mayoría pudimos resolverlos poco después de que se introdujo el problema. Los entornos de desarrollo local fueron realmente la parte más difícil, ya que durante el desarrollo se podrían haber introducido algunos cambios de esquema, que no siempre eran compatibles con los conjuntos de cambios posteriores, sino la naturaleza estructurada de los cambios, y tener un estado conocido para revertir y conducir a muy pocos problemas reales
Hay algunas advertencias con este enfoque:
- Todos y cualquier cambio al esquema debe implementarse en los conjuntos de cambios. La mayor causa de confusión siempre fue que alguien solo jugueteaba un poco.
- El primer punto también se aplica, incluso si está utilizando una herramienta que modifica el esquema, por ejemplo, una herramienta ORM como Hibernate . Debe ser bastante íntimo con esta herramienta para comprender los cambios que hace y requiere.
- Todos los usuarios deben comprar esto y ser educados para seguir las reglas. Compruebe 1.
- Llega un momento en que la migración de muchos conjuntos de cambios comienza a tomar demasiado tiempo. En este momento, tendrá que crear una nueva línea de base, que puede ser un poco difícil, especialmente con muchas sucursales. También es bueno planificar con anticipación para esto, y al menos conocer todas las sucursales DB existentes.
- Necesita planear un poco con las sucursales para saber si van a migrar de nuevo al maestro en algún momento. La fusión ingenua podría no funcionar bien para los cambios de esquema.
- Para ramas de larga vida y conjuntos de datos separados, este modelo podría no ser lo suficientemente fuerte
Sin embargo, el punto es que cuanta más estructura y control tenga sobre la base de datos, más fáciles serán las migraciones. Por lo tanto, herramientas como Liquibase podrían ser un activo realmente valioso para ayudarlo a rastrear esos cambios. Esto se aplica a los modelos más complejos, incluso en mayor medida, que a los más simples, así que al menos no considere abandonar todas las herramientas que ya tiene. Y tómate un tiempo para explorar otras herramientas alternativas.
Cierta estructura y control es mejor que nada, o incluso peor, pensando que tiene el control con una gran cantidad de scripts manuales.
Un enfoque que estoy pensando en probar en nuestro proyecto actual es crear una ''migración'' de sucursales y todas las migraciones (y solo las únicas) están comprometidas con esta rama. Los desarrolladores deben fusionarse desde esta rama en su rama actual antes de crear una migración para que su migración siempre se cree sobre la última migración. Todos los proyectos se fusionan desde esta rama, de modo que cada rama tiene un concepto de un historial de migración lineal. Esto le da a cada rama la capacidad de moverse entre las versiones de la base de datos. Cuando se cambia a una rama que depende de una versión diferente de la base de datos, el desarrollador aplica la migración que sea apropiada.
La molestia (además del trabajo adicional y la diligencia de cometer migraciones a la rama especial) es recordar qué migración corresponde a una rama en particular. Supongo que una forma de hacerlo es en lugar de cometer migraciones directamente en la rama de migraciones, cometer la migración (y solo la migración) en la rama actual y luego seleccionar esa confirmación en la rama de migraciones. Luego, puede ver la última vez que seleccionó la rama actual en la rama de migraciones y saber que esa diferencia contiene la migración necesaria. Creo que eso sería posible. Además, el desarrollador puede crear una migración solo para ver qué cambios serían necesarios, y luego tratar de inferir qué migración sería apropiada para usar.
Perdón por la vaga sugerencia; Si terminamos probando este enfoque, editaré esta sugerencia con recomendaciones más concretas.