06 lug 2025·8 min di lettura

Profilazione memoria Go per picchi di traffico: guida pprof

La profilazione della memoria in Go ti aiuta a gestire picchi improvvisi di traffico. Una guida pratica con pprof per individuare i punti caldi di allocazione in JSON, scansioni DB e middleware.

Profilazione memoria Go per picchi di traffico: guida pprof

Cosa fanno i picchi improvvisi di traffico alla memoria di un servizio Go

Un “picco di memoria” in produzione raramente significa che un solo numero è salito. Potresti vedere RSS (memoria processo) crescere rapidamente mentre l'heap di Go si muove poco, oppure l'heap può crescere e calare a ondate quando il GC entra in azione. Contemporaneamente la latenza spesso peggiora perché il runtime passa più tempo a liberare memoria.

Pattern comuni nelle metriche:

  • RSS sale più velocemente del previsto e a volte non scende del tutto dopo il picco
  • Heap in-use aumenta, poi cala in cicli netti quando il GC gira più spesso
  • Il tasso di allocazione salta (byte allocati al secondo)
  • Il tempo di pausa del GC e il tempo CPU del GC aumentano, anche se ogni pausa è breve
  • La latenza delle richieste aumenta e la latenza di coda diventa rumorosa

I picchi di traffico amplificano le allocazioni per richiesta perché gli sprechi “piccoli” scalano linearmente con il carico. Se una richiesta alloca 50 KB in più (buffer JSON temporanei, oggetti per lo scan di ogni riga, dati di contesto middleware), a 2.000 richieste al secondo stai dando all'allocatore circa 100 MB al secondo. Go può gestire molto, ma il GC deve comunque tracciare e liberare quegli oggetti a vita breve. Quando le allocazioni superano la pulizia, il target dell'heap cresce, RSS segue e puoi raggiungere i limiti di memoria.

I sintomi sono familiari: OOM dal tuo orchestrator, salti improvvisi di latenza, più tempo speso nel GC e un servizio che sembra “occupato” anche quando la CPU non è saturata. Puoi anche avere thrash del GC: il servizio resta su ma continua ad allocare e raccogliere, quindi la throughput cala proprio quando ne hai più bisogno.

pprof aiuta a rispondere rapidamente a una domanda: quali percorsi di codice allocano di più e quelle allocazioni sono necessarie? Un profilo heap mostra cosa è trattenuto in questo momento. Le viste focalizzate sulle allocazioni (come alloc_space) mostrano cosa viene creato e buttato via.

Quello che pprof non fa è spiegare ogni byte di RSS. RSS include più dell'heap Go (stack, metadata del runtime, mappature OS, allocazioni cgo, frammentazione). pprof è ottimo per indicare i punti caldi di allocazione nel tuo codice Go, non per dimostrare un totale esatto a livello di container.

Configurare pprof in sicurezza (passo dopo passo)

pprof è più semplice da usare come endpoint HTTP, ma quegli endpoint possono rivelare molte informazioni sul tuo servizio. Trattali come una funzione di amministrazione, non come un'API pubblica.

1) Aggiungi gli endpoint pprof

In Go, la configurazione più semplice è eseguire pprof su un server admin separato. Questo tiene le route di profiling lontane dal router principale e dal middleware.

package main

import (
	"log"
	"net/http"
	_ "net/http/pprof"
)

func main() {
	go func() {
		// Admin only: bind to localhost
		log.Println(http.ListenAndServe("127.0.0.1:6060", nil))
	}()

	// Your main server starts here...
	// http.ListenAndServe(":8080", appHandler)
	select {}
}

Se non puoi aprire una seconda porta, puoi montare le route pprof nel server principale, ma è più facile esporle per sbaglio. Una porta admin separata è il default più sicuro.

2) Mettilo al sicuro prima di distribuire

Parti da controlli difficili da sbagliare. Legare l'endpoint a localhost significa che non è raggiungibile da internet a meno che qualcuno non esponga anche quella porta.

Una checklist rapida:

  • Esegui pprof su una porta admin, non sulla porta principale rivolta agli utenti
  • Bind a 127.0.0.1 (o a un'interfaccia privata) in produzione
  • Aggiungi una allowlist al bordo di rete (VPN, bastion o subnet interna)
  • Richiedi autenticazione se il tuo perimetro può applicarla (basic auth o token)
  • Verifica di poter recuperare i profili che userai: heap, allocs, goroutine

3) Build e rollout in sicurezza

Mantieni la modifica piccola: aggiungi pprof, spediscilo e conferma che sia raggiungibile solo da dove ti aspetti. Se hai staging, testalo lì prima simulando del carico e catturando un heap e un profilo allocs.

Per la produzione, fai rollout graduale (una istanza o una piccola fetta di traffico). Se pprof è mal configurato, il raggio d'azione rimane piccolo mentre sistemi il problema.

Catturare i profili giusti durante un picco

Durante un picco, una singola istantanea raramente basta. Cattura una piccola timeline: qualche minuto prima del picco (baseline), durante il picco (impatto) e qualche minuto dopo (recupero). Questo rende più facile separare cambi reali dalle normali oscillazioni di warm-up.

Se puoi riprodurre il picco con carico controllato, cerca di imitare la produzione: mix di richieste, dimensione dei payload e concorrenza. Un picco di richieste piccole si comporta molto diverso da uno di grandi risposte JSON.

Prendi sia un profilo heap sia uno focalizzato sulle allocazioni. Rispondono a domande diverse:

  • Heap (inuse) mostra cosa è ancora in memoria al momento della cattura
  • Allocazioni (alloc_space o alloc_objects) mostrano cosa viene allocato intensamente, anche se viene liberato presto

Un pattern pratico: cattura un profilo heap, poi un profilo allocs, quindi ripeti 30–60 secondi dopo. Due punti durante il picco aiutano a capire se un percorso sospetto è stabile o in accelerazione.

# examples: adjust host/port and timing to your setup
curl -o heap_during.pprof "http://127.0.0.1:6060/debug/pprof/heap"
curl -o allocs_30s.pprof "http://127.0.0.1:6060/debug/pprof/allocs?seconds=30"

Oltre ai file pprof, registra alcune statistiche runtime in modo da spiegare cosa faceva il GC nello stesso momento. Dimensione heap, numero di GC e tempo di pausa sono quasi sempre sufficienti. Anche una breve riga di log per ogni cattura aiuta a correlare “le allocazioni sono aumentate” con “il GC ha iniziato a girare costantemente”.

Tieni note dell'incidente mentre procedi: versione build (commit/tag), versione Go, flag importanti, cambi di configurazione e quale traffico c'era (endpoint, tenant, dimensione payload). Questi dettagli spesso contano quando poi confronti i profili e scopri che il mix di richieste è cambiato.

Come leggere profili heap e di allocazione

Un profilo heap risponde a domande diverse a seconda della vista.

Inuse mostra cosa è ancora in memoria al momento della cattura. Usalo per fughe di memoria, cache a lunga durata o richieste che lasciano oggetti dietro.

Alloc space (allocazioni totali) mostra cosa è stato allocato nel tempo, anche se è stato liberato in fretta. Usalo quando i picchi causano molto lavoro di GC, salti di latenza o OOM dovuti al churn.

Il campionamento conta. Go non registra ogni singola allocazione; le campiona (controllato da runtime.MemProfileRate), così piccole, frequenti allocazioni possono essere sotto-rappresentate e i numeri sono stime. I colpevoli più grandi tendono comunque a emergere, specialmente sotto picco. Cerca tendenze e contributori principali, non contabilità perfetta.

Le viste pprof più utili:

  • top: una lettura rapida di chi domina inuse o alloc (controlla sia flat che cumulative)
  • list : sorgenti a livello di riga dentro una funzione calda
  • graph: percorsi di chiamata che spiegano come ci sei arrivato

I diff sono dove diventa pratico. Confronta un profilo baseline (traffico normale) con uno da picco per evidenziare cosa è cambiato, invece di inseguire rumore di fondo.

Convalida le scoperte con una piccola modifica prima di un grande refactor:

  • Riusa un buffer (o aggiungi un piccolo sync.Pool) nel percorso caldo
  • Riduci la creazione di oggetti per richiesta (per esempio evita di costruire mappe intermedie per il JSON)
  • Riprofilare sotto lo stesso carico e confermare che il diff si riduce dove ti aspettavi

Se i numeri si muovono nella direzione giusta, hai trovato una causa reale, non solo un rapporto allarmante.

Trovare punti caldi di allocazione nell'encoding JSON

Build APIs faster
Ship production-ready APIs with database modeling and business logic built in.
Crea backend

Durante i picchi, il lavoro JSON può diventare una voce significativa di memoria perché viene eseguito su ogni richiesta. I punti caldi JSON spesso appaiono come molte piccole allocazioni che aumentano il carico sul GC.

Segnali d'allarme in pprof

Se l'heap o la vista delle allocazioni punta a encoding/json, guarda cosa passi al pacchetto. Questi pattern comunemente gonfiano le allocazioni:

  • Usare map[string]any (o []any) per le risposte invece di struct tipizzate
  • Marshaling dello stesso oggetto più volte (per esempio, sia per il log che per la risposta)
  • Pretty printing con json.MarshalIndent in produzione
  • Costruire JSON tramite stringhe temporanee (fmt.Sprintf, concatenazione) prima del marshal
  • Convertire grandi []byte in string (o viceversa) solo per adattarsi a un'API

json.Marshal alloca sempre un nuovo []byte per l'output completo. json.NewEncoder(w).Encode(v) di solito evita quel grande buffer perché scrive su un io.Writer, ma può comunque allocare internamente, specialmente se v è pieno di any, mappe o strutture con molti puntatori.

Fix rapidi e esperimenti veloci

Inizia con struct tipizzate per la forma della risposta. Ridurranno il lavoro di reflection ed eviteranno il boxing per campo nelle interfacce.

Poi rimuovi temporanei per richiesta evitabili: riusa bytes.Buffer tramite sync.Pool (con attenzione), evita l'indentazione in produzione e non rimarshalizzare solo per i log.

Esperimenti semplici per confermare che il JSON è il colpevole:

  • Sostituisci map[string]any con una struct per un endpoint caldo e confronta i profili
  • Passa da Marshal a Encoder scrivendo direttamente nella risposta
  • Rimuovi MarshalIndent o formattazioni di debug e riprofilare con lo stesso carico
  • Salta l'encoding JSON per risposte cacheate e misura la riduzione

Trovare punti caldi nelle scansioni delle query

Quando la memoria salta durante un picco, le letture dal database sono una sorpresa comune. È facile concentrarsi sul tempo SQL, ma lo step di scan può allocare molto per riga, soprattutto quando scansiona in tipi flessibili.

Colpevoli comuni:

  • Scansionare in interface{} (o map[string]any) lasciando che il driver decida i tipi
  • Convertire []byte in string per ogni campo
  • Usare wrapper nullable (sql.NullString, sql.NullInt64) in grandi result set
  • Recuperare grosse colonne text/blob che non servono sempre

Un pattern che brucia memoria silenziosamente è scansionare i dati di riga in variabili temporanee, poi copiarli in una struct vera (o costruire una mappa per riga). Se puoi scansionare direttamente in una struct con campi concreti, eviti allocazioni extra e controlli di tipo.

Dimensione del batch e paginazione cambiano la forma della memoria. Recuperare 10.000 righe in una slice alloca per la crescita della slice e per ogni riga, il tutto in una volta. Se l'handler ha bisogno solo di una pagina, spingila nella query e mantieni la dimensione pagina stabile. Se devi processare molte righe, streammale e aggrega piccoli riassunti invece di memorizzare ogni riga.

Le grandi colonne testuali meritano attenzione. Molti driver restituiscono il testo come []byte. Convertirlo in string copia i dati, quindi farlo per ogni riga può far esplodere le allocazioni. Se hai bisogno del valore solo talvolta, ritarda la conversione o scansiona meno colonne per quell'endpoint.

Per confermare se è il driver o il tuo codice a allocare di più, guarda cosa domina i profili:

  • Se i frame puntano al tuo codice di mapping, concentrati sui target di scan e sulle conversioni
  • Se i frame puntano in database/sql o nel driver, riduci righe/colonne prima e poi considera opzioni specifiche del driver
  • Controlla sia alloc_space che alloc_objects; molte piccole allocazioni possono essere peggiori di poche grandi

Esempio: un endpoint “list orders” fa SELECT * in []map[string]any. Durante un picco, ogni richiesta costruisce migliaia di piccole mappe e stringhe. Cambiare la query per selezionare solo le colonne necessarie e scansionare in []Order{ID int64, Status string, TotalCents int64} spesso riduce le allocazioni subito. Lo stesso vale se stai profilando un backend Go generato da AppMaster: il punto caldo è solitamente come formi e scansioni i dati, non il database in sé.

Pattern di middleware che allocano silenziosamente per richiesta

Keep code clean as you iterate
Regenerate clean Go code when requirements change, without leaving old memory-heavy paths behind.
Genera codice

Il middleware sembra economico perché è “solo un wrapper”, ma gira su ogni richiesta. Durante un picco, piccole allocazioni per richiesta si sommano e appaiono come un tasso di allocazione crescente.

Il middleware di logging è una sorgente comune: formattare stringhe, costruire mappe di campi o copiare header per una presentazione migliore. I helper per gli ID di richiesta possono allocare quando generano un ID, lo convertono in stringa e lo attaccano al context. Anche context.WithValue può allocare se inserisci nuovi oggetti (o nuove stringhe) ad ogni richiesta.

Compressione e gestione del body sono altri colpevoli frequenti. Se il middleware legge tutto il body per “sbirciare” o validarlo, puoi finire con un grande buffer per richiesta. Il middleware gzip può allocare molto se crea reader e writer nuovi ogni volta invece di riusare buffer.

Auth e session layer sono simili. Se ogni richiesta parse token, decodifica cookie in base64 o carica blob di sessione in struct fresche, ottieni churn costante anche quando il lavoro dell'handler è leggero.

Tracing e metriche possono allocare più del previsto quando le label vengono costruite dinamicamente. Concatenare nomi di route, user agent o tenant ID in nuove stringhe per richiesta è un costo nascosto classico.

Pattern che spesso appaiono come “morte da mille tagli”:

  • Costruire log con fmt.Sprintf e nuove map[string]any per richiesta
  • Copiare header in nuove mappe o slice per logging o signing
  • Allocare nuovi buffer e reader/writer gzip invece di fare pooling
  • Creare label metriche ad alta cardinalità (molte stringhe uniche)
  • Inserire nuove struct nel context ad ogni richiesta

Per isolare il costo del middleware, confronta due profili: uno con tutta la catena attiva e uno con middleware disabilitato o sostituito da un no-op. Un test semplice è un endpoint di health che dovrebbe allocare quasi nulla. Se /health alloca molto durante un picco, l'handler non è il problema.

Se generi backend Go con AppMaster, la stessa regola vale: tieni le feature cross-cutting misurabili e tratta le allocazioni per richiesta come un budget da auditare.

Fix che spesso ripagano velocemente

Export and inspect everything
Export the backend source and run your usual tooling, including pprof and load tests.
Esporta sorgente

Una volta che hai le viste heap e allocs da pprof, dai priorità ai cambi che riducono le allocazioni per richiesta. L'obiettivo non è l'astuzia: è che il percorso caldo crei meno oggetti a vita breve, specialmente sotto carico.

Inizia con i miglioramenti sicuri e noiosi

Se le dimensioni sono prevedibili, prealloca. Se un endpoint ritorna di solito circa 200 elementi, crea la slice con capacity 200 così non crescerà e non si copierà più volte.

Evita di costruire stringhe nei percorsi caldi. fmt.Sprintf è comodo ma spesso alloca. Per il logging, preferisci campi strutturati e riusa un piccolo buffer quando ha senso.

Se generi grosse risposte JSON, considera di streammarle invece di costruire un grande []byte o string in memoria. Un pattern comune è: arriva la richiesta, leggi un grosso body, costruisci una grande risposta, la memoria sale finché il GC non recupera.

Cambi rapidi che tipicamente si vedono chiaramente nei profili prima/dopo:

  • Preallocare slice e map quando conosci l'intervallo di dimensione
  • Sostituire formattazioni fmt pesanti nel handling delle richieste con alternative più leggere
  • Streammare grandi risposte JSON (encode direttamente sul response writer)
  • Usare sync.Pool per oggetti riutilizzabili e di forma costante (buffer, encoder) e restituirli coerentemente
  • Impostare limiti sulle richieste (dimensione body, payload, page size) per limitare i casi peggiori

Usa sync.Pool con attenzione

sync.Pool aiuta quando ripeti l'allocazione dello stesso oggetto, come un bytes.Buffer per richiesta. Può però peggiorare le cose se fai pooling di oggetti con dimensioni imprevedibili o dimentichi di resettarli, mantenendo vive grandi array sottostanti.

Misura prima e dopo usando lo stesso carico:

  • Cattura un profilo allocs durante la finestra del picco
  • Applica una modifica alla volta
  • Riesegui lo stesso mix di richieste e confronta gli allocs/op totali
  • Guarda la latenza di coda, non solo la memoria

Se generi backend Go con AppMaster, questi fix si applicano ancora al codice personalizzato intorno ad handler, integrazioni e middleware. È in quei punti che le allocazioni guidate da picchi tendono a nascondersi.

Errori comuni con pprof e falsi allarmi

Il modo più veloce per sprecare una giornata è ottimizzare la cosa sbagliata. Se il servizio è lento, parti dalla CPU. Se viene killato per OOM, parti dall'heap. Se sopravvive ma il GC lavora senza sosta, guarda il tasso di allocazione e il comportamento del GC.

Un'altra trappola è guardare il solo “top” e pensare di aver finito. “Top” nasconde il contesto. Ispeziona sempre le call stack (o un flame graph) per vedere chi ha chiamato l'allocatore. La soluzione spesso è una o due frame sopra la funzione calda.

Occhio anche a confondere inuse con churn. Una richiesta potrebbe allocare 5 MB di oggetti a vita breve, scatenare GC extra e finire con solo 200 KB inuse. Se guardi solo inuse, perdi il churn. Se guardi solo le allocazioni totali, potresti ottimizzare qualcosa che non rimane residente e non è rischioso per OOM.

Controlli rapidi prima di cambiare il codice:

  • Conferma la vista giusta: heap inuse per retention, alloc_space/alloc_objects per churn
  • Confronta le stack, non solo i nomi delle funzioni (encoding/json è spesso sintomo)
  • Riproduci il traffico realisticamente: stessi endpoint, dimensioni dei payload, header, concorrenza
  • Cattura baseline e profilo da picco, poi fai il diff

Test di carico non realistici causano falsi allarmi. Se il tuo test invia corpi JSON piccolissimi ma in produzione sono 200 KB, ottimizzerai il percorso sbagliato. Se il test ritorna una riga DB, non vedrai il comportamento di scanning che appare con 500 righe.

Non inseguire il rumore. Se una funzione appare solo nel profilo da picco (non nella baseline), è un buon indizio. Se appare in entrambi allo stesso livello, potrebbe essere lavoro normale di background.

Un walkthrough realistico di incidente

Cut churn at the source
Model data, APIs, and logic without hand-writing boilerplate that adds allocations.
Inizia a costruire

Una promozione di lunedì mattina parte e la tua API Go inizia a ricevere 8x traffico normale. Il primo sintomo non è un crash. RSS sale, il GC si fa più attivo e la p95 di latenza schizza. L'endpoint più caldo è GET /api/orders perché l'app mobile lo aggiorna a ogni apertura di schermata.

Prendi due snapshot: uno a servizio calmo (baseline) e uno durante il picco. Cattura lo stesso tipo di profilo heap in entrambi i casi così il confronto resta equo.

Il flusso che funziona sul momento:

  • Prendi un profilo heap baseline e annota RPS, RSS e p95
  • Durante il picco, prendi un altro profilo heap più un profilo allocs nella stessa finestra di 1–2 minuti
  • Confronta i principali allocator tra i due e focalizzati su ciò che è cresciuto di più
  • Cammina dalla funzione più grande verso i suoi caller finché non arrivi al percorso dell'handler
  • Fai una piccola modifica, deploya su una singola istanza e riprofilare

In questo caso il profilo da picco mostrò che la maggior parte delle nuove allocazioni veniva dall'encoding JSON. L'handler costruiva map[string]any per le righe, poi faceva json.Marshal su una slice di mappe. Ogni richiesta creava molte stringhe e valori interfaccia a vita breve.

La correzione più piccola e sicura fu smettere di costruire mappe. Scansionare le righe del DB direttamente in una struct tipizzata e codificare quella slice. Niente altro cambiò: stesse proprietà, stessa forma di risposta, stessi status code. Dopo il rollback graduale su un'istanza, le allocazioni nel percorso JSON calarono, il tempo di GC scese e la latenza si stabilizzò.

Solo dopo si fece rollout graduale monitorando memoria, GC ed errori. Se generi servizi su una piattaforma no-code come AppMaster, è anche un promemoria a mantenere i modelli di risposta tipizzati e coerenti, perché aiuta a evitare costi nascosti di allocazione.

Prossimi passi per prevenire il prossimo picco di memoria

Una volta stabilizzato il picco, rendi il prossimo noioso. Tratta il profiling come una procedura ripetibile.

Scrivi un runbook breve che il team possa seguire anche quando è stanco. Deve dire cosa catturare, quando catturarlo e come confrontarlo con una baseline nota. Mantienilo pratico: comandi esatti, dove mettere i profili e cosa significa “normale” per i tuoi principali allocator.

Aggiungi monitoraggio leggero per la pressione delle allocazioni prima di arrivare a OOM: dimensione heap, cicli di GC al secondo e byte allocati per richiesta. Rilevare “allocazioni per richiesta +30% settimana su settimana” è spesso più utile che aspettare l'allarme di memoria.

Spingi i controlli prima con un breve load test in CI su un endpoint rappresentativo. Piccoli cambi di risposta possono raddoppiare le allocazioni se scatenano copie extra, e è meglio trovare questo prima che il traffico di produzione lo faccia.

Se esegui un backend generato, esporta il sorgente e profilalo allo stesso modo. Il codice generato è comunque codice Go e pprof punterà a funzioni e righe reali.

Se i requisiti cambiano spesso, AppMaster (appmaster.io) può essere un modo pratico per ricostruire e rigenerare backend Go puliti mentre l'app evolve, quindi profilare il codice esportato sotto carico realistico prima di distribuirlo.

FAQ

Perché un improvviso picco di traffico fa aumentare la memoria anche se il mio codice non è cambiato?

Un picco aumenta spesso il tasso di allocazione più di quanto pensi. Anche piccoli oggetti temporanei per richiesta si sommano linearmente con l'aumento delle RPS, costringendo il GC a lavorare di più e facendo salire la memoria anche se non ci sono cambiamenti nel codice.

Perché RSS cresce mentre l'heap di Go sembra stabile?

Le metriche dell'heap misurano la memoria gestita da Go, mentre RSS include anche altro: stack delle goroutine, metadati del runtime, mappature OS, frammentazione e eventuali allocazioni non-heap (incluso cgo). Durante i picchi è normale che RSS e heap si comportino diversamente: usa pprof per trovare i punti caldi di allocazione invece di cercare di far combaciare RSS esattamente.

Dovrei guardare prima l'heap o i profili di allocazione durante un picco?

Se sospetti retention (oggetti che restano vivi), parti da un profilo heap. Se sospetti churn (molti oggetti a vita breve), cattura un profilo orientato alle allocazioni, come allocs/alloc_space. Nei picchi di traffico spesso il churn è il problema principale perché aumenta il tempo di GC e la latenza di coda.

Qual è il modo più sicuro per esporre pprof in produzione?

Il setup più semplice e sicuro è esporre pprof su un server admin separato legato a 127.0.0.1 e renderlo raggiungibile solo tramite accesso interno. Tratta pprof come una interfaccia di amministrazione perché espone dettagli interni del servizio.

Quanti profili dovrei catturare e quando?

Cattura una breve timeline: un profilo qualche minuto prima del picco (baseline), uno durante il picco (impatto) e uno dopo (recupero). Questo aiuta a capire cosa è cambiato rispetto al comportamento normale.

Qual è la differenza tra inuse e alloc_space in pprof?

Usa inuse per capire cosa è effettivamente mantenuto al momento della cattura, e alloc_space (o alloc_objects) per vedere cosa viene creato intensamente. Un errore comune è guardare solo inuse e perdere il churn che causa thrash del GC.

Quali sono i modi più rapidi per ridurre le allocazioni legate al JSON?

Se encoding/json domina le allocazioni, spesso il problema è la forma dei dati. Sostituire map[string]any con struct tipizzate, evitare json.MarshalIndent in produzione e non costruire JSON attraverso stringhe temporanee riduce spesso le allocazioni in modo immediato.

Perché la scansione delle query del database può far esplodere la memoria durante i picchi?

Scansionare righe in destinazioni flessibili come interface{} o map[string]any, convertire []byte in string per molti campi o recuperare troppe righe/colonne può allocare molto per richiesta. Selezionare solo le colonne necessarie, usare paginazione e scansionare direttamente in struct concrete sono spesso rimedi efficaci.

Quali pattern di middleware causano comunemente allocazioni “morte da mille tagli”?

La middleware gira su ogni richiesta, quindi piccole allocazioni si sommano. Creare nuove stringhe per i log, generare etichette metriche ad alta cardinalità, creare reader/writer gzip a ogni richiesta o inserire nuovi oggetti nel context sono esempi tipici di churn costante nelle profile.

Posso usare questo workflow pprof sui backend Go generati da AppMaster?

Sì. Lo stesso approccio guidato da profili si applica a qualsiasi codice Go, generato o scritto a mano. Se esporti il backend generato, puoi eseguire pprof, individuare le call path che allocano e poi adattare modelli, handler e logica per ridurre le allocazioni per richiesta prima del prossimo picco.

Facile da avviare
Creare qualcosa di straordinario

Sperimenta con AppMaster con un piano gratuito.
Quando sarai pronto potrai scegliere l'abbonamento appropriato.

Iniziare