← Tutte le guide

Django iterator(): leggere milioni di righe senza esaurire la memoria

22 ottobre 2026 · 2 min di lettura

Un export o una migrazione dati che scorre tutta una tabella con for o in Ordine.objects.all() funziona in sviluppo e va in out-of-memory in produzione. Il motivo è la cache del queryset.

Cosa succede con all()

Quando cicli su un queryset Django carica tutte le righe, crea un oggetto modello per ciascuna e le tiene nella cache interna (_result_cache), così un secondo ciclo non rifà la query. Comodo, ma la memoria cresce con il numero di righe.

Cosa cambia con iterator()

for o in Ordine.objects.iterator(chunk_size=2000):
    elabora(o)

iterator() non riempie la cache: legge a blocchi (chunk_size, default 2000) e scarta gli oggetti già elaborati. La memoria resta quasi costante.

Numeri misurati

50.000 righe (testo da 200 caratteri), SQLite in memoria, Django 4.2.30, Python 3.9.6, picco misurato con tracemalloc (che rallenta tutto: contano i rapporti, non i millisecondi assoluti).

Istruzione Tempo Picco memoria
for o in qs.all() circa 820 ms circa 33 MB
qs.iterator() circa 680 ms circa 1,3 MB
qs.iterator(chunk_size=500) circa 660 ms circa 0,3 MB

Su PostgreSQL iterator() usa un cursore lato server; su SQLite il driver legge comunque a blocchi, ma il risparmio di memoria Python è lo stesso. Con PostgreSQL dietro pgbouncer in modalità transaction pooling i cursori lato server vanno disattivati (DISABLE_SERVER_SIDE_CURSORS).

Quando non usarlo

  • Servono più cicli sugli stessi dati: iterator() non memorizza nulla, ogni chiamata rifà la query. Se i dati stanno in memoria, usa all() e basta.
  • prefetch_related: da Django 4.1 funziona con iterator() solo se passi chunk_size; senza, il prefetch viene ignorato. Con select_related non ci sono problemi.
  • Tabelle piccole: sotto qualche migliaio di righe la differenza non si sente.

Regola pratica

Per elaborazioni batch, export e comandi di gestione su tabelle grandi: iterator(chunk_size=...). Per tutto il resto: all().

Lo script di misura è in verifica/iterator_memoria.py; i valori variano da macchina a macchina, conta l'ordine di grandezza.