modulo 09 / lezione 48
Performance, memoria e osservabilità
Profiling backend e flamegraph
Profilare un servizio backend (Node, Python, Java) e leggere flamegraph generati con perf, py-spy o async-profiler per individuare hot path.
In parole semplici
Lato server, la lentezza ha le stesse sotto-categorie del client (CPU, I/O, locking) ma strumenti diversi. Il flamegraph è la rappresentazione più potente: mostra dove il programma passa il tempo, raggruppato per stack di chiamata.
Imparare a leggere un flamegraph trasforma “il server è lento” in “il metodo X consuma il 47% del tempo”.
Parole nuove
- profiler: strumento che campiona lo stack a intervalli regolari.
- sampling profiler: registra ad intervalli, leggero ma stocastico.
- flamegraph: visualizzazione di stack di chiamata in cui la larghezza = tempo.
- hot path: percorso di codice dove si concentra il tempo.
Cosa impari
- Generare un flamegraph per Node, Python o Java.
- Leggere flamegraph distinguendo larghezza, altezza, colore.
- Identificare hot path e formulare ipotesi di ottimizzazione.
Cosa è un flamegraph
Diagramma a barre orizzontali:
- Asse X: l’asse del tempo, ma non temporale: è lo spazio dei campioni raccolti.
- Asse Y: profondità dello stack. In basso main(), in alto le foglie.
- Larghezza: percentuale di campioni in cui quella funzione era nello stack.
- Colore: spesso casuale (per distinguere blocchi). Alcuni profiler usano colori semantici (rosso = on-CPU, blu = off-CPU).
Lettura: cerca la barra più larga in alto. È la funzione dove il programma passa tempo “alla foglia”. Sotto si vede il chi-la-chiama.
Strumenti per Node.js
0x
npm install -g 0x
0x app.js
# esegui, ctrl+c, apre browser con flamegraph
clinic.js
npm install -g clinic
clinic doctor -- node app.js
clinic flame -- node app.js
clinic bubbleprof -- node app.js
doctor analizza CPU/event loop, flame produce flamegraph, bubbleprof mostra dipendenze async.
Node nativo
node --prof app.js # genera log
node --prof-process isolate-*.log > processed.txt
Strumenti per Python
py-spy
pip install py-spy
py-spy record -o profile.svg -- python app.py
# oppure attach a un processo in corso
py-spy top --pid 1234
Vantaggio: non richiede modifiche al codice. Anche su processi in produzione.
cProfile
python -m cProfile -o profile.out app.py
# poi:
python -m pstats profile.out
Più verboso, granulare ma meno visuale.
Strumenti per JVM
async-profiler
./profiler.sh -d 30 -f flame.html <pid>
Open source, low overhead, supporta CPU, allocations, locks.
JFR (Java Flight Recorder)
Built-in in OpenJDK 11+. Avvio:
java -XX:StartFlightRecording=duration=30s,filename=profile.jfr -jar app.jar
Analizza con JDK Mission Control.
Lettura di un flamegraph: esempio
Scenario: endpoint /utenti lento.
Flamegraph mostra:
handle_request 100%
├── parse_request 5%
├── auth_check 8%
├── query_database 70%
│ └── orm_serialize 60%
│ └── deepClone 45% ← hot path!
└── render_response 17%
Cosa significa:
- 70% del tempo dell’handler è nel DB.
- Dentro al DB, 60% è in
orm_serialize(serializzazione dei risultati). - Dentro
orm_serialize, 45% è indeepClone.
Ipotesi di ottimizzazione:
- Hot path:
deepClone. Probabilmente serializza N volte per request. - Indagine: c’è un
JSON.parse(JSON.stringify(...))da qualche parte? Sostituibile con clone superficiale o lazy?
Distinguere CPU-bound da I/O-bound
In flamegraph CPU-only, le pause su I/O non appaiono. Indizi:
- Profilo “vuoto” durante il request → I/O.
- Profilo pieno di
await/Promise→ forse libreria spende CPU su parsing.
Strumenti per I/O:
- wall-clock profiler (mostra tempo reale, inclusi await):
clinic bubbleprof,async-profilerconwallmode.
Workflow di indagine performance backend
- Identifica l’endpoint lento: log o APM (modulo 9.4).
- Riproduci in staging con carico simile.
- Profila per 30-60 secondi.
- Leggi flamegraph: trova le 3 barre più larghe.
- Formula ipotesi: per ognuna, perché è così grande?
- Prova ottimizzazione mirata: una alla volta.
- Misura di nuovo: confronto.
Ottimizzazioni tipiche
- N+1 query: una query principale + N query per oggetto correlato. Sostituire con
JOINo eager load. - Serializzazione ripetuta: cache.
- Hash/crittografia in hot path: spostare offline o asincrono.
- JSON.parse di file enormi a ogni request: caricare in memoria una volta.
- Algoritmi
O(n²)su dataset grandi.
Esercizio guidato
Per un piccolo server Node:
- Scrivi un endpoint che fa un’operazione finta lenta (es. loop pesante o JSON.parse di un file grande).
- Esegui
clinic flame -- node app.js. Carica conautocannonocurlripetuto. - Stop. Apri il flamegraph.
- Identifica la barra più larga in alto. Quale funzione?
- Ottimizza. Rifai il profilo. Differenza?
Errori frequenti del principiante
- Ottimizzare senza profilare.
- Confondere larghezza con altezza. Larghezza = tempo. Altezza = profondità.
- Profilare in dev con HMR. Risultati inutilizzabili.
- Profilare per 5 secondi. Troppo poco: il sampling non statistico.