Monitoraggio delle Performance di MongoDB: Comandi e Metriche Chiave Spiegati

Impara a monitorare proattivamente le performance del tuo MongoDB utilizzando comandi essenziali della shell. Questa guida dettaglia come tracciare lo stato delle connessioni tramite `db.currentOp()` e `db.serverStatus()`, analizzare le query lente usando i comandi di profiling (`db.setProfilingLevel`), e interpretare metriche cruciali relative all'utilizzo delle risorse e alla salute degli indici per un'ottimizzazione ottimale del database.

Monitoraggio delle Performance di MongoDB: Comandi e Metriche Chiave Spiegati

Una gestione efficace del database si basa su un monitoraggio robusto. Per MongoDB, un database NoSQL documentale leader, comprendere le metriche delle performance è fondamentale per mantenere alta disponibilità e reattività. Query lente, consumo eccessivo di risorse o picchi imprevisti di connessioni possono compromettere gravemente le performance dell'applicazione.

Quando MongoDB rallenta, la prima domanda utile non è "il database è difettoso?" ma "cosa sta facendo il server in questo momento, e questo è diverso dal normale?" I comandi seguenti sono quelli che uso per una prima analisi prima di modificare gli indici, ridimensionare l'hardware o incolpare l'applicazione.

Comandi Essenziali per il Monitoraggio nella Shell MongoDB (mongosh)

L'interfaccia principale per eseguire questi comandi è la Shell MongoDB (mongosh), o la shell legacy mongo. Tutti i comandi mostrati qui vengono eseguiti all'interno di questo ambiente shell.

1. Comprendere le Connessioni Correnti: db.currentOp() e db.serverStatus()

Monitorare le connessioni attive è fondamentale per prevenire l'esaurimento delle connessioni e identificare operazioni di lunga durata che potrebbero bloccare le risorse.

db.currentOp()

Questo comando restituisce informazioni sulle operazioni attualmente in esecuzione sul database. È indispensabile per identificare query lente o bloccanti in tempo reale.

Esempio di Utilizzo:

Per vedere tutte le operazioni attualmente in esecuzione:

db.currentOp()

Per cercare specificamente operazioni che durano più di una certa soglia (ad esempio, operazioni in esecuzione da più di 5 secondi):

db.currentOp({"secs_running": {$gt: 5}})

L'output include dettagli come op, ns (namespace), query e secs_running.

db.serverStatus()

Sebbene questo comando fornisca informazioni complete sullo stato, la sua sezione connections è cruciale per monitorare il pooling delle connessioni e i limiti.

Metriche Chiave all'interno di serverStatus (Sezione Connessioni):

  • current: Il numero di connessioni attive al server.
  • available: Il numero di connessioni disponibili che possono essere stabilite (in base al massimo configurato).
db.serverStatus().connections

2. Analizzare le Performance delle Query: db.getProfilingStatus() e db.setProfilingLevel()

MongoDB fornisce strumenti di profiling integrati che registrano i dettagli di esecuzione delle operazioni del database, rendendo possibile identificare query che consumano molte risorse.

Livelli di Profiling

I livelli di profiling determinano quali operazioni vengono registrate:

  • 0 (Spento): Nessuna operazione viene profilata.
  • 1 (Operazioni Lente): Vengono profilate solo le operazioni più lente della soglia configurata (slowms).
  • 2 (Tutte le Operazioni): Tutte le operazioni vengono profilate, il che genera un carico di scrittura significativo e dovrebbe essere usato solo brevemente per risolvere problemi mirati.

Verifica dello Stato

Per vedere il livello di profiling corrente:

db.getProfilingStatus()

Impostazione del Livello (Esempio)

Per abilitare il profiling solo per le operazioni lente (operazioni che superano i 100 millisecondi):

// Imposta slowms a 100 millisecondi (il default è solitamente 100)
db.setProfilingLevel(1, { slowms: 100 })

Consiglio: Riporta sempre il profiling al livello 0 dopo aver raccolto le informazioni necessarie per prevenire il degrado delle performance causato da una registrazione eccessiva.

Visualizzare le Query Lente Profilate

Le operazioni profilate vengono memorizzate nella raccolta system.profile all'interno del database specifico monitorato. Per visualizzare le 10 query più lente nell'ultima ora:

db.system.profile.find().sort({millis: -1}).limit(10).pretty()

3. Metriche di Utilizzo delle Risorse

Comprendere come MongoDB utilizza CPU, memoria e risorse I/O è essenziale per le decisioni di scalabilità.

Utilizzo di Memoria e Archiviazione: db.serverStatus()

Le sezioni globalLock e storageEngine all'interno di serverStatus forniscono approfondimenti sulla gestione delle risorse.

Indicatori di Memoria:

  • resident: Quantità di memoria fisica utilizzata dal processo.
  • virtual: Memoria virtuale totale allocata dal processo.
db.serverStatus().globalLock

Monitoraggio della Contesa sui Lock

MongoDB utilizza meccanismi di blocco interni. Monitorare l'acquisizione dei lock e le attese aiuta a identificare i colli di bottiglia di concorrenza.

Metriche Chiave in globalLock:

  • currentQueue.readers: Numero di lettori in attesa di un lock.
  • currentQueue.writers: Numero di scrittori in attesa di un lock.
  • totalTime: Tempo totale speso in attesa di lock attraverso tutte le operazioni.

Valori elevati in currentQueue spesso indicano che gli indici mancano o che le operazioni di scrittura sono eccessivamente lunghe, causando l'accodamento di lettori/scrittori.

4. Utilizzo e Salute degli Indici: db.collection.stats()

Indici poco utilizzati o mancanti sono la causa più comune di degrado delle performance. Il comando stats() aiuta ad analizzare l'efficienza degli indici.

Quando eseguito su una raccolta specifica (ad esempio, users):

db.users.stats()

Metriche Chiave da Controllare:

  • totalIndexSize: Lo spazio totale su disco consumato da tutti gli indici su quella raccolta.
  • indexSizes: Una ripartizione dell'utilizzo dello spazio per indice.
  • Se un indice è presente ma mai utilizzato per le letture, è un overhead che dovrebbe essere considerato per la rimozione.

5. I/O del Disco e Throughput: db.serverStatus() (Rete e Operazioni)

Monitorare l'attività di rete e il tasso di operazioni fornisce una visione del throughput del database.

Tasso di Operazioni (da opcounters):

opcounters tiene traccia del numero totale di operazioni eseguite dall'ultimo riavvio del server, categorizzate per tipo:

  • insert, query, update, delete, getmore, command.

Tracciando le modifiche a questi contatori nel tempo (ad esempio, confrontando due chiamate consecutive a serverStatus), puoi calcolare il throughput operativo (operazioni al secondo).

Esempio di Confronto:

  1. Esegui db.serverStatus().opcounters al tempo T1.
  2. Esegui db.serverStatus().opcounters al tempo T2.
  3. Sottrai i valori T1 dai valori T2 per ottenere il totale delle operazioni eseguite in quell'intervallo.

Best Practice per il Monitoraggio Proattivo

  • L'Automazione è Fondamentale: Affidarsi esclusivamente a comandi manuali della shell è inefficiente. Integra il monitoraggio utilizzando strumenti come MongoDB Cloud Manager/Ops Manager o soluzioni di monitoraggio di terze parti che interrogano automaticamente questi endpoint.
  • Stabilisci delle Baseline: Esegui i comandi quando il sistema è in salute per stabilire una baseline delle performance. Qualsiasi deviazione da questa baseline richiede un'indagine immediata.
  • Concentrati sulla Latenza: Sebbene i conteggi delle operazioni siano utili, dai priorità alle metriche di latenza (come il tempo riportato dai log di profiling) rispetto al throughput grezzo quando diagnostichi i problemi di esperienza utente finale.
  • Controlla le Connessioni Frequentemente: Nelle applicazioni ad alto traffico, i limiti di connessione vengono spesso raggiunti per primi. Monitora db.serverStatus().connections.current rispetto al massimo configurato.

Una Checklist Pratica per la Prima Analisi

Quando qualcuno dice "MongoDB è lento", evita di saltare direttamente alle modifiche degli indici. Inizia con una breve checklist e annota ciò che vedi.

Controlla se il server è sovraccarico di operazioni attive:

db.currentOp({
  active: true,
  secs_running: { $gt: 2 }
});

Alcune operazioni di lunga durata possono essere normali per i lavori di analisi. Un grande accumulo di scritture, scansioni di raccolte o operazioni bloccate è diverso. Cerca il namespace in ns, il tipo di operazione in op e la forma della query. Se molte operazioni sono in attesa dietro un singolo aggiornamento o una creazione di indice, la soluzione non è la stessa di un indice mancante su una query di lettura.

Poi controlla le connessioni:

db.serverStatus().connections;

current in rapido aumento può significare che un pool di connessioni dell'applicazione è stato configurato male, una distribuzione ha creato troppi worker, o i client stanno andando in timeout e si riconnettono. available vicino allo zero è un segnale urgente perché i nuovi client potrebbero non riuscire a connettersi. La risposta giusta potrebbe essere la regolazione del pool nell'app, non l'aumento del limite del server.

Successivamente, controlla i contatori delle operazioni due volte, a breve distanza:

const a = db.serverStatus().opcounters;
sleep(5000);
const b = db.serverStatus().opcounters;
printjson({
  insertPer5s: b.insert - a.insert,
  queryPer5s: b.query - a.query,
  updatePer5s: b.update - a.update,
  deletePer5s: b.delete - a.delete,
  commandPer5s: b.command - a.command
});

I contatori dall'avvio sono utili per il contesto a lungo termine, ma le differenze in un intervallo noto ti dicono cosa sta succedendo ora. Se il traffico di comandi è alto ma le query sono basse, potresti avere a che fare con controlli dei metadati, rumore di monitoraggio o comportamento del driver piuttosto che letture normali.

Usare explain() Prima di Incolpare l'Hardware

La raccolta di profiling può dirti quali operazioni sono lente. explain() ti aiuta a capire perché una query è lenta prima di aggiungere CPU o memoria.

db.users.find({ email: "[email protected]" }).explain("executionStats");

Nell'output, confronta totalDocsExamined con nReturned. Se MongoDB esamina un numero enorme di documenti per restituire un singolo utente, la query probabilmente necessita di un indice migliore o di un filtro diverso. Se totalKeysExamined è alto, un indice esiste ma potrebbe non essere abbastanza selettivo per il modello di query.

Per una query composta, l'ordine degli indici è importante:

db.orders.find({
  accountId: "acct_123",
  status: "open",
  createdAt: { $gte: ISODate("2025-11-01T00:00:00Z") }
}).sort({ createdAt: -1 });

Un indice utile potrebbe essere:

db.orders.createIndex({ accountId: 1, status: 1, createdAt: -1 });

Questa non è una regola universale. Il miglior indice dipende dalla cardinalità, dall'ordinamento e dall'insieme completo di query che colpiscono la raccolta. Il punto è far sì che il database ti mostri il piano di esecuzione invece di indovinare.

Leggere i Dati di Profiling Senza Reagire in Modo Eccessivo

Il livello di profiling 2 registra ogni operazione e può aggiungere overhead su sistemi occupati. Usalo solo per una finestra breve e mirata. Il livello 1 con una soglia slowms ragionevole è più sicuro per trovare operazioni lente.

db.setProfilingLevel(1, { slowms: 200 });

Dopo aver raccolto i dati, ispeziona le voci più lente:

db.system.profile.find(
  {},
  {
    ns: 1,
    op: 1,
    millis: 1,
    command: 1,
    keysExamined: 1,
    docsExamined: 1,
    nreturned: 1
  }
).sort({ millis: -1 }).limit(20).pretty();

Una singola query lenta non significa sempre un incidente di produzione. Un report programmato, una cache fredda dopo un riavvio o un'attività di manutenzione rara possono apparire in cima. I modelli contano più di un singolo campione. Se la stessa forma di query appare ripetutamente ed esamina molti più documenti di quanti ne restituisca, hai un vero candidato per l'ottimizzazione.

Monitorare i Replica Set e la Pressione sull'Archiviazione

Per i replica set, le performance non riguardano solo il primario. Un secondario che resta indietro può influenzare la fiducia nel failover e i carichi di lavoro di lettura se i client utilizzano letture secondarie.

rs.status();

Cerca membri che non sono in salute, cambiamenti di stato imprevisti o lag di replica che non si recupera. Il lag accettabile esatto dipende dall'applicazione. Un carico di lavoro simile a una coda può tollerare un piccolo ritardo. Una dashboard che promette letture quasi in tempo reale potrebbe no.

La pressione sull'archiviazione necessita dello stesso contesto. db.serverStatus() può mostrare metriche del motore di archiviazione e di WiredTiger, ma gli strumenti a livello di disco contano ancora. Se MongoDB è in attesa di dischi lenti, i comandi della shell all'interno del database mostreranno i sintomi piuttosto che la causa principale. Correla con metriche dell'host come latenza del disco, utilizzo del filesystem, CPU steal e pressione della memoria.

Trasformare i Controlli Manuali in Avvisi

I comandi manuali sono migliori durante le indagini. Per le operazioni normali, converti i segnali utili in controlli automatizzati: utilizzo delle connessioni, salute della replica, tasso di query lente, utilizzo del disco, page fault o pressione della cache dove disponibile, e latenza delle operazioni. Avvisa su comportamenti negativi sostenuti, non su ogni picco di un minuto.

I buoni avvisi includono il contesto. "Query lente MongoDB alte" è meno utile di un avviso che include il database, la raccolta, la forma della query, il tasso corrente e un collegamento a campioni di profiling recenti o pannelli della dashboard. L'obiettivo è abbreviare i primi dieci minuti dell'incidente.

Cosa Non Fare Durante un Rallentamento

Evita di apportare più modifiche contemporaneamente. Aggiungere un indice, aumentare i limiti di connessione, riavviare l'applicazione e modificare le dimensioni del pool nello stesso incidente potrebbe risolvere il sintomo, ma ti lascia senza idea di quale azione abbia aiutato. Apporta una modifica, osserva la metrica che dovrebbe migliorare e prendi appunti.

Fai attenzione con killOp. Può essere utile quando un'operazione è chiaramente dannosa, ma uccidere operazioni casuali di lunga durata può peggiorare il comportamento dell'applicazione. Se l'operazione appartiene a una migrazione, un backup, una creazione di indice o un lavoro di reporting, identifica il proprietario prima di fermarla, a meno che il database non sia già in seri guai.

Non trattare serverStatus() come un singolo punteggio di salute magico. È una raccolta di contatori e istantanee. Un valore alto può essere normale su un sistema grande e occupato, e un valore basso può essere negativo su un sistema piccolo e sensibile alla latenza. La domanda utile è se il valore è cambiato in un modo che corrisponde al problema visibile all'utente.

Separa anche i sintomi del database dai sintomi della distribuzione. Una nuova release che modifica la forma delle query, apre pool di connessioni più grandi o avvia una migrazione in background può far sembrare MongoDB la causa principale. Confronta la tempistica delle operazioni lente con distribuzioni, pianificazioni di job, backup e cambiamenti di traffico prima di apportare una correzione solo al database.

Il monitoraggio di MongoDB funziona meglio quando confronti il comportamento corrente con una baseline nota. db.currentOp(), db.serverStatus(), il profiling, explain() e i controlli del replica set ti danno abbastanza prove per decidere se il problema è una query, un indice, il comportamento della connessione del client, la replica o l'host sottostante al database.