Sto cercando di analizzare un problema di memoria/GC in un'app server Node.js relativamente complessa. Anche sotto carico molto moderato, sta diventando insensibile per periodi notevoli e queste pause si allungano nel tempo. L'esecuzione con il parametro --trace-gc
dimostra che i tempi di raccolta dei rifiuti estremamente lunghi sono la causa più probabile:Come eseguire il debug/analizzare pause di GC estremamente lunghe in Node.js/V8
[4805] 537 ms: Mark-sweep 17.6 (46.4) -> 10.3 (47.4) MB, 20 ms [allocation failure] [GC in old space requested].
[4805] 1338 ms: Mark-sweep 31.3 (58.4) -> 19.2 (57.2) MB, 40 ms [allocation failure] [promotion limit reached].
[4805] 2662 ms: Mark-sweep 58.0 (79.2) -> 43.9 (85.2) MB, 109 ms [Runtime::PerformGC] [promotion limit reached].
[4805] 4014 ms: Mark-sweep 90.1 (111.5) -> 70.6 (113.9) MB, 114 ms [allocation failure] [promotion limit reached].
[4805] 7283 ms: Mark-sweep 129.7 (153.9) -> 112.0 (158.9) MB, 511 ms [allocation failure] [promotion limit reached].
[4805] 10979 ms: Mark-sweep 184.6 (210.9) -> 160.3 (212.9) MB, 422 ms [Runtime::PerformGC] [promotion limit reached].
[4805] 1146869 ms: Mark-sweep 243.8 (271.4) -> 191.6 (267.9) MB, 1856 ms [allocation failure] [promotion limit reached].
[4805] 1731440 ms: Mark-sweep 282.1 (307.4) -> 197.5 (298.9) MB, 1/11230 ms [allocation failure] [promotion limit reached].
[4805] 2024385 ms: Mark-sweep 291.0 (320.8) -> 197.3 (306.9) MB, 9076 ms [Runtime::PerformGC] [promotion limit reached].
[4805] 2623396 ms: Mark-sweep 290.9 (317.1) -> 196.9 (311.9) MB, 1/15401 ms [allocation failure] [promotion limit reached].
[4805] 3223769 ms: Mark-sweep 291.4 (323.6) -> 187.8 (318.9) MB, 1/13385 ms [allocation failure] [promotion limit reached].
[4805] 4225777 ms: Mark-sweep 280.1 (324.2) -> 190.6 (315.9) MB, 1/13266 ms [allocation failure] [promotion limit reached].
[4805] 4705442 ms: Mark-sweep 286.2 (321.4) -> 195.2 (314.9) MB, 1/17256 ms [Runtime::PerformGC] [promotion limit reached].
[4805] 5225595 ms: Mark-sweep 288.3 (324.0) -> 201.7 (316.9) MB, 1/22266 ms [Runtime::PerformGC] [promotion limit reached].
[4805] 6127372 ms: Mark-sweep 296.5 (324.6) -> 200.5 (316.9) MB, 1/28325 ms [allocation failure] [promotion limit reached].
[4805] 6523938 ms: Mark-sweep 297.8 (328.9) -> 198.8 (323.9) MB, 1/27213 ms [allocation failure] [promotion limit reached].
[4805] 7355394 ms: Mark-sweep 292.1 (330.7) -> 223.9 (322.9) MB, 60202 ms [allocation failure] [promotion limit reached].
La piena (--trace-gc-verbose
) uscita può essere trovato here.
Questi registri sono il risultato di esecuzione il server con i seguenti parametri:
--expose-gc --trace-gc --trace-gc-verbose --trace-gc-ignore-scavenger --max-old-space-size=1000
Quanto più a lungo si corre, più a lungo le pause diventano (spesso diversi minuti), fino a che alla fine si blocca completamente dopo un paio di ore. La memoria disponibile non si esaurisce mai e l'RSS non si avvicina nemmeno al limite di spazio vecchio di 1000mb, quindi non sembra essere una perdita. Mi sembra che ci potrebbe essere qualcosa di piuttosto insolito all'interno del codice, che rende molto "difficile" per il GC fare il suo lavoro in tempi accettabili.
La mia domanda è: come posso analizzare ulteriormente questo problema e restringere le possibili cause? Qualche strumento raccomandabile per aiutare con problemi come questo? Sono essenzialmente alla ricerca di un approccio più efficiente rispetto alla disattivazione ingenua e su parti del codice, che sono estremamente macchinose e richiedono molto tempo.
Per inciso, gradirei molto i collegamenti a qualsiasi documentazione che spieghi i termini/messaggi utilizzati nell'output di debug GC (come "limite di promozione raggiunto") e i numeri elencati qui. Ho una comprensione molto basilare di come funziona il V8 GC (this ha aiutato molto), ma la maggior parte di quell'uscita è ancora oltre me.
Nel caso sia importante: è in esecuzione su Node.js v0.10.33 su Ubuntu 14.04 Server.
EDIT: Qualche tempo fa siamo passati a io.js, dove questo problema semplicemente non si verifica più (presumibilmente a causa della recente versione molto più V8). Non ho mai trovato la causa di questo problema con Nodo v0.10, per non parlare di una soluzione.
l'RSS non è la quantità di memoria utilizzata, almeno non nel senso descritto, heapUsed è più vicino a ciò che si desidera vedere. sembra che "i vecchi puntatori" siano dove sta andando l'ariete, quindi sospetto che una chiusura venga accidentalmente messa in cache, forse essendo eccessivamente generosa nel passare l'oggetto req/res in giro. vedere alcuni dettagli sul termine e in generale su http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection – dandavis
Buon punto: i grafici per heapUsati e heapTotal seguono esattamente la curva RSS nel mio monitoraggio strumento, però (non c'è scambio in corso per quanto posso dire). Grazie per il suggerimento "puntatori vecchi" - lo esaminerò più da vicino. – d0gb3r7
Utilizzare prototipi e OOP anziché chiusure annidate all'interno di chiusure annidate all'interno di chiusure all'infinito – Esailija