MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Come aumentare le prestazioni dell'operazione di aggiornamento in Mongo?

Quando si esegue l'aggiornamento nel modo in cui si è, è necessario recuperare il contenuto del documento per ispezionarlo e apportare tali modifiche. MongoDB non ha operazioni atomiche che agiscano sui valori esistenti nel modo in cui desideri, quindi è ovviamente necessaria l'iterazione.

Non c'è alcuna reale differenza nella parte "query" di come stai abbinando sull'espressione regolare tra le tue due versioni dell'istruzione. In ogni caso, il contenuto viene convertito in BSON prima di essere comunque inviato al server, quindi se utilizzi un generatore di espressioni standard o un documento BSON diretto ha poche conseguenze.

Ma passiamo ai miglioramenti delle prestazioni che possono essere apportati.

Utilizza le operazioni in blocco per aggiornare

Come affermato, le operazioni in blocco sono il modo in cui dovresti aggiornare su tale iterazione dell'elenco e "dovresti" anche utilizzare un cursore anziché convertire tutti i risultati in un elenco, poiché risparmierà memoria.

Evitando tutte le dichiarazioni di tipo specifico e rappresentando semplicemente come BsonDocument (che probabilmente ti farà risparmiare sul marshalling, ma non necessario), quindi il processo di esempio di base sarebbe:

var pattern = @"(?si)<([^\s<]*workUnit[^\s<]*)>.*?</\1>";
var filter = Builders<JobInfoRecord>.Filter.Regex(x => x.SerializedBackgroundJobInfo,
                                              new BsonRegularExpression(pattern, "i"));


var ops = new List<WriteModel<BsonDocument>>();
var writeOptions = new BulkWriteOptions() { IsOrdered = false };

using ( var cursor = await records.FindAsync<BsonDocument>(filter))
{
    while ( await cursor.MoveNextAsync())
    {
        foreach( var doc in cursor.Current )
        {
            // Replace inspected value
            var updatedJobInfo = Regex.Replace(doc.SerializedBackgroundJobInfo, pattern, "<$1></$1>");

            // Add WriteModel to list
            ops.Add(
                new UpdateOneModel<BsonDocument>(
                    Builders<BsonDocument>.Filter.Eq("JobTypeValue", doc.JobTypeValue),
                    Builders<BsonDocument>.Update.Set("SerializedBackgroundJobInfo", updatedJobInfo)
                )
            );

            // Execute once in every 1000 and clear list
            if (ops.Count == 1000)
            {
                BulkWriteResult<BsonDocument> result = await records.BulkWriteAsync(ops,writeOptions);
                ops = new List<WriteModel<BsonDocument>>();
            }
        }
    }

    // Clear any remaining
    if (ops.Count > 0 )
    {
        BulkWriteResult<BsonDocument> result = await records.BulkWriteAsync(ops,writeOptions);
    }

}

Quindi, anziché fare una richiesta al database per ogni singolo documento recuperato dalla query, crei un List di WriteModel operazioni invece.

Una volta che questo elenco è cresciuto fino a un valore ragionevole ( 1000 in questo esempio ), esegui il commit dell'operazione di scrittura sul server in un'unica richiesta e risposta per tutte le operazioni in batch. Qui utilizziamo BulkWriteAsync .

Puoi creare i lotti in una dimensione maggiore di 1000, se lo desideri, ma in genere è un numero ragionevole da gestire. L'unico vero limite è il limite BSON di 16 MB, che poiché tutte le richieste sono ancora in realtà documenti BSON, questo vale ancora. Ad ogni modo ci vogliono molte richieste per avvicinarsi a 16 MB, ma c'è anche una corrispondenza di impedenza da considerare nel modo in cui la richiesta verrà elaborata quando raggiunge effettivamente il server, come documentato:

"Ogni gruppo di operazioni può avere al massimo 1000 operazioni. Se un gruppo supera questo limite, MongoDB dividerà il gruppo in gruppi più piccoli di 1000 o meno. Ad esempio, se l'elenco delle operazioni in blocco è composto da 2000 operazioni di inserimento, MongoDB crea 2 gruppi, ciascuno con 1000 operazioni."

Pertanto, mantenendo la dimensione della richiesta allo stesso livello di come la elaborerà il server, ottieni anche il vantaggio del yield dove "batch multipli" possono infatti agire in connessioni parallele al server, piuttosto che lasciare che sia il server a fare la divisione e l'accodamento.

Il risultato restituito è BulkWriteResult che conterrà informazioni sul numero di "corrispondenze" e "modifiche" ecc. dal batch di operazioni inviate.

Naturalmente poiché le operazioni sono in "batch", ha senso quindi controllare alla fine dell'iterazione del ciclo per vedere se esistono altre operazioni "batch" nell'elenco, e quindi ovviamente inviare allo stesso modo.

Notando anche il IsOrdered = false come BulkWriteOptions significa che il batch di operazioni non viene effettivamente eseguito in ordine seriale, il che significa che il server può effettivamente eseguire le operazioni in "parallelo". Ciò può apportare miglioramenti di velocità "enormi" laddove non è richiesto l'ordine di impegno. L'impostazione predefinita è inviare "ordinato" e in serie.

Questo non è necessario per impostare questa opzione, ma se il tuo ordine non è importante (cosa che non dovrebbe essere in questo caso poiché nessun'altra richiesta di operazione qui dipende dalla precedente modifica di un documento) allora vale la pena ottenere il miglioramento.

Di cosa si tratta è "ridurre" il numero di richieste effettive fatte al server. L'invio di aggiornamenti e l'attesa di una risposta richiede tempo e nelle grandi operazioni è un esercizio molto costoso. Questo è ciò che devono affrontare le operazioni in blocco, applicando diverse operazioni all'interno di un'unica richiesta.

Ridurre tale sovraccarico è un "enorme" guadagno in termini di prestazioni. Ecco perché lo usi.