2012-05-31 29 views
15

De alguien que tiene una aplicación django en un entorno de producción no trivial, ¿cómo maneja las migraciones de bases de datos? Sé que hay south, pero parece que eso extrañaría bastante si se trata de algo sustancial.Migraciones de base de datos en la producción de django

Las otras dos opciones (que puedo pensar o he usado) es hacer los cambios en una base de datos de prueba y luego (desconectarme con la aplicación) e importar esa exportación sql. O, quizás una opción más arriesgada, hacer los cambios necesarios en la base de datos de producción en tiempo real, y si algo sale mal, volver al respaldo.

¿Cómo maneja habitualmente las migraciones de su base de datos y los cambios de esquema?

+1

La mayoría de los desarrolladores de Django que conozco usan South. Yo uso el Sur yo mismo. ¿Qué crees que "extrañará bastante"? He tenido mis quejas con South, pero en su mayor parte, funciona. – super9

+0

¿Cuál es el volumen aproximado de su aplicación? – David542

Respuesta

14

Creo que hay dos partes en este problema.

Primero está gestionando el esquema de la base de datos y sus cambios. Hacemos esto usando South, manteniendo los modelos de trabajo y los archivos de migración en nuestro repositorio de SCM. Por seguridad (o paranoia), tomamos un volcado de la base de datos antes (y si estamos realmente asustados, después) de ejecutar cualquier migración. South ha sido adecuado para todos nuestros requisitos hasta ahora.

En segundo lugar está desplegando el cambio de esquema que va más allá de solo ejecutar el archivo de migración generado por South. En mi experiencia, un cambio en la base de datos normalmente requiere un cambio en el código implementado. Si tiene incluso un pequeño conjunto de servidores web, mantener el código implementado sincronizado con la versión actual del esquema de la base de datos puede no ser trivial; esto empeora si considera las diferentes capas de almacenamiento en caché y el efecto para un usuario del sitio ya activo. Los diferentes sitios manejan este problema de manera diferente, y no creo que haya una respuesta única para todos.


Resolver la segunda parte de este problema no es necesariamente sencillo. No creo que haya un enfoque único para todos, y no hay suficiente información sobre su sitio web y su entorno para sugerir una solución que sea la más adecuada para su situación. Sin embargo, creo que hay algunas consideraciones que pueden tenerse en cuenta para ayudar a guiar el despliegue en la mayoría de las situaciones.

Llevar todo el sitio (servidores web y base de datos) fuera de línea es una opción en algunos casos. Sin duda, es la forma más sencilla de administrar actualizaciones. Pero el tiempo de inactividad frecuente (incluso cuando está planificado) puede ser una buena forma de ir rápidamente a nuestro negocio, hace que sea tedioso implementar incluso pequeños cambios de código y puede llevar muchas horas si tiene un gran conjunto de datos y/o una migración compleja. Dicho esto, para los sitios que ayudo a administrar (que son todos internos y generalmente solo se usan durante las horas de trabajo en días hábiles), este enfoque funciona de maravilla.

Tenga cuidado si realiza los cambios en una copia de su base de datos maestra. El principal problema aquí es que su sitio aún está activo y, presumiblemente, acepta escrituras en la base de datos. ¿Qué sucede con los datos escritos en la base de datos maestra mientras está ocupado migrando el clon para un uso posterior? Su sitio tiene que estar inactivo todo el tiempo o poner temporalmente en estado de solo lectura, de lo contrario, los perderá.

Si sus cambios son compatibles con versiones anteriores y tiene una granja de servidores web, a veces puede salirse con la actualización del servidor de base de datos de producción en vivo (que creo que es inevitable en la mayoría de las situaciones) y luego actualizar nodos en la granja tomando fuera del equilibrador de carga durante un breve período. Esto puede funcionar bien; sin embargo, el principal problema aquí es que si un nodo que ya ha sido actualizado envía una solicitud de una URL que no es compatible con un nodo anterior, se producirá un error ya que no se puede gestionar en el nivel del equilibrador de carga.

He visto/escuchado algunas otras maneras de trabajar bien.

El primero es ajustar todos los cambios de código en un bloqueo de características que luego se puede configurar en tiempo de ejecución a través de algunas opciones de configuración en todo el sitio. Esto significa esencialmente que puede liberar código donde se desactivan todos sus cambios, y luego de que haya realizado todas las actualizaciones necesarias en sus servidores, cambia su opción de configuración para habilitar la característica. Pero esto produce un código bastante pesado ...

El segundo es dejar que el código administre la migración. He oído hablar de sitios donde los cambios en el código están escritos de tal manera que maneja la migración en tiempo de ejecución. Es capaz de detectar la versión del esquema que se está utilizando y el formato de los datos que obtuvo. Si los datos provienen del esquema anterior, hace la migración en su lugar, si los datos ya provienen del nuevo esquema, no hace nada. . A partir del uso natural del sitio, una gran parte de sus datos serán migrados por personas que usan el sitio, el resto lo puede hacer con un script de migración cuando lo desee.

Pero creo que en este punto Google se convierte en su amigo, porque como digo, la solución es muy específica del contexto y me preocupa que esta respuesta empiece a perder sentido ... Busque algo como "implementación sin tiempo cero" "y obtendrá resultados como this con muchas ideas ...

+0

Gracias por esta respuesta. ¿Podría explicar brevemente un poco más sobre cómo está logrando la segunda parte de esto? – David542

+1

Agregué algunos detalles más para la segunda parte del problema, espero que ayude. Depende también de tu escala: Facebook tiene que manejar este problema de una manera muy diferente al blog que puedes tener en tu dormitorio. –

+0

Genial, gracias por su tiempo – David542

4

Uso South para un servidor de producción con una base de código de ~ 40K líneas y hasta ahora no hemos tenido ningún problema. También hemos pasado por un par de refactores importantes para algunos de nuestros modelos y no hemos tenido ningún problema.

Una cosa que también tenemos es el control de versiones en nuestros modelos que nos ayuda a revertir cualquier cambio que hagamos a los modelos en el lado del software, con South siendo más para los datos reales. Usamos Django Reversion

+1

Solo leí la documentación de Django Reversión brevemente, pero no estaba claro para mí por qué la usaría en lugar de una SCM "adecuada" (como SVN o git) para gestionar los modelos al costado (y de la misma manera) as) el resto del código para su sitio. ¿Qué me perdí? –

+0

@MarkStreatfield: la reversión es para administrar _content_ de modelos a lo largo del tiempo, en lugar de la estructura. –

+0

Ah, ya veo, gracias por la aclaración, lo extrañé por completo de alguna manera. Por lo tanto, sería útil para hacer algo similar a lo que se describe en esta pregunta http://stackoverflow.com/questions/39281/database-design-for-revisions/126468? –

1

South no se usa en todas partes. Al igual que en mi orgainzation, tenemos 3 niveles de prueba de código. Uno de ellos es el entorno de desarrollo local, uno está organizando el entorno de desarrollo y el tercero es el de una producción.

Local Dev está en las manos de los desarrolladores donde puede jugar de acuerdo a sus necesidades. Luego viene el desarrollo provisional, que se mantiene idéntico a la producción, por supuesto, hasta que un cambio db tiene que hacerse en el sitio en vivo, donde realizamos los cambios db en la puesta en escena primero y verificamos si todo funciona bien y luego cambiamos manualmente la producción db haciéndolo idéntico a la puesta en escena de nuevo.

1

Si su base de datos no es trivial y PostgreSQL usted tiene un montón de excelentes opciones de SQL-sabia, incluyendo:

  • snapshotting y el desmantelamiento
  • la replicación en directo a un servidor de copia de seguridad
  • actualización de prueba luego vivir

La opción de actualización de prueba es agradable (pero mejor hecho en colaboración con una instantánea)

su postgres 
pg_dump <db> > $(date "+%Y%m%d_%H%M").sql 
psql template1 
# create database upgrade_test template current_db 
# \c upgradetest 
# \i upgrade_file.sql 
...assuming all well... 
# \q 
pg_dump <db> > $(date "+%Y%m%d_%H%M").sql # we're paranoid 
psql <db> 
# \i upgrade_file.sql 

Si te gusta la disposición anterior, pero está preocupado por el tiempo que se tarda en ejecutar la actualización dos veces, puede bloquear db para escrituras y luego, si la actualización a upgradetest va bien a continuación, puede cambiar el nombre de db a dbold y upgradetest a db. Hay muchas opciones.

Si tiene un archivo SQL que enumera todos los cambios que desea realizar, un comando psql extremadamente útil \set ON_ERROR_STOP 1. Esto detiene el script de actualización en sus pistas en el momento en que algo sale mal. Y, con muchas pruebas, puede asegurarse de que nada lo haga.

Hay una gran cantidad de herramientas de diferencia de esquema de base de datos disponibles, con un número anotado en this Respuesta de StackOverflow. Pero básicamente es bastante fácil de hacer con la mano ...

pg_dump --schema-only production_db > production_schema.sql 
pg_dump --schema-only upgraded_db > upgrade_schema.sql 
vimdiff production_schema.sql upgrade_schema.sql 
or 
diff -Naur production_schema.sql upgrade_schema.sql > changes.patch 
vim changes.patch (to check/edit) 
3

veces me he tomado un enfoque poco convencional (la lectura de las otras respuestas tal vez no es tan poco convencional) a este problema. Nunca lo intenté con django, así que hice algunos experimentos con él.

En resumen, dejo que el código capte la excepción resultante del esquema anterior y aplique la actualización de esquema adecuada. No espero que esta sea la respuesta aceptada; solo es apropiada en algunos casos (y algunos podrían argumentar que nunca). Pero creo que tiene una elegancia de patito feo.

Por supuesto, tengo un entorno de prueba que puedo restablecer al estado de producción en cualquier momento. Usando ese entorno de prueba, actualizo mi esquema y escribo código contra él, como de costumbre.

Luego revertir el cambio de esquema y probar el nuevo código de nuevo. Capturo los errores resultantes, realizo la actualización del esquema y luego vuelvo a intentar la consulta errónea.

La función de actualización debe escribirse para que "no dañe", por lo que si se la llama varias veces (como puede suceder cuando se pone en producción), solo actúa una vez.

código real pitón - Me puso esto al final de mi settings.py para probar el concepto, pero es probable que desee mantener en un módulo separado:

from django.db.models.sql.compiler import SQLCompiler 
from MySQLdb import OperationalError 

orig_exec = SQLCompiler.execute_sql 
def new_exec(self, *args, **kw): 
    try: 
     return orig_exec(self, *args, **kw) 
    except OperationalError, e: 
     if e[0] != 1054: # unknown column 
      raise 
     upgradeSchema(self.connection) 
     return orig_exec(self, *args, **kw) 
SQLCompiler.execute_sql = new_exec 

def upgradeSchema(conn): 
    cursor = conn.cursor() 
    try: 
     cursor.execute("alter table users add phone varchar(255)") 
    except OperationalError, e: 
     if e[0] != 1060: # duplicate column name 
      raise 

Una vez que su entorno de producción es de hasta Hasta la fecha, puede eliminar este código de autoactualización de su código base. Pero incluso si no lo hace, el código no está haciendo ningún trabajo innecesario significativo.

Usted tendría que adaptar la clase de excepción (MySQLdb.OperationalError en mi caso) y los números (1054 columna "desconocido"/1.060 "columna duplicado" en mi caso) a su motor de base de datos y el esquema de cambio, pero que debería ser fácil.

Es posible que desee añadir algunas comprobaciones adicionales para garantizar el SQL que se está ejecutando en realidad es que yerran debido al cambio de esquema en cuestión en lugar de algún otro problema, pero incluso si no lo hace, esto debería volver a subir excepción no relacionado. La única penalización es que en esa situación estaría probando la actualización y la consulta incorrecta dos veces antes de generar la excepción.

Una de mis cosas favoritas sobre python es la capacidad de anular fácilmente los métodos del sistema en tiempo de ejecución como este. Proporciona tanta flexibilidad.

Cuestiones relacionadas