MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Jak zwiększyć wydajność operacji aktualizacji w Mongo?

Podczas aktualizacji w taki sposób, w jaki jesteś, musisz pobrać treść dokumentu, aby ją sprawdzić i wprowadzić takie modyfikacje. MongoDB nie ma operacji atomowych, które działają na istniejących wartościach w sposób, w jaki chcesz, więc iteracja jest oczywiście wymagana.

Nie ma żadnej rzeczywistej różnicy w części „zapytanie”, w której dopasowuje się wyrażenie regularne między dwiema wersjami instrukcji. Bez względu na wszystko, treść i tak jest konwertowana na BSON przed wysłaniem na serwer, więc jeśli używasz standardowego konstruktora wyrażeń lub bezpośredniego dokumentu BSON, nie ma to większego znaczenia.

Ale przejdźmy do ulepszeń wydajności, które można wprowadzić.

Użyj operacji zbiorczych do aktualizacji

Jak już wspomniano, operacje zbiorcze to sposób, w jaki powinieneś aktualizować taką iterację listy, a także „powinno się” używać kursora zamiast konwertować wszystkie wyniki na listę, ponieważ oszczędza to pamięć.

Unikanie wszystkich deklaracji określonego typu i po prostu reprezentowanie jako BsonDocument (co prawdopodobnie zaoszczędzi ci na marshallingu, ale nie jest potrzebne), wtedy podstawowy przykładowy proces to:

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);
    }

}

Więc zamiast wysyłać żądania do bazy danych dla każdego dokumentu pobranego z zapytania, tworzysz List WriteModel zamiast tego.

Gdy ta lista rozrośnie się do rozsądnej wartości (w tym przykładzie 1000), zatwierdź operację zapisu na serwerze w jednym żądaniu i odpowiedzi dla wszystkich operacji wsadowych. Tutaj używamy BulkWriteAsync .

Możesz tworzyć partie w rozmiarze większym niż 1000, jeśli chcesz, ale generalnie jest to rozsądna liczba. Jedynym prawdziwym twardym limitem jest limit BSON wynoszący 16 MB, który, ponieważ wszystkie żądania nadal są w rzeczywistości dokumentami BSON, nadal obowiązuje. W każdym razie potrzeba wielu żądań, aby zbliżyć się do 16 MB, ale należy również wziąć pod uwagę dopasowanie impedancji, w jaki sposób żądanie zostanie przetworzone, gdy faktycznie dotrze do serwera, zgodnie z dokumentacją:

"Każda grupa operacji może mieć maksymalnie 1000 operacji. Jeśli grupa przekroczy ten limit, MongoDB podzieli grupę na mniejsze grupy liczące 1000 lub mniej. Na przykład, jeśli lista operacji zbiorczych składa się z 2000 operacji wstawiania, MongoDB tworzy 2 grupy, każda z 1000 operacji."

Dlatego utrzymując rozmiar żądania na tym samym poziomie, na jakim serwer będzie je przetwarzał, zyskasz również korzyści z yield gdzie "wiele partii" może w rzeczywistości działać w równoległych połączeniach z serwerem, zamiast pozwalać serwerowi na dzielenie i kolejkowanie.

Zwrócony wynik to BulkWriteResult który będzie zawierał informacje o liczbie „dopasowań” i „modyfikacji” itp. z wysłanej partii operacji.

Naturalnie, ponieważ operacje są wykonywane „wsadowo”, sensowne jest sprawdzenie na końcu iteracji pętli, czy na liście istnieje więcej operacji „wsadowych”, a następnie oczywiście przesłanie ich w ten sam sposób.

Zwracając również uwagę na IsOrdered = false jako BulkWriteOptions oznacza, że ​​partia operacji nie jest faktycznie wykonywana w kolejności seryjnej, co oznacza, że ​​serwer może faktycznie wykonywać zadania „równolegle”. Może to spowodować „ogromną” poprawę szybkości, gdy kolejność zaangażowania nie jest wymagana. Domyślnie jest przesyłanie „zamówione” i seryjne.

Nie jest to wymagane, aby ustawić tę opcję, ale jeśli twoje zamówienie nie jest ważne (co nie powinno być w tym przypadku, ponieważ żadne inne żądania operacji nie zależą od poprzedniej modyfikacji dokumentu), wtedy poprawa, którą uzyskasz, jest warta zachodu.

Chodzi o „zmniejszenie” liczby rzeczywistych żądań kierowanych do serwera. Wysyłanie aktualizacji i oczekiwanie na odpowiedź wymaga czasu, a przy dużych operacjach jest to bardzo kosztowne ćwiczenie. Właśnie tym mają się zajmować operacje zbiorcze, poprzez zastosowanie kilku operacji w ramach jednego żądania.

Zmniejszenie tego narzutu to „ogromny” wzrost wydajności. Dlatego tego używasz.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. MongoDB — eksport danych

  2. Aplikacja Heroku ulega awarii po aktualizacji MongoDB do wersji 3.0

  3. Policz pola w kolekcji MongoDB

  4. db.collection nie jest funkcją podczas korzystania z MongoClient v3.0

  5. Mongoose nadpisuje dokument zamiast pól `$set`