2011-06-07 15 views
6

Necesito agregar una nueva columna a una tabla django grande (5 m de fila). Tengo un sur schemamigration que crea la nueva columna. Ahora estoy escribiendo un script datamigration para rellenar la nueva columna. Se parece a esto. (Si no está familiarizado con las migraciones al sur, simplemente ignorar la orm. anteponiendo el nombre del modelo.)Migración de datos eficiente en una gran tabla django

print "Migrating %s articles." % orm.Article.objects.count() 
cnt = 0 
for article in orm.Article.objects.iterator():    
    if cnt % 500 == 0: 
     print " %s done so far" % cnt 
    # article.newfield = calculate_newfield(article) 
    article.save() 
    cnt += 1 

Cambié de objects.all a objects.iterator para reducir los requisitos de memoria. Pero algo todavía está consumiendo mucha memoria cuando ejecuto este script. Incluso con la línea realmente útil comentada como arriba, la secuencia de comandos todavía crece a usar 10+ GB de ram antes de llegar muy lejos de la mesa y renunciar a ella.

Parece que algo se aferra a estos objetos en la memoria. ¿Cómo puedo ejecutar esto para que no sea un cerdo de memoria?

FWIW, estoy usando python 2.6, django 1.2.1, south 0.7.2, mysql 5.1.

Respuesta

7

Asegúrese settings.DEBUG se establece en False. DEBUG=True llena la memoria especialmente con operaciones intensivas de base de datos, ya que almacena todas las consultas enviadas al RDBMS dentro de una vista.

Con Django 1.8 desactivado, no debería ser necesario ya que ahora se almacenan un máximo de 9000 consultas codificadas, en lugar de un número infinito antes.

+0

Lo resuelve por mí. Creo que DEBUG guarda en caché toda la consulta, por lo que si está insertando 10 GB como parte de su migración de datos, usa (más de) 10 GB de memoria. O en mi caso falla, ya que estoy usando un kernel PAE de 32 bits. –

+0

¿este ajuste todavía es necesario en las versiones más nuevas de Django? – nivniv

+0

Es necesario hasta Django 1.7, pero aún se recomienda: se ha establecido un límite estricto de 9000 consultas almacenadas en memoria en Django 1.7 o 1.8. –

2

Bienvenido a Django's ORM. Creo que este es un problema inherente.

También he tenido problemas con bases de datos grandes, dumpdata, loaddata y similares.

Tiene dos opciones.

  1. Deja de intentar usar el sur y escribe tu propia migración de ORM. Puede tener múltiples definiciones de base de datos en su configuración. Crea "viejo" y "nuevo". Escriba su propio migrador de una sola vez desde la base de datos anterior a la nueva base de datos. Una vez que se pruebe y funcione, ejecútalo por última vez y luego cambia las definiciones de la base de datos y reinicia Django.

  2. Desplácese hacia el sur y el ORM y escriba su propia migración de SQL. Use SQL sin procesar para copiar datos de la estructura anterior a la nueva estructura. Depurar por separado. Cuando sea bueno, ejecútalo una vez más y luego cambia tu configuración y reinicia Django.

No es que sur o el ORM son particularmente malo. Pero, para el procesamiento masivo en bases de datos grandes, almacenan demasiado en la memoria caché.

1

Si no necesita acceso total a los objetos, siempre puede usar un combo de only y values o values_list en su conjunto de consulta. Eso debería ayudar a reducir significativamente los requisitos de memoria, pero no estoy seguro de si será suficiente.

2

orm.Article.objects.iterator()

¿Eso ejecutar la consulta entera y guardar el resultado en la memoria? ¿O recuperar filas de la base de datos de a una por vez?

Supongo que lo hace todo a la vez. Ver si se puede reemplazar ese bucle con un cursor de base de datos que extrae los datos de forma incremental:

por ejemplo: http://docs.python.org/library/sqlite3.html#sqlite3.Cursor.fetchmany

db = blah.connect("host='%s' dbname='%s' user='%s' password='%s'" % ... 
new, old = db.cursor(), db.cursor() 
old.execute(""" 
    SELECT * 
    FROM whatever 
""") 
for row in old.fetchmany(size=500): 
    (col1, col2, col3...) = row 
    new = db.cursor() 
    new.execute(""" 
     INSERT INTO yourtable (
      col1, col2, col3...) 
     VALUES (
      %s, %s, %s, %s, %s) 
     """,(col1, col2, col3,...)) 
new.close() 
old.close() 

será lento. Saqué esto de una secuencia de comandos de migración independiente así que mmm.

fetchmany es estándar (PEP249).No he hecho exactamente lo que estás buscando, así que queda algo de trabajo por hacer con esta muestra: no he recorrido el ciclo, para obtener conjuntos de hasta 500, así que tendrás que resolverlo para ti.

+0

¿Eso funciona para mysql también? – Leopd

+0

sí. fetchmany es estándar (PEP249). –

2

O, ¿qué sucede si crea una consulta cruda in situ que implementa un límite de tamaño de conjunto de resultados rudimentario?

a la: https://docs.djangoproject.com/en/1.3/topics/db/sql/#index-lookups

while min < rowcount: 
    min += 500 
    max = min + 500 
    articles = Article.objects.raw('SELECT * from article where id > %s and id < %s' % (min, max)) 
    for old_article in articles: 
    # create the new article 
    article.save() 
Cuestiones relacionadas