Ho un'applicazione Django che presenta alcuni strani comportamenti di raccolta dei dati inutili. C'è in particolare una visione che continuerà a far crescere la dimensione della VM in modo significativo ogni volta che viene chiamata - fino a un certo limite, momento in cui l'utilizzo si riduce nuovamente. Il problema è che ci vuole molto tempo prima che venga raggiunto quel punto, e in effetti la macchina virtuale che esegue la mia app non ha memoria sufficiente per tutti i processi FCGI per occupare la stessa quantità di memoria che a volte fanno.Python: comportamento del garbage collector
Ho passato gli ultimi due giorni a indagare su questo e a conoscere la garbage collection di Python, e penso di capire cosa sta succedendo ora - per la maggior parte. Quando si utilizza
gc.set_debug(gc.DEBUG_STATS)
Poi per una singola richiesta, viene visualizzato il seguente output:
>>> c = django.test.Client()
>>> c.get('/the/view/')
gc: collecting generation 0...
gc: objects in each generation: 724 5748 147341
gc: done.
gc: collecting generation 0...
gc: objects in each generation: 731 6460 147341
gc: done.
[...more of the same...]
gc: collecting generation 1...
gc: objects in each generation: 718 8577 147341
gc: done.
gc: collecting generation 0...
gc: objects in each generation: 714 0 156614
gc: done.
[...more of the same...]
gc: collecting generation 0...
gc: objects in each generation: 715 5578 156612
gc: done.
Quindi, in sostanza, una grande quantità di oggetti sono allocati, ma sono inizialmente trasferito in generazione 1, e quando gen 1 viene spazzato durante la stessa richiesta, vengono spostati alla generazione 2. Se faccio un manuale gc.collect (2) in seguito, vengono rimossi. E, come ho già detto, viene rimosso anche quando si verifica il successivo passaggio automatico di gen 2, che, se ho capito bene, in questo caso sarebbe tipo ogni 10 richieste (a questo punto l'app ha bisogno di circa 150 MB).
OK, quindi inizialmente ho pensato che potrebbe esserci qualche riferimento ciclico all'interno dell'elaborazione di una richiesta che impedisce a questi oggetti di essere raccolti all'interno della gestione di tale richiesta. Tuttavia, ho passato ore a provare a trovarne uno usando pympler.muppy e objgraph, sia dopo che eseguendo il debug all'interno dell'elaborazione della richiesta, e non sembra che ce ne siano. Piuttosto, sembra che i 14.000 oggetti creati durante la richiesta si trovino tutti all'interno di una catena di riferimenti a qualche oggetto globale di richiesta, cioè, una volta che la richiesta scompare, possono essere liberati.
Questo è stato il mio tentativo di spiegarlo, comunque. Tuttavia, se ciò è vero e non vi sono effettivamente dipendenze cicliche, non dovrebbe essere liberato l'intero albero degli oggetti una volta che qualsiasi oggetto della richiesta che li fa ritirare va via, senza che il garbage collector sia coinvolto, semplicemente in base al conteggio dei riferimenti scendendo a zero?
Con questo setup, qui sono le mie domande:
Condivide la sopra nemmeno senso, o devo cercare il problema altrove? E 'solo uno sfortunato incidente che dati significativi siano tenuti in giro così a lungo in questo particolare caso d'uso?
C'è qualcosa che posso fare per evitare il problema. Vedo già alcune potenzialità per ottimizzare la vista, ma questa sembra essere una soluzione con uno scopo limitato - sebbene non sia sicuro di quale sia la mia generica; quanto è consigliabile, ad esempio, chiamare manualmente gc.collect() o gc.set_threshold()?
In termini di come il garbage collector stesso funziona:
Ho capito bene che un oggetto è sempre spostato alla prossima generazione, se una spazzata guarda e determina che i riferimenti IT è non ciclico, ma può infatti essere tracciato su un oggetto radice.
Cosa succede se il gc esegue, per esempio, una scansione di generazione 1 e trova un oggetto a cui fa riferimento un oggetto all'interno della generazione 2; segue quella relazione all'interno della generazione 2, o aspetta che si verifichi uno sweep di generazione 2 prima di analizzare la situazione?
Quando si utilizza gc.DEBUG_STATS, mi preoccupo principalmente delle informazioni "oggetti in ogni generazione"; tuttavia, continuo a ricevere centinaia di "gc: 0.0740 secondi trascorsi.", "gc: 1258233035.9370 secondi trascorsi." messaggi; sono totalmente scomodi - ci vuole un tempo considerevole per essere stampati, e rendono le cose interessanti molto più difficili da trovare. C'è un modo per sbarazzarsi di loro?
Suppongo che non esista un modo per eseguire gc.get_objects() per generazione, ad esempio recuperare gli oggetti dalla generazione 2, ad esempio?
+1 per menzionare l'impostazione DEBUG = False in modo che Django non registri tutte le query SQL. – Kekoa
wow, che immagine mentale di Django che mangia molto per il cibo –