Prestazioni eccellenti del database sono importanti quando si sviluppano applicazioni con MongoDB. A volte l'intero processo di pubblicazione dei dati può subire un peggioramento a causa di una serie di motivi, alcuni dei quali includono:
- Modelli di progettazione dello schema inappropriati
- Uso improprio o non utilizzato delle strategie di indicizzazione
- Hardware inadeguato
- Ritardo di replica
- Tecniche di interrogazione con prestazioni scadenti
Alcune di queste battute d'arresto potrebbero costringerti ad aumentare le risorse hardware mentre altre no. Ad esempio, strutture di query scadenti potrebbero richiedere molto tempo per l'elaborazione della query, causando un ritardo di replica e forse anche una perdita di dati. In questo caso, si potrebbe pensare che forse la memoria di archiviazione non sia sufficiente e che probabilmente debba essere aumentata. Questo articolo illustra le procedure più appropriate che puoi utilizzare per migliorare le prestazioni del tuo database MongoDB.
Progettazione di schemi
Fondamentalmente le due relazioni di schema più comunemente utilizzate sono...
- Uno a pochi
- Uno a molti
Sebbene la progettazione dello schema più efficiente sia la relazione uno-a-molti, ognuno ha i suoi pregi e limiti.
Uno a pochi
In questo caso, per un determinato campo, sono presenti documenti incorporati ma non sono indicizzati con l'identità dell'oggetto.
Ecco un semplice esempio:
{
userName: "Brian Henry",
Email : "[email protected]",
grades: [
{subject: ‘Mathematics’, grade: ‘A’},
{subject: English, grade: ‘B’},
]
}
Un vantaggio dell'utilizzo di questa relazione è che è possibile ottenere i documenti incorporati con una sola query. Tuttavia, dal punto di vista della query, non è possibile accedere a un singolo documento incorporato. Quindi, se non hai intenzione di fare riferimento ai documenti incorporati separatamente, sarà ottimale utilizzare questo schema di progettazione.
Uno a molti
Per questa relazione, i dati in un database sono correlati ai dati in un database diverso. Ad esempio, puoi avere un database per gli utenti e un altro per i post. Quindi, se un utente pubblica un post, questo viene registrato con l'ID utente.
Schema utenti
{
Full_name: “John Doh”,
User_id: 1518787459607.0
}
Schema dei messaggi
{
"_id" : ObjectId("5aa136f0789cf124388c1955"),
"postTime" : "16:13",
"postDate" : "8/3/2018",
"postOwnerNames" : "John Doh",
"postOwner" : 1518787459607.0,
"postId" : "1520514800139"
}
Il vantaggio di questa progettazione dello schema è che i documenti sono considerati autonomi (possono essere selezionati separatamente). Un altro vantaggio è che questo design consente agli utenti di ID diversi di condividere informazioni dallo schema dei post (da cui il nome One-to-Many) e talvolta può essere lo schema "N-to-N", fondamentalmente senza utilizzare il join di tabella. Il limite con questa progettazione dello schema è che devi eseguire almeno due query per recuperare o selezionare i dati nella seconda raccolta.
Il modo in cui modellare i dati dipenderà quindi dal modello di accesso dell'applicazione. Oltre a questo è necessario considerare la progettazione dello schema di cui abbiamo discusso sopra.
Tecniche di ottimizzazione per la progettazione di schemi
-
Utilizza il più possibile l'incorporamento dei documenti in quanto riduce il numero di query che devi eseguire per un determinato set di dati.
-
Non utilizzare la denormalizzazione per i documenti che vengono aggiornati frequentemente. Se un campo verrà aggiornato frequentemente, ci sarà il compito di trovare tutte le istanze che devono essere aggiornate. Ciò si tradurrà in un'elaborazione lenta delle query, quindi sopraffacendo anche i meriti associati alla denormalizzazione.
-
Se è necessario recuperare un documento separatamente, non è necessario utilizzare l'incorporamento poiché query complesse come il pipelining aggregato richiedono più tempo per essere eseguite.
-
Se la matrice di documenti da incorporare è abbastanza grande, non incorporarli. La crescita dell'array dovrebbe avere almeno un limite.
Corretta indicizzazione
Questa è la parte più critica dell'ottimizzazione delle prestazioni e richiede una comprensione completa delle query dell'applicazione, del rapporto tra letture e scritture e della quantità di memoria libera disponibile nel sistema. Se utilizzi un indice, la query eseguirà la scansione dell'indice e non della raccolta.
Un ottimo indice è quello che coinvolge tutti i campi scansionati da una query. Questo è indicato come un indice composto.
Per creare un unico indice per un campo puoi utilizzare questo codice:
db.collection.createIndex({“fields”: 1})
Per un indice composto, per creare l'indicizzazione:
db.collection.createIndex({“filed1”: 1, “field2”: 1})
Oltre a query più rapide mediante l'uso dell'indicizzazione, c'è un ulteriore vantaggio di altre operazioni come l'ordinamento, i campioni e il limite. Ad esempio, se progetto il mio schema come {f:1, m:1} posso eseguire un'operazione aggiuntiva oltre a trova come
db.collection.find( {f: 1} ).sort( {m: 1} )
La lettura dei dati dalla RAM è più efficiente rispetto alla lettura degli stessi dati dal disco. Per questo motivo, si consiglia sempre di assicurarsi che il proprio indice rientri interamente nella RAM. Per ottenere l'attuale indexSize della tua raccolta, esegui il comando :
db.collection.totalIndexSize()
Otterrai un valore come 36864 byte. Anche questo valore non dovrebbe occupare una grande percentuale della dimensione complessiva della RAM, poiché è necessario soddisfare le esigenze dell'intero working set del server.
Una query efficiente dovrebbe anche migliorare la selettività. La selettività può essere definita come la capacità di una query di restringere il risultato utilizzando l'indice. Per essere più secanti, le tue query dovrebbero limitare il numero di documenti possibili con il campo indicizzato. La selettività è principalmente associata a un indice composto che include un campo a bassa selettività e un altro campo. Ad esempio se hai questi dati:
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 7, b: "cd", c: 58 }
{ _id: ObjectId(), a: 8, b: "kt", c: 33 }
La query {a:7, b:“cd”} analizzerà 2 documenti per restituire 1 documento corrispondente. Tuttavia, se i dati per il valore a sono distribuiti uniformemente, ad esempio
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 8, b: "cd", c: 58 }
{ _id: ObjectId(), a: 9, b: "kt", c: 33 }
La query {a:7, b:“cd”} analizzerà 1 documento e restituirà questo documento. Quindi questo richiederà un tempo più breve rispetto alla prima struttura di dati.
ClusterControlSingle Console per l'intera infrastruttura di databaseScopri cos'altro c'è di nuovo in ClusterControlInstalla ClusterControl GRATISFornitura delle risorse
Memoria di archiviazione, RAM e altri parametri operativi inadeguati possono degradare drasticamente le prestazioni di un MongoDB. Ad esempio, se il numero di connessioni utente è molto elevato, ostacolerà la capacità dell'applicazione server di gestire le richieste in modo tempestivo. Come discusso in Cose chiave da monitorare in MongoDB, puoi ottenere una panoramica di quali risorse limitate hai e come puoi ridimensionarle per adattarle alle tue specifiche. Per un gran numero di richieste di applicazioni simultanee, il sistema di database sarà sopraffatto per stare al passo con la domanda.
Ritardo di replica
A volte potresti notare alcuni dati mancanti dal tuo database o quando elimini qualcosa, appare di nuovo. Per quanto tu possa avere uno schema ben progettato, un'indicizzazione appropriata e risorse sufficienti, all'inizio la tua applicazione funzionerà senza intoppi, ma poi a un certo punto noterai questi ultimi problemi menzionati. MongoDB si basa sul concetto di replica in cui i dati vengono copiati in modo ridondante per soddisfare alcuni criteri di progettazione. Un presupposto con questo è che il processo è istantaneo. Tuttavia, potrebbe verificarsi un certo ritardo forse a causa di un errore di rete o di errori non gestiti. In poche parole, ci sarà un grande divario tra il tempo con cui un'operazione viene elaborata sul nodo primario e il tempo in cui verrà applicata nel nodo secondario.
Batti d'arresto con ritardi di replica
-
Dati incoerenti. Ciò è particolarmente associato alle operazioni di lettura distribuite tra i secondari.
-
Se il ritardo è sufficientemente ampio, molti dati non replicati potrebbero trovarsi sul nodo primario e dovranno essere riconciliati nel nodo secondario. Ad un certo punto, questo potrebbe essere impossibile soprattutto quando il nodo primario non può essere ripristinato.
-
Il mancato ripristino del nodo primario può costringere a eseguire un nodo con dati non aggiornati e di conseguenza può far cadere l'intero database per far ripristinare il primario.
Cause del guasto del nodo secondario
-
Superamento della potenza primaria rispetto a quella secondaria per quanto riguarda le specifiche di CPU, IOPS del disco e I/O di rete.
-
Operazioni di scrittura complesse. Ad esempio un comando come
db.collection.update( { a: 7} , {$set: {m: 4} }, {multi: true} )
Il nodo primario registrerà questa operazione nell'oplog abbastanza velocemente. Tuttavia, per il nodo secondario, deve recuperare quelle operazioni, leggere nella RAM qualsiasi indice e pagina di dati per soddisfare alcune specifiche di criteri come l'id. Dal momento che deve farlo abbastanza velocemente per mantenere la velocità con il nodo primario esegue l'operazione, se il numero di operazioni è abbastanza grande, ci sarà un ritardo previsto.
-
Blocco del secondario durante l'esecuzione di un backup. In questo caso potremmo dimenticare di disabilitare il primario, quindi continueremo con le sue operazioni normalmente. Nel momento in cui verrà rilasciato il blocco, il ritardo di replica avrà un grande divario soprattutto quando si ha a che fare con un'enorme quantità di backup dei dati.
-
Costruzione dell'indice. Se un indice si accumula nel nodo secondario, tutte le altre operazioni ad esso associate vengono bloccate. Se l'indice è di lunga durata, si verificherà il problema del ritardo di replica.
-
Secondario non connesso. A volte il nodo secondario potrebbe non funzionare a causa di disconnessioni di rete e ciò si traduce in un ritardo di replica quando viene riconnesso.
Come ridurre al minimo il ritardo di replica
-
Usa indici univoci oltre alla tua raccolta con il campo _id. Questo per evitare che il processo di replica fallisca completamente.
-
Prendi in considerazione altri tipi di backup come snapshot point-in-time e filesystem che non richiedono necessariamente il blocco.
-
Evita di creare indici di grandi dimensioni poiché causano operazioni di blocco in background.
-
Rendi il secondario abbastanza potente. Se l'operazione di scrittura è leggera, l'utilizzo di secondari sottodimensionati sarà economico. Ma, per carichi di scrittura pesanti, il nodo secondario potrebbe rimanere indietro rispetto al primario. Per essere più seccante, il secondario dovrebbe avere una larghezza di banda sufficiente per aiutare a leggere gli oplog abbastanza velocemente da mantenere la sua velocità con il nodo primario.
Tecniche di query efficienti
Oltre alla creazione di query indicizzate e all'utilizzo della selettività delle query come discusso sopra, ci sono altri concetti che puoi utilizzare per velocizzare e rendere efficaci le tue query.
Ottimizzazione delle tue query
-
Utilizzo di una query coperta. Una query coperta è sempre completamente soddisfatta da un indice, quindi non è necessario esaminare alcun documento. La query coperta quindi dovrebbe avere tutti i campi come parte dell'indice e di conseguenza il risultato dovrebbe contenere tutti questi campi.
Consideriamo questo esempio:
{_id: 1, product: { price: 50 }
Se creiamo un indice per questa raccolta come
{“product.price”: 1}
Considerando un'operazione di ricerca, questo indice coprirà questa query;
db.collection.find( {“product.price”: 50}, {“product.price”: 1, _id: 0} )
e restituisci solo il campo product.price e il valore.
-
Per i documenti incorporati, utilizzare la notazione del punto (.). La notazione del punto aiuta ad accedere agli elementi di una matrice e ai campi del documento incorporato.
Accesso a un array:
{ prices: [12, 40, 100, 50, 40] }
Per specificare ad esempio il quarto elemento, puoi scrivere questo comando:
“prices.3”
Accesso a un array di oggetti:
{ vehicles: [{name: toyota, quantity: 50}, {name: bmw, quantity: 100}, {name: subaru, quantity: 300} }
Per specificare il campo del nome nell'array veicoli puoi usare questo comando
“vehicles.name”
-
Controlla se una query è coperta. Per fare ciò usa db.collection.explain(). Questa funzione fornirà informazioni sull'esecuzione di altre operazioni, ad es. db.collection.explain().aggregate(). Per saperne di più sulla funzione di spiegazione puoi dare un'occhiata a spiegare().
In generale, la tecnica suprema per quanto riguarda l'interrogazione è l'utilizzo degli indici. L'interrogazione solo di un indice è molto più veloce dell'interrogazione di documenti al di fuori dell'indice. Possono adattarsi alla memoria, quindi disponibili nella RAM anziché nel disco. In questo modo è facile e veloce recuperarli dalla memoria.