Django iterator(): leggere milioni di righe senza esaurire la memoria
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, usaall()e basta. prefetch_related: da Django 4.1 funziona coniterator()solo se passichunk_size; senza, il prefetch viene ignorato. Conselect_relatednon 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.