Ich muss Transaktionen ausführen (Beginnen, Festschreiben oder Zurücksetzen), Sperren (für Aktualisierungen auswählen).
Bearbeiten:
Der Fall ist dieser:
Kann ich das mit CouchDB lösen?
Nein. CouchDB verwendet ein Modell mit "optimistischer Parallelität". Im einfachsten Fall bedeutet dies lediglich, dass Sie zusammen mit Ihrem Update eine Dokumentversion senden. CouchDB lehnt die Änderung ab, wenn die aktuelle Dokumentversion nicht mit der von Ihnen gesendeten Version übereinstimmt.
Es ist wirklich täuschend einfach. Sie können viele normale transaktionsbasierte Szenarien für CouchDB neu gestalten. Sie müssen jedoch Ihr RDBMS-Domänenwissen ablehnen, wenn Sie CouchDB lernen. Es ist hilfreich, Probleme auf einer höheren Ebene anzugehen, anstatt zu versuchen, Couch zu einer SQL-basierten Welt zu formen.
Bestandsverfolgung
Bei dem von Ihnen beschriebenen Problem handelt es sich hauptsächlich um ein Inventarproblem. Wenn Sie über ein Dokument verfügen, das eine Position beschreibt, und das ein Feld für "Verfügbare Menge" enthält, können Sie Parallelitätsprobleme wie folgt behandeln:
_rev
-Eigenschaft, die CouchDB mitsendet_rev
-Eigenschaft zurück_rev
mit der aktuell gespeicherten Nummer übereinstimmt, tun Sie dies!_rev
nicht übereinstimmt), rufen Sie die neueste Dokumentversion abIn diesem Fall gibt es zwei mögliche Fehlerszenarien, über die man nachdenken muss. Wenn die neueste Dokumentversion eine Menge von 0 hat, behandeln Sie sie wie in einem RDBMS und weisen den Benutzer darauf hin, dass er nicht wirklich kaufen kann, was er kaufen möchte. Wenn die letzte Dokumentversion eine Menge größer als 0 hat, wiederholen Sie einfach den Vorgang mit den aktualisierten Daten und beginnen am Anfang von vorne. Dies zwingt Sie zu etwas mehr Arbeit als ein RDBMS und könnte bei häufigen, widersprüchlichen Aktualisierungen etwas ärgerlich werden.
Nun, die Antwort, die ich gerade gegeben habe, setzt voraus, dass Sie Dinge in CouchDB auf die gleiche Art und Weise tun werden wie in einem RDBMS. Ich könnte dieses Problem etwas anders angehen:
Ich würde mit einem "Masterprodukt" -Dokument beginnen, das alle Deskriptordaten (Name, Bild, Beschreibung, Preis usw.) enthält. Dann würde ich für jede spezifische Instanz ein "Inventar-Ticket" -Dokument mit Feldern für product_key
und claimed_by
hinzufügen. Wenn Sie ein Hammermodell verkaufen und 20 davon zum Verkauf anbieten, haben Sie möglicherweise Dokumente mit Schlüsseln wie hammer-1
, hammer-2
usw., um jeden verfügbaren Hammer darzustellen.
Dann würde ich eine Ansicht erstellen, die mir eine Liste der verfügbaren Hämmer gibt, mit einer Reduzierungsfunktion, mit der ich eine "Summe" sehen kann. Diese sind komplett aus der Manschette, sollten jedoch eine Vorstellung davon vermitteln, wie eine Arbeitsansicht aussehen würde.
Karte
function(doc)
{
if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) {
emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev });
}
}
Dies gibt mir eine Liste der verfügbaren "Tickets" nach Produktschlüssel. Ich könnte mir eine Gruppe davon holen, wenn jemand einen Hammer kaufen möchte, und dann das Senden von Aktualisierungen (mithilfe von id
und _rev
) wiederholen, bis ich erfolgreich einen solchen beanspruche (zuvor beanspruchte Tickets führen zu einem Aktualisierungsfehler).
Reduzieren
function (keys, values, combine) {
return values.length;
}
Diese Reduzierungsfunktion gibt einfach die Gesamtzahl der inventory_ticket
-Artikel zurück, die nicht beansprucht wurden. Sie können also feststellen, wie viele "Hammer" zum Kauf angeboten werden.
Vorbehalte
Diese Lösung stellt ungefähr 3,5 Minuten des gesamten Denkens für das spezielle Problem dar, das Sie vorstellten. Es kann bessere Wege geben, dies zu tun! Das bedeutet, dass in Konflikt stehende Aktualisierungen erheblich reduziert werden und die Notwendigkeit reduziert wird, auf einen Konflikt mit einem neuen Update zu reagieren. Bei diesem Modell haben Sie nicht mehrere Benutzer, die versuchen, Daten im primären Produkteintrag zu ändern. Im schlimmsten Fall gibt es mehrere Benutzer, die versuchen, ein einzelnes Ticket zu beanspruchen. Wenn Sie mehrere davon aus Ihrer Ansicht ausgewählt haben, wechseln Sie einfach zum nächsten Ticket und versuchen es erneut.
Referenz: https://wiki.Apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F
Erweiterung der Antwort von MrKurt. Für viele Szenarien müssen Sie keine Stock-Tickets in der Reihenfolge einlösen. Anstatt das erste Ticket auszuwählen, können Sie die verbleibenden Tickets nach dem Zufallsprinzip auswählen. Angesichts einer großen Anzahl von Tickets und einer großen Anzahl von gleichzeitigen Anfragen erhalten Sie weniger Tickets für diese Tickets als alle, die versuchen, das erste Ticket zu erhalten.
Ein Entwurfsmuster für restfull-Transaktionen besteht darin, eine "Spannung" im System zu erzeugen. Für den gängigen Anwendungsfall einer Banküberweisung müssen Sie sicherstellen, dass die Summe für beide beteiligten Konten aktualisiert wird:
Das Scannen auf Spannung sollte in einem Backend-Prozess für alle "Spannungsdokumente" erfolgen, um die Spannungszeiten im System kurz zu halten. Im obigen Beispiel liegt eine erwartete Inkonsistenz vor, wenn das erste Konto aktualisiert wurde, das zweite jedoch noch nicht aktualisiert wurde. Dies muss auf dieselbe Weise berücksichtigt werden, wie Sie es mit der eventuellen Konsistenz tun, wenn Ihre Couchdb verteilt wird.
Eine weitere mögliche Implementierung vermeidet die Notwendigkeit von Transaktionen vollständig: Speichern Sie einfach die Spannungsdokumente und bewerten Sie den Zustand Ihres Systems, indem Sie alle beteiligten Spannungsdokumente auswerten. Im obigen Beispiel würde dies bedeuten, dass die Summe für ein Konto nur als die Summenwerte in den Transaktionsdokumenten bestimmt wird, an denen dieses Konto beteiligt ist. In Couchdb können Sie dies sehr schön als Karten-/Verkleinerungsansicht modellieren.
Nein, CouchDB ist generell nicht für Transaktionsanwendungen geeignet, da es keine atomaren Vorgänge in einer Cluster-/Replikationsumgebung unterstützt.
CouchDB hat die Transaktionsfähigkeit zugunsten der Skalierbarkeit geopfert. Für atomare Operationen benötigen Sie ein zentrales Koordinationssystem, das Ihre Skalierbarkeit einschränkt.
Wenn Sie garantieren können, dass Sie nur über eine CouchDB-Instanz verfügen oder dass jeder, der ein bestimmtes Dokument ändert, eine Verbindung zu derselben CouchDB-Instanz herstellt, können Sie das Konflikterkennungssystem verwenden, um mithilfe der oben beschriebenen Methoden eine Art Atomizität zu erstellen Wenn Sie einen gehosteten Dienst wie Cloudant verwenden, wird ein Fehler auftreten, und Sie müssen diesen Teil des Systems erneut ausführen.
Mein Vorschlag wäre also, für Ihre Kontostände etwas anderes als CouchDB zu verwenden. Auf diese Weise wird es viel einfacher.
Als Antwort auf das OP-Problem ist Couch hier wahrscheinlich nicht die beste Wahl. Die Verwendung von Ansichten ist eine großartige Möglichkeit, den Überblick über das Inventar zu behalten, aber das Festhalten an 0 ist mehr oder weniger unmöglich. Das Problem ist die Race-Bedingung, wenn Sie das Ergebnis einer Ansicht lesen und entscheiden, dass Sie ein "Hammer-1" -Element verwenden möchten, und dann ein Dokument schreiben, um es zu verwenden. Das Problem ist, dass es keine atomare Methode gibt, das Doc nur für die Verwendung des Hammers zu schreiben, wenn das Ergebnis der Ansicht ist, dass es> 0 Hammer-1 gibt. Wenn 100 Benutzer alle die Ansicht gleichzeitig abfragen und 1 Hammer-1 anzeigen, können sie alle ein Dokument schreiben, um einen Hammer 1 zu verwenden, was -99 Hammer-1 ergibt. In der Praxis sind die Bedingungen für das Rennen relativ klein - wirklich klein, wenn Ihre DB localhost ausführt. Sobald Sie jedoch skalieren und einen externen DB-Server oder -Cluster haben, wird das Problem deutlich spürbarer. Trotzdem ist es inakzeptabel, in einem kritischen Geldsystem eine solche Rasse zu haben.
Ein Update der Antwort von MrKurt (es kann sein, dass das Datum gerade datiert ist oder er einige CouchDB-Funktionen nicht kannte)
Eine Sicht ist ein guter Weg, um mit Salden/Beständen in CouchDB umzugehen.
Sie müssen die docid nicht ausgeben und in einer Ansicht revidieren. Beides erhalten Sie kostenlos, wenn Sie Ansichtsergebnisse abrufen. Wenn Sie sie ausgeben - insbesondere in einem ausführlichen Format wie einem Wörterbuch -, wird Ihre Ansicht nur unnötig groß.
Eine einfache Ansicht zum Nachverfolgen von Bestandsbilanzen sollte eher so aussehen (auch von oben auf den Kopf)
function( doc )
{
if( doc.InventoryChange != undefined ) {
for( product_key in doc.InventoryChange ) {
emit( product_key, 1 );
}
}
}
Und die Reduzierfunktion ist noch einfacher
_sum
Dies verwendet eine eingebaute Reduzierfunktion , die nur die Werte aller Zeilen mit übereinstimmenden Schlüsseln summiert.
In dieser Ansicht kann jedes Dokument ein Mitglied "InventoryChange" haben, das product_keys einer Änderung im Gesamtinventar dieser Dokumente zuordnet. dh.
{
"_id": "abc123",
"InventoryChange": {
"hammer_1234": 10,
"saw_4321": 25
}
}
Würde 10 Hammer_1234 und 25 Saw_4321 hinzufügen.
{
"_id": "def456",
"InventoryChange": {
"hammer_1234": -5
}
}
Würde 5 Hämmer aus dem Inventar brennen.
Mit diesem Modell aktualisieren Sie niemals Daten, sondern fügen nur Daten hinzu. Dies bedeutet, dass keine Möglichkeit für Aktualisierungskonflikte besteht. Alle Transaktionsprobleme beim Aktualisieren von Daten gehen weg :)
Eine weitere schöne Sache dieses Modells ist, dass JEDES Dokument in der Datenbank Elemente aus dem Inventar hinzufügen und entfernen kann. Diese Dokumente können alle möglichen anderen Daten enthalten. Möglicherweise verfügen Sie über ein "Versand" -Dokument mit einer Reihe von Daten zu Datum und Uhrzeit, Lager, Angestellter usw., und solange dieses Dokument eine Inventaränderung definiert, wird der Bestand aktualisiert. Ein "Verkauf" -Dokument und ein "DamagedItem" -Dokument usw. könnten, wenn man sich jedes Dokument ansieht, sehr deutlich lesen. Und die Ansicht erledigt die harte Arbeit.
Eigentlich kannst du das irgendwie. Schauen Sie sich die HTTP Document API an und blättern Sie zur Überschrift "Mehrere Dokumente mit einer einzigen Anforderung ändern".
Grundsätzlich können Sie eine Reihe von Dokumenten in einer einzigen Post-Anfrage an URI/{Datenbankname}/_ bulk_docs erstellen/aktualisieren/löschen. Sie werden entweder alle erfolgreich sein oder alle schlagen fehl. Das Dokument weist jedoch darauf hin, dass sich dieses Verhalten in der Zukunft ändern kann.
BEARBEITEN: Wie vorausgesagt funktionieren die Bulk-Dokumente ab Version 0.9 nicht mehr auf diese Weise.
Verwenden Sie einfach eine einfache Lösung von SQlite für Transaktionen. Nach Abschluss der Transaktion replizieren Sie sie erfolgreich, und markieren Sie sie in SQLite als repliziert
SQLite-Tabelle
txn_id , txn_attribute1, txn_attribute2,......,txn_status
dhwdhwu$sg1 x y added/replicated
Sie können auch die Transaktionen löschen, die erfolgreich repliziert wurden.