MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Como aumentar o desempenho da operação de atualização no Mongo?


Ao atualizar da maneira que você está, você precisa recuperar o conteúdo do documento para inspecioná-lo e fazer tais modificações. O MongoDB não possui operações atômicas que atuem nos valores existentes da maneira que você deseja, portanto, a iteração é obviamente necessária.

Não há diferença real na parte "consulta" de como você está correspondendo na expressão regular entre suas duas versões da instrução. Não importa o que aconteça, o conteúdo é convertido em BSON antes de ser enviado para o servidor de qualquer maneira, portanto, se você usar um construtor de expressão padrão ou um documento BSON direto, terá pouca importância.

Mas vamos às melhorias de desempenho que podem ser feitas.

Use operações em massa para atualizar


Como afirmado, as operações em massa são a maneira como você deve atualizar essa iteração de lista e também "deveria" usar um cursor em vez de converter todos os resultados em uma lista, pois isso economizará na memória.

Evitando todas as declarações de tipo específico e apenas representando como BsonDocument (o que provavelmente economizará em empacotamento, mas não é necessário), então o processo de exemplo básico seria:
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);
    }

}

Então, em vez de fazer uma solicitação ao banco de dados para cada documento recuperado da consulta, você cria uma List de WriteModel operações em vez disso.

Depois que essa lista crescer para um valor razoável (1000 neste exemplo), você confirma a operação de gravação no servidor em uma única solicitação e resposta para todas as operações em lote. Aqui usamos BulkWriteAsync .

Você pode criar os lotes em um tamanho maior que 1000, se quiser, mas geralmente é um número razoável de se lidar. O único limite real real é o limite BSON de 16 MB, que, como todas as solicitações ainda são documentos BSON, isso ainda se aplica. De qualquer forma, são necessários muitos pedidos para se aproximar de 16 MB, mas também há uma correspondência de impedância a ser considerada em como a solicitação será processada quando chegar ao servidor, conforme documentado:

"Cada grupo de operações pode ter no máximo 1.000 operações. Se um grupo exceder esse limite, o MongoDB dividirá o grupo em grupos menores de 1.000 ou menos. Por exemplo, se a lista de operações em massa consistir em 2.000 operações de inserção, o MongoDB cria 2 grupos, cada um com 1.000 operações."

Portanto, mantendo o tamanho da solicitação no mesmo nível de como o servidor o processará, você também obtém o benefício do yield onde "vários lotes" podem estar de fato agindo em conexões paralelas com o servidor, em vez de deixar o servidor fazer a divisão e o enfileiramento.

O resultado retornado é de BulkWriteResult que conterá informações sobre o número de "correspondências" e "modificações" etc. do lote de operações enviado.

Naturalmente, como as operações estão em "lotes", faz sentido verificar no final da iteração do loop para ver se existem mais operações "em lote" na lista e, é claro, enviar da mesma maneira.

Observando também o IsOrdered = false como BulkWriteOptions significa que o lote de operações não é realmente executado em ordem serial, o que significa que o servidor pode de fato executar as tarefas em "paralelo". Isso pode fazer melhorias de velocidade "enormes" onde a ordem de compromisso não é necessária. O padrão é enviar "encomendado" e em série.

Isso não é necessário para definir esta opção, mas se seu pedido não for importante (o que não deve ser neste caso, pois nenhuma outra solicitação de operação aqui depende da modificação anterior de um documento), então a melhoria que você obtém vale a pena.

O que se trata é "reduzir" o número de solicitações reais feitas ao servidor. Enviar atualizações e aguardar uma resposta leva tempo e, em grandes operações, é um exercício muito caro. É com isso que as Operações em Massa devem lidar, aplicando várias operações em uma solicitação.

Reduzir essa sobrecarga é um ganho de desempenho "enorme". É por isso que você usa isso.