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 :)
Django 1.4 fournit une méthode bulk_create()
sur l'objet QuerySet, voir:
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:
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;
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
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.
Drop to DB-API et utilisez cursor.executemany()
. Voir PEP 249 pour plus de détails.
J'ai exécuté quelques tests sur Django 1.10/Postgresql 9.4/Pandas 0.19.0 et j'ai obtenu les horaires suivants:
DataFrame.to_sql()
et n'obtenez pas d'ID: 774ms df.to_sql()
est plus rapide en option en combinant plusieurs insertions en une seule instruction - Je ne l'ai pas testé.bulk_create(Model(**df.to_records()))
et n'obtenez pas d'ID: 574ms to_csv
Dans StringIO
tampon et COPY
(cur.copy_from()
) et n'obtenez pas d'ID: 118ms 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.
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.
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.
Développement Django got bulk_create: https://docs.djangoproject.com/en/dev/ref/models/querysets/#Django.db.models.query.QuerySet.bulk_create