web-dev-qa-db-fra.com

Accélérez l'insertion en masse à l'aide de l'ORM de Django?

Je prévois de télécharger un milliard d'enregistrements provenant de ~ 750 fichiers (chacun ~ 250 Mo) sur une base de données à l'aide de l'ORM de Django. Actuellement, chaque fichier prend environ 20 minutes à traiter, et je me demandais s'il y avait un moyen d'accélérer ce processus.

J'ai pris les mesures suivantes:

Que puis-je faire d'autre pour accélérer les choses? Voici quelques-unes de mes réflexions:

Tout pointeur concernant ces articles ou toute autre idée serait le bienvenu :)

43
Jonathan
33
Gary

Ce n'est pas spécifique à Django ORM, mais récemment j'ai dû insérer en masse> 60 millions de lignes de 8 colonnes de données de plus de 2000 fichiers dans une base de données sqlite3. Et j'ai appris que les trois choses suivantes réduit le temps d'insertion de plus de 48 heures à ~ 1 heure:

  1. augmentez le paramètre de taille du cache de votre base de données pour utiliser davantage RAM (ceux par défaut toujours très petits, j'ai utilisé 3 Go); dans sqlite, cela est fait par PRAGMA cache_size = n_of_pages;

  2. faire du journalisation dans RAM au lieu du disque (cela pose un léger problème si le système échoue, mais quelque chose que je considère comme négligeable étant donné que vous avez déjà les données source sur le disque); dans sqlite, cela se fait par PRAGMA journal_mode = MEMORY

  3. dernier et peut-être le plus important: ne pas construire d'index lors de l'insertion. Cela signifie également de ne pas déclarer UNIQUE ou toute autre contrainte pouvant entraîner la création d'un index par DB. Construisez l'index uniquement après avoir terminé l'insertion.

Comme quelqu'un l'a mentionné précédemment, vous devez également utiliser cursor.executemany () (ou simplement le raccourci conn.executemany ()). Pour l'utiliser, faites:

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

Les données itérables peuvent être une liste ou quelque chose de similaire, ou même un lecteur de fichiers ouvert.

16
Yanshuai Cao

Drop to DB-API et utilisez cursor.executemany(). Voir PEP 249 pour plus de détails.

12

J'ai exécuté quelques tests sur Django 1.10/Postgresql 9.4/Pandas 0.19.0 et j'ai obtenu les horaires suivants:

  • Insérez 3000 lignes individuellement et obtenez les identifiants des objets remplis en utilisant Django ORM: 3200ms
  • Insérez 3000 lignes avec Pandas DataFrame.to_sql() et n'obtenez pas d'ID: 774ms
  • Insérez 3000 lignes avec Django manager .bulk_create(Model(**df.to_records())) et n'obtenez pas d'ID: 574ms
  • Insérez 3000 lignes avec to_csv Dans StringIO tampon et COPY (cur.copy_from()) et n'obtenez pas d'ID: 118ms
  • Insérez 3000 lignes avec to_csv Et COPY et obtenez les ID via un simple SELECT WHERE ID > [max ID before insert] (Probablement pas threadsafe sauf si COPY détient un verrou sur la table empêchant les insertions simultanées?) : 201 ms
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()

Les statistiques de performances ont toutes été obtenues sur une table contenant déjà 3 000 lignes fonctionnant sous OS X (i7 SSD 16 Go), en moyenne dix exécutions en utilisant timeit.

Je récupère mes clés primaires insérées en attribuant un identifiant de lot d'importation et en triant par clé primaire, bien que je ne sois pas à 100% certaines clés primaires seront toujours affectées dans l'ordre dans lequel les lignes sont sérialisées pour la commande COPY - apprécierait les opinions de toute façon.

7
Chris

Il y a aussi un extrait d'insertion en masse à http://djangosnippets.org/snippets/446/ .

Cela donne à une commande d'insertion plusieurs paires de valeurs (INSERT INTO x (val1, val2) VALUES (1,2), (3,4) --etc etc.). Cela devrait considérablement améliorer les performances.

Il semble également être largement documenté, ce qui est toujours un plus.

5
Seaux

De plus, si vous voulez quelque chose de simple et rapide, vous pouvez essayer ceci: http://djangosnippets.org/snippets/2362/ . C'est un simple gestionnaire que j'ai utilisé sur un projet.

L'autre extrait n'était pas aussi simple et était vraiment axé sur les insertions en vrac pour les relations. Il s'agit simplement d'une insertion en bloc simple et utilise simplement la même requête INSERT.

3
Seaux
3
Ilia Novoselov