Lors de la mise à jour comme vous le faites, vous devez récupérer le contenu du document afin de l'inspecter et d'apporter de telles modifications. MongoDB n'a pas d'opérations atomiques qui agissent sur les valeurs existantes comme vous le souhaitez, donc une itération est bien sûr nécessaire.
Il n'y a pas de réelle différence dans la partie "requête" de la façon dont vous faites correspondre l'expression régulière entre vos deux versions de l'instruction. Quoi qu'il en soit, le contenu est converti en BSON avant d'être envoyé au serveur de toute façon, donc si vous utilisez un générateur d'expression standard ou un document BSON direct, cela n'a que peu d'importance.
Mais passons aux améliorations de performances qui peuvent être apportées.
Utiliser les opérations groupées pour mettre à jour
Comme indiqué, les opérations en bloc sont la façon dont vous devriez mettre à jour une telle itération de liste, et vous "devriez" également utiliser un curseur plutôt que de convertir tous les résultats en liste, car cela économisera de la mémoire.
Éviter toutes les déclarations de type spécifiques et simplement représenter comme BsonDocument
(ce qui vous fera probablement économiser sur le marshalling, mais pas nécessaire) alors l'exemple de processus de base serait :
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);
}
}
Ainsi, plutôt que de faire une requête à la base de données pour chaque document extrait de la requête, vous créez une List
de WriteModel
opérations à la place.
Une fois que cette liste a atteint une valeur raisonnable (1000 dans cet exemple), vous validez l'opération d'écriture sur le serveur en une seule requête et réponse pour toutes les opérations par lots. Ici, nous utilisons BulkWriteAsync
.
Vous pouvez créer les lots dans une taille supérieure à 1000 si vous le souhaitez, mais il s'agit généralement d'un nombre raisonnable à gérer. La seule véritable limite stricte est la limite BSON de 16 Mo, qui s'applique toujours puisque toutes les demandes sont toujours en fait des documents BSON. Quoi qu'il en soit, il faut beaucoup de requêtes pour approcher les 16 Mo, mais il y a aussi une correspondance d'impédance à prendre en compte dans la manière dont la requête sera traitée lorsqu'elle atteindra réellement le serveur, comme documenté :
"Chaque groupe d'opérations peut avoir au plus 1 000 opérations. Si un groupe dépasse cette limite, MongoDB divisera le groupe en plus petits groupes de 1 000 opérations ou moins. Par exemple, si la liste des opérations en bloc comprend 2 000 opérations d'insertion, MongoDB crée 2 groupes, chacun avec 1000 opérations."
Par conséquent, en gardant la taille de la requête au même niveau que la façon dont le serveur la traitera, vous bénéficiez également du yield
où "plusieurs lots" peuvent en fait agir dans des connexions parallèles au serveur, plutôt que de laisser le serveur faire le fractionnement et la mise en file d'attente.
Le résultat renvoyé est de BulkWriteResult
qui contiendra des informations sur le nombre de "correspondances" et de "modifications" etc. du lot d'opérations envoyé.
Naturellement, puisque les opérations sont en "lots", il est logique de vérifier ensuite à la fin de l'itération de la boucle pour voir si d'autres opérations "batch" existent dans la liste, puis bien sûr de les soumettre de la même manière.
Notez également le IsOrdered = false
comme BulkWriteOptions
signifie que le lot d'opérations n'est pas réellement exécuté dans l'ordre sériel, ce qui signifie que le serveur peut en fait exécuter les tâches en "parallèle". Cela peut apporter des améliorations de vitesse "énormes" là où l'ordre d'engagement n'est pas requis. La valeur par défaut est de soumettre "commandé" et en série.
Ce n'est pas nécessaire pour définir cette option, mais si votre commande n'est pas importante (ce qui ne devrait pas être le cas dans ce cas, car aucune autre demande d'opération ici ne dépend de la modification précédente d'un document), alors l'amélioration que vous obtenez en vaut la peine.
Il s'agit de "réduire" le nombre de requêtes réelles adressées au serveur. L'envoi de mises à jour et l'attente d'une réponse prennent du temps, et dans les grandes opérations, c'est un exercice très coûteux. C'est ce que les opérations en bloc sont censées traiter, en appliquant plusieurs opérations au sein d'une même requête.
La réduction de cette surcharge est un gain de performances "énorme". C'est pourquoi vous l'utilisez.