117 votes

Pourquoi est-à itérer un grand Django QuerySet de consommer des quantités massives de la mémoire?

La table en question contient environ dix millions de lignes.

for event in Event.objects.all():
    print event

Cela entraîne l'utilisation de la mémoire pour augmenter de façon constante à 4 GO ou plus, à quel point les lignes d'impression rapide. Le long délai avant la première ligne imprimée me surpris – je m'attendais à imprimer presque instantanément.

J'ai aussi essayé d' Event.objects.iterator() qui se comportaient de la même manière.

Je ne comprends pas ce que Django est le chargement dans la mémoire, ou pourquoi il fait cela. Je m'attendais à Django pour itérer sur les résultats au niveau base de données, ce qui voudrais dire que les résultats pourraient être imprimé à environ un taux constant (plutôt que tout à la fois après une longue attente).

Qu'ai-je mal compris?

(Je ne sais pas si c'est pertinent, mais je suis en utilisant PostgreSQL.)

116voto

eternicode Points 2892

Nate C était proche, mais pas tout à fait.

À partir de la documentation:

Vous pouvez évaluer un QuerySet dans l'une des manières suivantes:

  • Itération. Un QuerySet est itératif, et il exécute son requête de base de données de la première itération sur elle. Par exemple, cela permettra d'imprimer l'en-tête de toutes les entrées dans la base de données:

    for e in Entry.objects.all():
        print e.headline
    

Si votre dix millions de lignes sont extraites, tout à la fois, lorsque vous entrez dans cette boucle et obtenir l'itération de la forme de la queryset. L'attendez-vous de l'expérience est de Django chargement de la base de données des lignes et créer des objets pour chacun d'eux, avant de retourner quelque chose que vous pouvez réellement effectuer une itération sur. Alors, vous avez tout dans la mémoire, et les résultats se bousculent.

De ma lecture de la documentation, iterator() ne fait rien de plus que de contournement de QuerySet interne de mécanismes de mise en cache. Je pense qu'il peut faire sens pour un faire un une-par-une chose, mais ce serait l'inverse besoin de dix millions de visites individuelles sur votre base de données. Peut-être pas souhaitable.

Itération sur de grands ensembles de données de manière efficace est quelque chose que nous n'avons toujours pas eu tout à fait raison, mais il y a quelques extraits que vous pourriez trouver utiles pour vos besoins:

49voto

mpaf Points 650

Peut-être pas le plus rapide ni le plus efficace, mais comme une solution toute prête, pourquoi ne pas utiliser django core Paginator et des objets de Page documentée ici:

https://docs.djangoproject.com/en/dev/topics/pagination/

Quelque chose comme ceci:

from django.core.paginator import Paginator
from djangoapp.models import model

paginator = Paginator(model.objects.all(), 1000) # chunks of 1000, you can 
                                                 # change this to desired chunk size

for page in range(1, paginator.num_pages + 1):
    for row in paginator.page(page).object_list:
        # here you can do whatever you want with the row
    print "done processing page %s" % page

8voto

nate c Points 3909

C'est à partir de la documentation: http://docs.djangoproject.com/en/dev/ref/models/querysets/

Pas de base de données de l'activité qui se produit réellement jusqu'à ce que vous faire quelque chose pour évaluer le queryset.

Ainsi, lorsque l' print event est d'exécuter la requête des feux (qui est un full table scan en fonction de votre commande.) et charges les résultats. Votre demande tous les objets et il n'existe aucun moyen pour obtenir le premier objet sans se faire tous les d'entre eux.

Mais si vous faites quelque chose comme:

Event.objects.all()[300:900]

http://docs.djangoproject.com/en/dev/topics/db/queries/#limiting-querysets

Ensuite, il va ajouter des décalages et les limites du sql à l'interne.

8voto

Frank Heikens Points 29270

Pour de grandes quantités de documents, une base de données de curseur effectue encore mieux. Vous avez besoin de SQL brut dans Django, Django-curseur est quelque chose de différent qu'un SQL cursur.

La LIMITE la méthode de la compensation proposée par Nate C pourrait être assez bon pour votre situation. Pour de grandes quantités de données, il est plus lent qu'un curseur que l'on doit exécuter la même requête à maintes reprises et a pour sauter de plus en plus de résultats.

8voto

kracekumar Points 2653

Django n'ont pas une bonne solution pour aller chercher des articles de grande taille, à partir de la base de données.

import gc
# Get the events in reverse order
eids = Event.objects.order_by("-id").values_list("id", flat=True)

for index, eid in enumerate(eids):
    event = Event.object.get(id=eid)
    # do necessary work with event
    if index % 100 == 0:
       gc.collect()
       print("completed 100 items")

values_list peut être utilisé pour récupérer toutes les id dans les bases de données et de récupérer chaque objet séparément. Sur une période de grands objets seront créés dans la mémoire et ne pas être nettoyée til pour la boucle se termine. Le code ci-dessus n'manuel de collecte des ordures après chaque 100e article est consommé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X