Cómo trabajar con migraciones de ramas y rieles de Git


131

Estoy trabajando en una aplicación de rieles con bastantes ramas git y muchas de ellas incluyen migraciones db. Intentamos tener cuidado, pero ocasionalmente algún código en el maestro solicita una columna que se eliminó / renombró en otra rama.

  1. ¿Cuál sería una buena solución para "emparejar" ramas git con estados DB?

  2. ¿Cuáles serían estos "estados" en realidad?

    No podemos simplemente duplicar una base de datos si tiene unos pocos GB de tamaño.

  3. ¿Y qué debería pasar con las fusiones?

  4. ¿La solución se traduciría también en bases de datos noSQL?

    Actualmente usamos MySQL, mongodb y redis


EDITAR: Parece que olvidé mencionar un punto muy importante, solo estoy interesado en el entorno de desarrollo pero con grandes bases de datos (unos pocos GB de tamaño).


¿Qué está haciendo para tener un entorno que ejecuta su rama maestra cuya base de datos puede ser modificada por otras ramas? No entiendo cuál es su flujo de trabajo o por qué cree que necesita mantener las ramas sincronizadas con bases de datos particulares.
Jonás

3
Digamos que tenemos una tabla en nuestra base de datos con clientes (nombre, correo electrónico, teléfono) y en una rama dividimos una de las columnas (nombre -> nombre_nombre + apellido_). Hasta que combinemos la rama con el maestro, el maestro y todas las demás ramas basadas en él fallarán.
Kostas

Respuestas:


64

Cuando agrega una nueva migración en cualquier sucursal, ejecute rake db:migratey confirme tanto la migración como db/schema.rb

Si hace esto, en el desarrollo, podrá cambiar a otra rama que tenga un conjunto diferente de migraciones y simplemente ejecutar rake db:schema:load.

Tenga en cuenta que esto recreará la base de datos completa y se perderán los datos existentes .

Probablemente solo desee ejecutar la producción desde una rama con la que tenga mucho cuidado, por lo que estos pasos no se aplican allí (solo se ejecuta rake db:migratecomo de costumbre allí). Pero en el desarrollo, no debería ser un gran problema recrear la base de datos a partir del esquema, que es lo rake db:schema:loadque hará.


55
Creo que esto solo resolverá el problema del esquema, los datos se perderán con cada migración descendente que nunca se volverá a ver. ¿Sería una buena idea guardar algún tipo de parche de datos db que se guarda al salir de una rama y otro que se carga cuando se mueve a otra rama? Los parches solo deben contener los datos que se perderían en el camino (migraciones).
Kostas

44
Si desea cargar datos, use db/seeds.rb No debería ser demasiado devastador destruir su base de datos de desarrollo si configura algunos datos iniciales razonables allí.
Andy Lindeman

No hay necesidad de bombardear nada. Vea mi solución a continuación. Solo tenga en cuenta que tendrá muchas instancias y cuando cambie de sucursal, los datos no estarán allí. Esto está totalmente bien si se está desarrollando con pruebas.
Adam Dymitruk

Gracias Andy, esta respuesta también es mi pregunta. Y acuerde usar db/seeds.rbpara ripopular los datos de db perdidos
pastullo

Para las aplicaciones grandes y complicadas donde necesita reproducir errores de la vida real localmente, es absolutamente imposible usar un archivo de semillas, necesita los datos reales de la producción (o puesta en escena). Y restaurar una base de datos puede llevar bastante tiempo, así que no, esta no es una buena solución para mi caso.
Joel_Blum

21

Si tiene una base de datos grande que no puede reproducir fácilmente, le recomiendo usar las herramientas de migración normales. Si desea un proceso simple, esto es lo que recomendaría:

  • Antes de cambiar de rama, retroceda ( rake db:rollback) al estado anterior al punto de ramificación. Luego, después de cambiar de rama, corre db:migrate. Esto es matemáticamente correcto y, siempre y cuando escribas downguiones, funcionará.
  • Si olvida hacer esto antes de cambiar de sucursal, en general puede regresar, retroceder y volver a cambiar de manera segura, por lo que creo que como un flujo de trabajo, es factible.
  • Si tiene dependencias entre migraciones en diferentes ramas ... bueno, tendrá que pensar mucho.

2
Debe tener en cuenta que no todas las migraciones son reversibles, dicho esto, no se garantiza que el primer paso sugerido tenga éxito. Creo que en el entorno de desarrollo una buena idea sería usar rake db:schema:loady, rake db:seedcomo había dicho @noodl.
pisaruk

@pisaruk Sé que respondiste esto hace seis años, pero al leer tengo curiosidad por saber cuál sería un ejemplo de migración no reversible. Me está costando imaginar una situación. Supongo que lo más simple sería una columna descartada que contenga un montón de datos, pero esto podría "revertirse" para tener una columna vacía o una columna con algún valor predeterminado. ¿Estabas pensando en otros casos?
Luke Griffiths

1
¡Creo que respondiste tu propia pregunta! Sí, una columna caída es un buen ejemplo. O una migración de datos destructiva.
ndp

13

Aquí hay un script que escribí para cambiar entre ramas que contienen diferentes migraciones:

https://gist.github.com/4076864

No resolverá todos los problemas que mencionó, pero dado un nombre de sucursal lo hará:

  1. Revierta cualquier migración en su rama actual que no exista en la rama dada
  2. Descarte cualquier cambio en el archivo db / schema.rb
  3. Echa un vistazo a la rama dada
  4. Ejecuta cualquier nueva migración existente en la rama dada
  5. Actualiza tu base de datos de prueba

Me encuentro haciendo esto manualmente todo el tiempo en nuestro proyecto, así que pensé que sería bueno automatizar el proceso.


1
Este script hace exactamente lo que quiero hacer, me encantaría verlo en un gancho de pago automático.
brysgo

1
Justo aquí, bifurqué tu esencia y la hice un gancho posterior al pago: gist.github.com/brysgo/9980344
brysgo

En su guión, ¿realmente quiso decir git checkout db/schema.rbo quiso decir git checkout -- db/schema.rb? (es decir, con guiones dobles)
user664833

1
Bueno, sí ... No sabía sobre dobles guiones en ese momento. Pero el comando funcionará igual a menos que tenga una rama llamada db/schema.rb. :)
Jon Lemmon

El comando evolucionado git_rails de @brysgo ( github.com/brysgo/git-rails ) funciona muy bien. Gracias a ti Jon :)
Zia Ul Rehman Mughal

7

Base de datos separada para cada rama

Es la única forma de volar.

Actualización 16 de octubre de 2017

Volví a esto después de bastante tiempo e hice algunas mejoras:

  • He agregado otra tarea de rastrillo de espacio de nombres para crear una rama y clonar la base de datos de una sola vez, con bundle exec rake git:branch.
  • Ahora me doy cuenta que la clonación de maestro no es siempre lo que quiere hacer, así que lo hizo más explícita que la db:clone_from_branchtarea toma SOURCE_BRANCHy una TARGET_BRANCHvariable de entorno. Cuando lo use git:branch, usará automáticamente la rama actual como SOURCE_BRANCH.
  • Refactorización y simplificación.

config/database.yml

Y para que sea más fácil para usted, así es como actualiza su database.ymlarchivo para determinar dinámicamente el nombre de la base de datos en función de la rama actual.

<% 
database_prefix = 'your_app_name'
environments    = %W( development test ) 
current_branch  = `git status | head -1`.to_s.gsub('On branch ','').chomp
%>

defaults: &defaults
  pool: 5
  adapter: mysql2
  encoding: utf8
  reconnect: false
  username: root
  password:
  host: localhost

<% environments.each do |environment| %>  

<%= environment %>:
  <<: *defaults
  database: <%= [ database_prefix, current_branch, environment ].join('_') %>
<% end %>

lib/tasks/db.rake

Aquí hay una tarea de Rastrillo para clonar fácilmente su base de datos de una rama a otra. Esto toma una SOURCE_BRANCHy una TARGET_BRANCHvariables de entorno. Basado en la tarea de @spalladino .

namespace :db do

  desc "Clones database from another branch as specified by `SOURCE_BRANCH` and `TARGET_BRANCH` env params."
  task :clone_from_branch do

    abort "You need to provide a SOURCE_BRANCH to clone from as an environment variable." if ENV['SOURCE_BRANCH'].blank?
    abort "You need to provide a TARGET_BRANCH to clone to as an environment variable."   if ENV['TARGET_BRANCH'].blank?

    database_configuration = Rails.configuration.database_configuration[Rails.env]
    current_database_name = database_configuration["database"]

    source_db = current_database_name.sub(CURRENT_BRANCH, ENV['SOURCE_BRANCH'])
    target_db = current_database_name.sub(CURRENT_BRANCH, ENV['TARGET_BRANCH'])

    mysql_opts =  "-u #{database_configuration['username']} "
    mysql_opts << "--password=\"#{database_configuration['password']}\" " if database_configuration['password'].presence

    `mysqlshow #{mysql_opts} | grep "#{source_db}"`
    raise "Source database #{source_db} not found" if $?.to_i != 0

    `mysqlshow #{mysql_opts} | grep "#{target_db}"`
    raise "Target database #{target_db} already exists" if $?.to_i == 0

    puts "Creating empty database #{target_db}"
    `mysql #{mysql_opts} -e "CREATE DATABASE #{target_db}"`

    puts "Copying #{source_db} into #{target_db}"
    `mysqldump #{mysql_opts} #{source_db} | mysql #{mysql_opts} #{target_db}`

  end

end

lib/tasks/git.rake

Esta tarea creará una rama git fuera de la rama actual (maestra o de otra manera), échale un vistazo y clona la base de datos de la rama actual en la base de datos de la nueva rama. Es hábil AF.

namespace :git do

  desc "Create a branch off the current branch and clone the current branch's database."
  task :branch do 
    print 'New Branch Name: '
    new_branch_name = STDIN.gets.strip 

    CURRENT_BRANCH = `git status | head -1`.to_s.gsub('On branch ','').chomp

    say "Creating new branch and checking it out..."
    sh "git co -b #{new_branch_name}"

    say "Cloning database from #{CURRENT_BRANCH}..."

    ENV['SOURCE_BRANCH'] = CURRENT_BRANCH # Set source to be the current branch for clone_from_branch task.
    ENV['TARGET_BRANCH'] = new_branch_name
    Rake::Task['db:clone_from_branch'].invoke

    say "All done!"
  end

end

Ahora, todo lo que necesitas hacer es correr bundle exec git:branch, ingresar el nuevo nombre de la sucursal y comenzar a matar zombies.


4

¿Quizás debería tomar esto como una pista de que su base de datos de desarrollo es demasiado grande? Si puede usar db / seeds.rb y un conjunto de datos más pequeño para el desarrollo, entonces su problema puede resolverse fácilmente usando schema.rb y seeds.rb de la rama actual.

Eso supone que su pregunta se relaciona con el desarrollo; No puedo imaginar por qué necesitarías cambiar de sucursal regularmente en producción.


No lo sabía db/seeds.rb, lo examinaré.
Kostas

3

Estaba luchando con el mismo problema. Aquí está mi solución:

  1. Asegúrese de que tanto el schema.rb como todas las migraciones sean verificadas por todos los desarrolladores.

  2. Debe haber una persona / máquina para implementaciones en producción. Llamemos a esta máquina como la máquina de fusión. Cuando los cambios se llevan a la máquina de fusión, la fusión automática para schema.rb fallará. Sin problemas. Simplemente reemplace el contenido con los contenidos anteriores de schema.rb (puede dejar una copia a un lado u obtenerla de github si la usa ...).

  3. Aquí está el paso importante. Las migraciones de todos los desarrolladores ahora estarán disponibles en la carpeta db / migrate. Siga adelante y ejecute bundle exec rake db: migrate. Traerá la base de datos en la máquina de fusión a la par con todos los cambios. También regenerará schema.rb.

  4. Comprometa y envíe los cambios a todos los repositorios (controles remotos e individuos, que también son controles remotos). ¡Deberías haber terminado!


3

Esto es lo que he hecho y no estoy seguro de haber cubierto todas las bases:

En desarrollo (usando postgresql):

  • sql_dump nombre_bd> tmp / branch1.sql
  • git checkout branch2
  • dropdb db_name
  • createdb nombre_bd
  • psql db_name <tmp / branch2.sql # (del conmutador de rama anterior)

Esto es mucho más rápido que las utilidades de rake en una base de datos con aproximadamente 50K registros.

Para la producción, mantenga la rama maestra como sacrosanta y todas las migraciones se registran, shema.rb se fusionó correctamente. Siga su procedimiento de actualización estándar.


Para tamaños de base de datos lo suficientemente pequeños y hacer esto en segundo plano cuando se desprotege una rama parece una solución muy buena.
Kostas

2

Desea preservar un "entorno db" por rama. Mire el guión borroso / limpio para señalar diferentes instancias. Si se queda sin instancias de db, haga que el script genere una instancia temporal para que cuando cambie a una nueva rama, ya esté allí y solo necesite cambiar el nombre del script. Las actualizaciones de la base de datos deberían ejecutarse justo antes de ejecutar sus pruebas.

Espero que esto ayude.


Esta solución solo es buena para las ramas "temporales". Por ejemplo, si tenemos un "borde" de rama donde probamos todo tipo de cosas locas (probablemente con otras sub-ramas) y luego lo fusionamos con el maestro de vez en cuando, las 2 bases de datos se separarán (sus datos no ser el mismo).
Kostas

Esta solución es buena para todo lo contrario. Esta es una muy buena solución si versiona el script de versión de su base de datos.
Adam Dymitruk

2

Experimento totalmente la pita que tienes aquí. Mientras lo pienso, el verdadero problema es que todas las ramas no tienen el código para deshacer ciertas ramas. Estoy en el mundo de Django, así que no conozco tan bien. Estoy jugando con la idea de que las migraciones viven en su propio repositorio que no se ramifica (git-submodule, del que me enteré recientemente). De esa manera todas las ramas tienen todas las migraciones. La parte adhesiva es asegurarse de que cada rama esté restringida solo a las migraciones que les interesan. Hacer / hacer un seguimiento de eso manualmente sería una pita y propenso a errores. Pero ninguna de las herramientas de migración está diseñada para esto. Ese es el punto en el que estoy sin un camino a seguir.


Esta es una buena idea, pero ¿qué sucede cuando una rama cambia el nombre de una columna? El resto de las ramas mirarán una mesa rota.
Kostas

um, esa es la parte adhesiva, qué rama se preocupa por qué migraciones. para que pueda "sincronizar" y sabe, "revertir esta migración" para que la columna regrese.
JohnO

1

Sugeriría una de dos opciones:

Opción 1

  1. Pon tus datos en seeds.rb. Una buena opción es crear sus datos de semillas a través de FactoryGirl / Fabrication gem. De esta manera, puede garantizar que los datos estén sincronizados con el código si suponemos que las fábricas se actualizan junto con la adición / eliminación de columnas.
  2. Después de cambiar de una rama a otra, ejecute rake db:reset, lo que efectivamente elimina / crea / inicializa la base de datos.

opcion 2

Mantenga manualmente los estados de la base de datos ejecutando siempre rake db:rollback/ rake db:migrateantes / después de un pago de sucursal. La advertencia es que todas sus migraciones deben ser reversibles, de lo contrario esto no funcionará.


0

En entorno de desarrollo:

Debería trabajar con rake db:migrate:redopara comprobar si su script es reversible, pero tenga en cuenta que siempre debe tener una seed.rbcon la población de datos.

Si trabaja con git, su seed.rb debe cambiarse con un cambio de migración y la ejecución del db:migrate:redoprincipio (cargue los datos para un nuevo desarrollo en otra máquina o nueva base de datos)

Además de "cambio", con los métodos de arriba y abajo, su código siempre será escenarios de cobertura para el "cambio" en este momento y cuando comience desde cero.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.