it-swarm.com.de

Kann ich Transaktionen und Sperren in CouchDB durchführen?

Ich muss Transaktionen ausführen (Beginnen, Festschreiben oder Zurücksetzen), Sperren (für Aktualisierungen auswählen).

Bearbeiten:

Der Fall ist dieser:

  • Ich möchte eine Auktionsseite betreiben.
  • Und ich denke auch, wie man direkt kauft.
  • Bei einem Direktkauf muss ich das Mengenfeld im Positionssatz dekrementieren, jedoch nur, wenn die Menge größer als Null ist. Deshalb brauche ich Sperren und Transaktionen.
  • Ich kann nicht ohne Sperren und/oder Transaktionen damit umgehen.

Kann ich das mit CouchDB lösen?

78
user2427

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:

  1. Rufen Sie das Dokument ab und beachten Sie die _rev-Eigenschaft, die CouchDB mitsendet
  2. Verringern Sie das Mengenfeld, wenn es größer als Null ist
  3. Senden Sie das aktualisierte Dokument mit der _rev-Eigenschaft zurück
  4. Wenn der _rev mit der aktuell gespeicherten Nummer übereinstimmt, tun Sie dies!
  5. Wenn ein Konflikt vorliegt (wenn _rev nicht übereinstimmt), rufen Sie die neueste Dokumentversion ab

In 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

135
MrKurt

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.

24
Kerr

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:

  • Erstellen Sie einen Transaktionsdokument "Überweisung von USD 10 von Konto 11223 auf Konto 88733". Dies erzeugt die Spannung im System.
  • Um einen Spannungs-Scan für alle Transaktionsdokumente aufzulösen, und
    • Wenn das Quellkonto noch nicht aktualisiert wurde, aktualisieren Sie das Quellkonto (-10 USD).
    • Wenn das Quellkonto aktualisiert wurde, das Transaktionsdokument jedoch nicht angezeigt wird, aktualisieren Sie das Transaktionsdokument (z. B. das gesetzte Flag "sourcedone" im Dokument).
    • Wenn das Zielkonto noch nicht aktualisiert wurde, aktualisieren Sie das Zielkonto (+10 USD).
    • Wenn das Zielkonto aktualisiert wurde, dies jedoch im Transaktionsdokument nicht angezeigt wird, aktualisieren Sie das Transaktionsdokument 
    • Wenn beide Konten aktualisiert wurden, können Sie das Transaktionsdokument löschen oder zur Prüfung aufbewahren.

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.

20
ordnungswidrig

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.

5

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.

5
wallacer

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.

3
Evan

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.

0
Ravinder Payal