Le tableau en question contient environ dix millions de lignes.
for event in Event.objects.all():
print event
Cela entraîne une augmentation constante de l'utilisation de la mémoire jusqu'à 4 Go environ, auquel cas les lignes s'impriment rapidement. Le long délai avant l'impression de la première ligne m'a surpris - je m'attendais à ce qu'il s'imprime presque instantanément.
J'ai aussi essayé Event.objects.iterator()
qui se comportait de la même manière.
Je ne comprends pas ce que Django charge en mémoire ni pourquoi il le fait. Je m'attendais à ce que Django itère à travers les résultats au niveau de la base de données, ce qui signifierait que les résultats seraient imprimés à peu près à un rythme constant (plutôt que tous en même temps après une longue attente).
Qu'est-ce que j'ai mal compris?
(Je ne sais pas si c'est pertinent, mais j'utilise PostgreSQL.)
la source
Réponses:
Nate C était proche, mais pas tout à fait.
À partir de la documentation :
Ainsi, vos dix millions de lignes sont récupérées, toutes à la fois, lorsque vous entrez pour la première fois dans cette boucle et obtenez la forme itérative de l'ensemble de requêtes. L'attente que vous rencontrez est que Django charge les lignes de la base de données et crée des objets pour chacune d'elles, avant de renvoyer quelque chose que vous pouvez réellement parcourir. Ensuite, vous avez tout en mémoire et les résultats se répandent.
D'après ma lecture de la documentation,
iterator()
ne fait rien de plus que de contourner les mécanismes de mise en cache internes de QuerySet. Je pense qu'il pourrait être judicieux de faire une chose une par une, mais cela exigerait à l'inverse dix millions de visites individuelles sur votre base de données. Peut-être pas tout à fait souhaitable.Itérer efficacement sur de grands ensembles de données est quelque chose que nous n'avons toujours pas tout à fait raison, mais il y a quelques extraits que vous pourriez trouver utiles à vos fins:
la source
Ce n'est peut-être pas le plus rapide ou le plus efficace, mais en tant que solution toute faite, pourquoi ne pas utiliser les objets Paginator et Page de django core documentés ici:
https://docs.djangoproject.com/en/dev/topics/pagination/
Quelque chose comme ça:
la source
Paginator
a maintenant lapage_range
propriété d'éviter le passe-partout. Si vous recherchez une surcharge mémoire minimale, vous pouvez utiliserobject_list.iterator()
ce qui ne remplira pas le cache de l'ensemble de requêtes .prefetch_related_objects
est alors requis pour la prélectureLe comportement par défaut de Django est de mettre en cache tout le résultat de QuerySet lorsqu'il évalue la requête. Vous pouvez utiliser la méthode iterator de QuerySet pour éviter cette mise en cache:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#iterator
La méthode iterator () évalue le jeu de requêtes, puis lit les résultats directement sans faire de mise en cache au niveau QuerySet. Cette méthode se traduit par de meilleures performances et une réduction significative de la mémoire lors de l'itération sur un grand nombre d'objets auxquels vous ne devez accéder qu'une seule fois. Notez que la mise en cache est toujours effectuée au niveau de la base de données.
Utiliser iterator () réduit l'utilisation de la mémoire pour moi, mais il est toujours plus élevé que prévu. L'utilisation de l'approche paginatrice suggérée par mpaf utilise beaucoup moins de mémoire, mais est 2 à 3 fois plus lente pour mon cas de test.
la source
Ceci provient de la documentation: http://docs.djangoproject.com/en/dev/ref/models/querysets/
Ainsi, lorsque le
print event
est exécuté, la requête se déclenche (qui est une analyse complète de la table selon votre commande) et charge les résultats. Vous demandez tous les objets et il n'y a aucun moyen d'obtenir le premier objet sans les obtenir tous.Mais si vous faites quelque chose comme:
http://docs.djangoproject.com/en/dev/topics/db/queries/#limiting-querysets
Ensuite, il ajoutera des décalages et des limites au sql en interne.
la source
Pour de grandes quantités d'enregistrements, un curseur de base de données fonctionne encore mieux. Vous avez besoin de SQL brut dans Django, le curseur Django est quelque chose de différent d'un cursur SQL.
La méthode LIMIT - OFFSET suggérée par Nate C pourrait convenir à votre situation. Pour de grandes quantités de données, il est plus lent qu'un curseur car il doit exécuter la même requête maintes et maintes fois et doit sauter de plus en plus de résultats.
la source
Django n'a pas de bonne solution pour récupérer des éléments volumineux de la base de données.
values_list peut être utilisé pour récupérer tous les identifiants dans les bases de données, puis pour récupérer chaque objet séparément. Au fil du temps, des objets volumineux seront créés en mémoire et ne seront pas récupérés tant que la boucle n'est pas terminée. Le code ci-dessus effectue un nettoyage manuel de la mémoire après chaque 100e élément consommé.
la source
Parce que de cette façon, les objets d'un ensemble de requêtes sont chargés en mémoire en une seule fois. Vous devez découper votre ensemble de requêtes en petits morceaux digestibles. Le modèle pour faire cela s'appelle l'alimentation à la cuillère. Voici une brève mise en œuvre.
Pour l'utiliser, vous écrivez une fonction qui effectue des opérations sur votre objet:
et d'exécuter cette fonction sur votre jeu de requêtes:
Cela peut être encore amélioré avec le multitraitement pour s'exécuter
func
sur plusieurs objets en parallèle.la source
Voici une solution incluant len et count:
Usage:
la source
J'utilise généralement une requête brute MySQL brute au lieu de Django ORM pour ce type de tâche.
MySQL prend en charge le mode streaming afin que nous puissions parcourir tous les enregistrements en toute sécurité et rapidement sans erreur de mémoire insuffisante.
Réf:
la source
queryset.query
pour votre exécution.