2010-11-27 16 views
41

Estoy planeando subir un billón de registros tomados de ~ 750 archivos (cada uno ~ 250MB) a un db usando el ORM de django. Actualmente cada archivo tarda ~ 20min en procesarse, y me preguntaba si hay alguna forma de acelerar este proceso.¿Acelerar la inserción masiva usando el ORM de Django?

que he tomado las siguientes medidas:

¿Qué más puedo hacer para acelerar las cosas? Éstos son algunos de mis pensamientos:

cualquier indicador con respecto a estos artículos o cualquier otra idea sería bienvenido :)

+0

También puede consultar la herramienta ETL como Pentaho Kettle. – rwilliams

+4

Optimizar las cosas de Python es casi seguro un desperdicio, ya que casi todo su tiempo se gasta en llamadas a bases de datos. Optimización 101, mida para saber a dónde va el tiempo de su programa antes de perder su tiempo tratando de optimizar las cosas equivocadas. La mayor ganancia aquí será mediante el uso de consultas de inserción masiva. – Eloff

+0

Recientemente hice algunos experimentos interesantes con django 1.8.5. Creo que crear un modelo es lo que más consume tiempo, cuando la cantidad de registros llega a 1 millón. Hay muchos controles de django invisibles detrás de la escena. Mi solución es usar SQL sin formato y 'cursor.executemany' en lugar de' bulk_create'. En mi caso, el tiempo se acorta de 13 minutos a 54 segundos. http://stackoverflow.com/questions/32805766/best-practice-of-bulk-create-for-massive-records – stanleyxu2005

Respuesta

30
+0

+1 si está utilizando Django 1.4 este es el camino a seguir. – santiagobasulto

+2

A menos que necesite recuperar las claves automáticas. Eso es posible con alguna base de datos al menos (MySQL, Postgres) si usa una consulta sin formato. – Eloff

+0

Las claves automáticas automáticas ahora están disponibles al menos desde 1.11 para postgresql. Nota adicional de que está considerando bulk_create para el rendimiento: https://code.djangoproject.com/ticket/28231 – NirIzr

12

Drop to DB-API y use cursor.executemany(). Vea PEP 249 para más detalles.

+1

No pude encontrar ninguna documentación \ ejemplos de cómo usar executemany() así que usé execute() pero cada vez que preparé una instrucción sql que insertó 3000 registros como una vez. Esto aceleró las cosas. Gracias – Jonathan

+0

La documentación está en PEP 249. –

+0

Si desea un ejemplo de 'cursor.executemany()', hay uno en esta otra [respuesta] (http://stackoverflow.com/questions/4298278/django-using- custom-raw-sql-insert-with-executemany-and-mysql/6101536 # 6101536) – Papples

5

También hay un fragmento de inserción masiva en http://djangosnippets.org/snippets/446/.

Esto le da a un comando insertar múltiples pares de valores (INSERT INTO x (val1, val2) VALUES (1,2), (3,4) - etc, etc. Esto debería mejorar mucho el rendimiento.

También parece estar muy documentada, lo que siempre es un plus.

3

Además, si quieres algo rápido y simple, puedes probar esto: http://djangosnippets.org/snippets/2362/. Es un administrador simple que utilicé en un proyecto.

El otro fragmento no era tan simple y se centraba realmente en inserciones masivas para las relaciones. Esto es simplemente una inserción masiva simple y solo usa la misma consulta INSERT.

15

Esto no es específico a Django ORM, pero recientemente he tenido que inserción masiva> 60 Millones de filas de 8 columnas de datos de más de 2000 archivos en una base de datos sqlite3. Y aprendí que las siguientes tres cosas reducen el tiempo de inserción de más de 48 horas a ~ 1 hora:

  1. aumento del valor de tamaño de caché de su base de datos a utilizar más memoria RAM (falta de pago siempre muy pequeña, solía 3GB); en sqlite, esto lo hace PRAGMA cache_size = n_of_pages;

  2. qué journalling en la memoria RAM en lugar de disco (esto causa una ligera problema si falla el sistema, pero algo que considero que es insignificante dado que dispone de los datos de origen en el disco ya); en sqlite esto se hace mediante PRAGMA journal_mode = MEMORY

  3. último y tal vez el más importante: no compilar el índice mientras se inserta . Esto también significa no declarar una restricción ÚNICA u otra que pueda provocar que DB construya un índice. Genere índice solo después de que haya terminado de insertar.

Como alguien ha mencionado anteriormente, también se debe utilizar cursor.executemany() (o simplemente la conn.executemany acceso directo()). Para usarlo, hacer:

cursor.executemany('INSERT INTO mytable (field1, field2, field3) VALUES (?, ?, ?)', iterable_data) 

El iterable_data podría ser una lista o algo igual, o incluso un lector de archivos abiertos.

+0

¿Alguna vez ha usado peewee ORM? Estoy teniendo problemas de rendimiento con la inserción masiva – themantalope

5

Me corrieron algunas pruebas de Django 1.10/PostgreSQL 9.4/pandas 0.19.0 y tiene los siguientes horarios:

  • Insertar filas 3000 de forma individual y obtener los identificadores de objetos pobladas utilizando Django ORM: 3200ms
  • Insertar
  • 3000 filas con pandas DataFrame.to_sql() y los identificadores de no conseguir : 774ms
  • Insertar 3000 filas con administrador de Django .bulk_create(Model(**df.to_records())) y los ID de no conseguir : 574ms
  • Insertar 3000 filas con to_csv a StringIO tampón y COPY (cur.copy_from()) y el ID de no conseguir: 118ms
  • Insertar 3000 filas con to_csv y COPY y obtener identificaciones a través de simples SELECT WHERE ID > [max ID before insert] (probablemente no threadsafe menos COPY sostiene un candado en la mesa que impide las inserciones simultáneas?): 201ms
def bulk_to_sql(df, columns, model_cls): 
    """ Inserting 3000 takes 774ms avg """ 
    engine = ExcelImportProcessor._get_sqlalchemy_engine() 
    df[columns].to_sql(model_cls._meta.db_table, con=engine, if_exists='append', index=False) 


def bulk_via_csv(df, columns, model_cls): 
    """ Inserting 3000 takes 118ms avg """ 
    engine = ExcelImportProcessor._get_sqlalchemy_engine() 
    connection = engine.raw_connection() 
    cursor = connection.cursor() 
    output = StringIO() 
    df[columns].to_csv(output, sep='\t', header=False, index=False) 
    output.seek(0) 
    contents = output.getvalue() 
    cur = connection.cursor() 
    cur.copy_from(output, model_cls._meta.db_table, null="", columns=columns) 
    connection.commit() 
    cur.close() 

Las estadísticas de rendimiento se obtuvieron todos en una tabla que ya contiene 3.000 filas que se ejecutan en OS X (i7 SSD 16 GB), promedio de diez carreras usando timeit.

Devuelvo las claves primarias insertadas asignando un ID de lote importado y ordenando por clave principal, aunque no estoy 100% seguro de que las claves primarias siempre se asignarán en el orden en que las filas se serializan para el comando COPY. apreciar las opiniones de cualquier manera.

Cuestiones relacionadas