sábado, 19 de noviembre de 2011

¿Como evitar problemas en las migrations?

Primero de todo teniendo en cuenta para que sirven:
  1. Actualizar el esquema de base de datos (añadir tablas, campos, índices, eliminar...) [50% del valor total]
  2. Automatizar transformaciones o scripts para mover datos en el despliegue de producción (típico caso de una tabla que se divide en dos y tenemos que recolocar los datos con cierta lógica). [30% del valor total]
  3. Poder movernos en la línea temporal y en caso de necesidad poder bajar a una versión de código inferior con una base de datos acorde. [20% del valor total]
Estos tres puntos son lo más importante y lo que más dolores de cabeza trae en entornos donde no hay una solución equivalente a las migrations (que muchas veces termina siendo un fichero leame o muchos scripts que la gente tiene que ir haciendo y que la persona que despliega tiene que acordarse de ejecutar en en desarrollo, producción, test, ...) todo lo demás son añadidos o como dirían los yankis "sugar", y me refiero a todo, des de los métodos del "Active Record" hasta el potencial derivado de utilizar código rails. Y el azúcar en exceso es malo (cita de mi médico).

Cuando aparecen los problemas:
  1. Al intentar hacer las migrations des de zero [ahora] hay algún paso que falla.
  2. Al desplegar en el entorno de producción/test falla.
Las causas de los problemas:

Modificación a posteriori de una migration subida al repositorio (GIT): típico caso donde alguien se ha dejado de añadir un campo, el nombre estaba mal escrito, el tipo de datos no era el necesario y en lugar de hacer una nueva migration se decide sobrescribir la original y subirlo al repositorio GIT. En que escenarios falla:
  • Si en lapos de tiempo entre la primera y la segunda versión, alguien había bajado los cambios del repositorio y había ejecutado "rake db:migrate". Aún bajando la nueva versión que podamos subir esa migration no volverá a ejectuarse y nuestro compañero quedará tocado.
  • Si tenemos sistemas automatizados como el servidor de integración continua, van a sufrir los mismos problemas. (Siempre y cuando este servidor rake db:migrate)
  • Si queremos movernos en la línea temporal de nuestro código o entre versiones (commits) es posible que nos encontremos con inconsistencias.
  • Favorece la aparición de condicionales, ejemplo añade una columna si no existe....
Esté caso se da con mucha frecuencia cuando tenemos un programador que percibe las migrations como parte del código a mantener "limpio" y ordenado, en lugar de ser una secuencia cronológica de cambios y mejoras, tener muchas migrations no es malo.

Haber implementado solo "up" si solo nos preocupamos de migrar para adelante estamos perdiendo el 20% del potencial y nos exponemos a:
  • En caso de error en producción si tenemos que retroceder de versión nos quedaremos con un código inconsistente ya que la base de datos no habrá bajado de versión (a no ser que recuperemos la copia de seguridad). Esto puede suceder tanto en el momento del deploy como al cabo de unos días que se decida volver a la versión antigua.
  • No poder probar el código entre versiones, solo podremos trabajar en la última versión y si tenemos que hacer un "rake db:migrate:redo" tendremos que ayudarnos de sql a mano.
  • Favorecer los condicionales en las migrations, mismo caso, añade la columna si no existe (porqué antes no la hemos borrado).
Por suerte el generador de scaffold ya nos ayuda cuando somo novatos y en rails 3.1 ha aparecido la opción "change" que tiene un número limitado de métodos (add_column, add_index, add_timestamp, create_table, remove_timestamps, rename_column, rename_index, rename_table) que no hace falta escribir "up" y "down"  y el solo sabe resolver. Para todo lo demás valorar

Pasar del "active record" y utilizar execute, a no ser que tengamos muy claro y pactado con el equipo y el cliente que vamos a utilizar un SGBD concreto, añadir "execute" nos limita a la hora de cambiar de base de datos, nos obliga a aprender y entender SQL (eso no quita que como programador debas conocerlo) y en general favorece el espagueti coding (sentencias de más de una linea con mega construcciones de tildes)

Utilizar modelos en nuestras migrations, es hambre para hoy y pan para mañana, a la larga cuando añadamos complejidad a nuestros modelos terminará generando problemas, el ejemplo:
Tenemos una migration que crea una tabla de usuarios con el campo correo (como login) y contraseña y utilizamos la misma migration para poner el usuario admin y de esta forma asegurar que siempre tenemos un primer usuario, más adelante decidimos ampliar la tabla con el campo nombre de usuario y lo hacemos obligatorio, des de este momento no podremos volver a hacer el up de esta migratio, ya que nos fallará al no tener informado el nombre de usuario.
Situaciones que lo promueven:
  • Populamos la base de datos des de las migrations. Es un error, debemos popular des del seed (prometo un articulo dedicado al seed y como popular).
  • Hacemos cambios de estructura (el ejemplo de pasar de una tabla a dos y tener que repartir la información). Ya de por si es un caso complejo, porqué nos encontramos con el código y los modelos en una versión superior, seguramente estaremos utilizando execute para acceder a la información antigua, con lo que mi recomendación es tratarlo como un caso excepcional y hacerlo todo con execute y sql directo, intentando ser lo más curiosos y encapsulando todo lo que podamos.


En caso de pánico cargar el fichero schema.rb con "db:schema:load" or "rake db:setup", el segundo incluye la carga del seed. Warning: ambas ordenes recrean la base de datos de zero, con lo que perderemos todos los registros, con lo que esto nos puede servir para un desarrollador que se une al equipo o un entorno de test / preproducción.

El resumen de consejos es:
  • Si se ha versionado (en git commit) no se puede modificar. Cremos una nueva migration y corregimos. [riesgo alto]
  • Siempre implementar la opción de up y down conjuntamente. [riesgo moderado]
  • Utilizar "active record" y evitar execute siempre que se pueda. [limitación al cambiar de SGBD]
  • Utilizar modelos en nuestras migrations. [riesgo alto]
  • Tener muchas migrations no es malo. [desmintiendo el mito]

No hay comentarios:

Publicar un comentario