Migración iterativa de la database con git / gerrit

Problema: cómo manejar scripts de migration de database secuenciales con commit / gerrit lentos.

La configuration es la siguiente:

  • el desarrollo continuo de las tablas de la database se realiza agregando files con nombres secuenciales (12.sql, 13.sql, 14.sql, etc.)
  • un número de versión se establece en la database
  • una herramienta de migration comtesting la versión contra files, para ver si hay migraciones de bases de datos no procesadas

El principal problema con este enfoque (que funciona bien de otra manera) ha sido que cuando dos desarrolladores agregan un script de migration al mismo time, el último de ellos en comprometerse obtendrá un conflicto de combinación. En subversión (que hemos utilizado hasta ahora), este fue un conflicto de tree que el commiter manejó al revertir ese file y agregarlo con un nuevo nombre de file, como la última acción. Como trabajamos con estas cosas en trunk, generalmente lograron arreglar el nombre de file como una última acción rápida.

Ahora que estamos migrando a git, el conflicto se mostrará como una diferencia dentro del file, lo que hace que sea más complicado separar los dos files comprometidos (que pueden ser parte de packages de confirmación diferentes, junto con otros files). Además, dado que estamos utilizando gerrit para la revisión de código, habrá una demora entre empujar y colocar los files en el repository principal de git, lo que da como resultado este escenario de vez en cuando:

  1. Empujas una migration
  2. Gerrit obtiene un conflicto después de revisar el código
  3. Arregla el conflicto cambiando el nombre del file e inserta un nuevo compromiso (que debe revisarse de nuevo)
  4. Alguien logró get una nueva migration antes de que se revisara el código, por lo que Gerrit vuelve a tener un conflicto
  5. Arregla el conflicto cambiando el nombre del file e inserta un nuevo compromiso (que debe revisarse de nuevo)
  6. repite desde 4 hasta que tengas suerte y Gerrit logra enviar la migration con éxito.

¿Cuál es la mejor manera de resolver esta situación?

Utilizo liquibase para administrar mis migraciones de bases de datos.

Utiliza una syntax XML para describir cada "set de cambios" a la database. Si bien esto puede ser una barrera para la adopción, me resulta mucho más fácil fusionar las contribuciones de diferentes desarrolladores en el mismo file.

<changeSet id="bob-20130115-1" author="bob"> <createTable tableName="commontable"> .. .. </createTable> </changeSet> <changeSet id="tom-20130115-1" author="tom"> <addColumn tableName="commontable"> <column name="newcolumn" type="varchar(255)"/> </addColumn> </changeSet> 

Una segunda ventaja de usar liquibase es que admite la reversión. En caso de conflicto, podemos deshacer cambios, volviendo a la última versión estable, corregir el file de migration y realizar una nueva actualización.

Si no tiene el lujo de cambiar de herramienta, entonces sugiero que adopte el enfoque de liquibase de tratar cada migration durante DEV como un "set de cambios". Use un solo file de migration para cada iteración y agregue un comentario al inicio y al final de la contribución de cada desarrollador. En caso de conflictos de fusión, no intente volver a escribir el SQL, en su lugar vuelva a orderar para que el cambio de un desarrollador sea anterior o posterior a los demás. (El enfoque de obligar al otro desarrollador a crear otro file es válido, pero me ha resultado difícil implementarlo en la práctica, cuando un tercer desarrollador aparece más tarde y entra en conflicto con el siguiente número de la secuencia …)

Administrar el entorno de integración sin una function de reversión es complicado … No conozco ningún método sencillo además de rebuild la database desde cero cada vez. Quizás la única forma efectiva es seguir los consejos de VonC y mantener el número de cambiadores de bases de datos al mínimo.

Notas:

  • Cada desarrollador que usa su propia database es un hecho. Está utilizando una herramienta de migration, por lo que no debería haber objeciones. Simplifica los problemas, especialmente cuando necesita volar y rebuild su esquema. También lo obliga a abordar el desafío de "datos de testing". Muele mis engranajes cuando trabajo en un proyecto donde los datos de testing se encuentran en una única instancia de database ….
  • Recientemente descubrí que SQL Alchemy migrate tiene las mismas capacidades de reversión que liquibase. Te obliga a escribir los scripts de avance y retroceso para cada migration.

Secundo Liquibase como una solución. Una propiedad de Liquibase que Mark O'Connor en mi humilde opinión no señaló muy bien es que Liquibase mantiene una list de sets de cambios aplicados frente al último cambio aplicado en su enfoque.

Por lo tanto (siempre que los identificadores de cambio no entren en conflicto) las twigs no son el problema. Liquibase simplemente aplica todos los cambios pendientes que aún no se han aplicado en su order secuencial dentro del XML. Como característica para detectar conflictos, Liquibase almacena también un hash MD5 del código fuente del set de cambios en sí, de modo que también es capaz de detectar que el contenido del cambio "XY" se modificó. En estos casos, debe averiguar manualmente qué salió mal y enseñar a sus colegas desarrolladores a no cambiar ningún set de cambios después de que haya sido aplicado por un tercero.

Si no quiere cambiar / introducir una nueva herramienta de control de versión de database como liquibase, creo que podría ampliar fácilmente su enfoque personalizado en esta dirección. Simplemente mantenga una list de los cambios aplicados y podrá abordar bien las sucursales siempre que sus nombres de file no coincidan.

dos desarrolladores agregan un script de migration al mismo time

Deben hacer sus modificaciones en su propia sucursal y activar una revisión por separado en Gerrit.
Un integrador (o uno de los dos desarrolladores) se encargará de fusionar y revisar los dos esfuerzos de desarrollo.

Eso, o la comunicación entre los dos necesita mejorar, para que "dev 1" no presione nada sin tener que verificar con el "dev 2" si "dev 1" necesita get un parche (que representa "dev 2" 's trabajo) primero en el tree de trabajo de "dev 1".