2017-11-07 7 views
3

Auf einem Single-Instanz MongoDB-Server, auch wenn die Schreibanforderungen auf dem Client auf Journaled festgelegt sind, ist eine in jedem paar tausend Dokumente nicht sofort ersetzbar nach dem Einfügen.ReplaceOneAsync() unmittelbar nach InsertOneAsync() nicht immer funktioniert, auch wenn Journaled

Ich hatte den Eindruck, dass Dokumente sofort zur Abfrage verfügbar sind.

Der folgende Code fügt ein Dokument ein, aktualisiert dann die DateModified-Eigenschaft des Dokuments und versucht, das Dokument basierend auf der ID des Dokuments und dem alten Wert dieser Eigenschaft zu aktualisieren.

public class MyDocument 
{ 
    public BsonObjectId Id { get; set; } 

    public DateTime DateModified { get; set; } 
} 

static void Main(string[] args) 
{ 
    var r = Task.Run(MainAsync); 

    Console.WriteLine("Inserting documents... Press any key to exit."); 
    Console.ReadKey(intercept: true); 
} 

private static async Task MainAsync() 
{ 
    var client = new MongoClient("mongodb://localhost:27017"); 
    var database = client.GetDatabase("updateInsertedDocuments"); 

    var concern = new WriteConcern(journal: true); 

    var collection = database.GetCollection<MyDocument>("docs").WithWriteConcern(concern); 

    int errorCount = 0; 
    int totalCount = 0; 

    do 
    { 
     totalCount++; 

     // Create and insert the document 
     var document = new MyDocument 
     { 
      DateModified = DateTime.Now, 
     }; 
     await collection.InsertOneAsync(document); 

     // Save and update the modified date 
     var oldDateModified = document.DateModified; 
     document.DateModified = DateTime.Now; 

     // Try to update the document by Id and the earlier DateModified 
     var result = await collection.ReplaceOneAsync(d => d.Id == document.Id && d.DateModified == oldDateModified, document); 

     if (result.ModifiedCount == 0) 
     { 
      Console.WriteLine($"Error {++errorCount}/{totalCount}: doc {document.Id} did not have DateModified {oldDateModified.ToString("yyyy-MM-dd HH:mm:ss.ffffff")}"); 

      await DoesItExist(collection, document, oldDateModified); 
     } 
    } 
    while (true); 
} 

Der Code wird mit einer Rate von ca. 250 Dokumenten pro Sekunde eingefügt. Eine in etwa 1.000-15.000 Aufrufen an ReplaceOneAsync(d => d.Id == document.Id && d.DateModified == oldDateModified, ...) schlägt fehl, da es eine ModifiedCount von 0 zurückgibt. Die Fehlerrate hängt davon ab, ob wir einen Debug- oder einen Release-Build ausführen und ob der Debugger angehängt ist oder nicht: mehr Geschwindigkeit bedeutet mehr Fehler.

Der gezeigte Code repräsentiert etwas, das ich nicht wirklich leicht ändern kann. Natürlich würde ich lieber eine Reihe von Update.Set() Calls durchführen, aber das ist momentan keine Option. Die InsertOneAsync(), gefolgt von einer ReplaceOneAsync() wird durch eine Art von Repository-Muster abstrahiert, die Entitäten per Referenz aktualisiert. Die nicht asynchronen Gegenstücke der Methoden zeigen dasselbe Verhalten an.

Eine einfache Thread.Sleep(100) zwischen Einfügen und Ersetzen mildert das Problem.

Wenn die Abfrage fehlschlägt, und wir eine Weile warten und dann versuchen, das Dokument im folgenden Code erneut abzufragen, wird es jedes Mal gefunden.

private static async Task DoesItExist(IMongoCollection<MyDocument> collection, MyDocument document, DateTime oldDateModified) 
{ 
    Thread.Sleep(500); 

    var fromDatabaseCursor = await collection.FindAsync(d => d.Id == document.Id && d.DateModified == oldDateModified); 
    var fromDatabaseDoc = await fromDatabaseCursor.FirstOrDefaultAsync(); 

    if (fromDatabaseDoc != null) 
    { 
     Console.WriteLine("But it was found!"); 
    } 
    else 
    { 
     Console.WriteLine("And wasn't found!"); 
    } 
} 

Versionen, auf denen dies der Fall ist: 3.4.0

  • MongoDB Community Server, 3.4.1, 3.4.3, 3.4.4 und 3.4.10, alle auf WiredTiger Speicher-Engine
  • Server läuft auf Windows, andere Betriebssysteme als auch
  • C# Mongo Treiber 2.3.0 und 2.4.4

Ist dies ein Problem in MongoDB, oder tun wir (oder Esel (falsch) etwas falsch?

Oder das eigentliche Endziel, wie kann ich sicherstellen, dass ein Insert sofort durch ein Update abrufbar ist?

+0

Ich habe den von Ihnen geposteten Code ausprobiert, kann aber die Situation, die Sie gesehen haben, nicht reproduzieren. Welche Version ist der C# -Treiber, den Sie verwendet haben? –

+0

@Kevin guten Fang, passiert es auf Treiber 2.3.0 und 2.4.4. Die Fehlerrate mit diesen Treibern auf meiner aktuellen Maschine (MongoDB 3.4.3) beträgt 1 zu 2500 Einfügungen. Ich muss das Tool nicht lange laufen lassen, damit Fehler angezeigt werden. – CodeCaster

+0

@KevinAdistambha, konnte ich es auch mit Mongo 3.4.4 und 3.6.6-RC3, Treiber 2.4.4 und C# 4.6 reproduzieren.1 – Skami

Antwort

3

ReplaceOneAsync gibt 0 zurück, wenn das neue Dokument mit dem alten identisch ist (weil sich nichts geändert hat).

Es sieht für mich so aus, wenn Ihr Test schnell genug ausgeführt wird, können die verschiedenen Aufrufe von DateTime.Now den gleichen Wert zurückgeben, daher ist es möglich, dass Sie das exakt gleiche Dokument an InsertOneAsync und ReplaceOneAsync übergeben.

+0

Richtig, also sollten wir 'MatchedCount', nicht' ModifiedCount' überprüfen. Ich könnte schwören, dass wir nicht versuchen, ein Dokument zu ersetzen, wenn keine Änderungen daran vorgenommen wurden, aber das klingt nach einer idealen Gelegenheit, weitere Komponententests um diesen Code hinzuzufügen. – CodeCaster

Verwandte Themen