Domanda

Devo fare transazioni (inizio, commit o rollback), blocchi (selezionare per l'aggiornamento). Come posso farlo in un modello di documento db?

Modifica:

Il caso è questo:

  • Voglio gestire un sito di aste.
  • E penso anche a come dirigere l'acquisto.
  • In un acquisto diretto devo ridurre il campo quantità nel record articolo, ma solo se la quantità è maggiore di zero. Ecco perché ho bisogno di blocchi e transazioni.
  • Non so come risolverlo senza blocchi e / o transazioni.

Posso risolverlo con CouchDB?

È stato utile?

Soluzione

No. CouchDB utilizza una "concorrenza ottimistica" modello. In parole povere, ciò significa solo che invii una versione del documento insieme al tuo aggiornamento e CouchDB rifiuta la modifica se la versione del documento corrente non corrisponde a quella che hai inviato.

È ingannevolmente semplice, davvero. È possibile riformulare molti scenari basati su transazioni normali per CouchDB. Tuttavia, è necessario eliminare le conoscenze del dominio RDBMS durante l'apprendimento di CouchDB. È utile affrontare i problemi da un livello superiore, piuttosto che tentare di trasformare Couch in un mondo basato su SQL.

Tenere traccia dell'inventario

Il problema che hai delineato è principalmente un problema di inventario. Se hai un documento che descrive un articolo e include un campo per "quantità disponibile", puoi gestire problemi di concorrenza come questo:

  1. Recupera il documento, prendi nota della proprietà _rev che CouchDB invia insieme
  2. Decrementa il campo quantità, se è maggiore di zero
  3. Invia indietro il documento aggiornato, usando la proprietà _rev
  4. Se _rev corrisponde al numero attualmente memorizzato, eseguire!
  5. In caso di conflitto (quando _rev non corrisponde), recupera la versione del documento più recente

In questo caso, ci sono due possibili scenari di errore a cui pensare. Se la versione più recente del documento ha una quantità pari a 0, la gestisci come faresti in un RDBMS e avvisa l'utente che non possono effettivamente acquistare ciò che desiderano acquistare. Se la versione del documento più recente ha una quantità maggiore di 0, è sufficiente ripetere l'operazione con i dati aggiornati e ricominciare dall'inizio. Questo ti obbliga a fare un po 'più di lavoro di un RDBMS e potrebbe diventare un po' fastidioso se ci sono aggiornamenti frequenti e contrastanti.

Ora, la risposta che ho appena dato presuppone che farai le cose in CouchDB più o meno come faresti in un RDBMS. Potrei affrontare questo problema in modo leggermente diverso:

Comincerei con un "prodotto principale" documento che include tutti i dati del descrittore (nome, immagine, descrizione, prezzo, ecc.). Quindi aggiungerei un "biglietto di inventario" documento per ogni istanza specifica, con campi per product_key e claim_by . Se vendi un modello di martello e ne hai 20 da vendere, potresti avere documenti con chiavi come hammer-1 , hammer-2 , ecc., A rappresenta ogni martello disponibile.

Quindi, creerei una vista che mi dà un elenco di martelli disponibili, con una funzione di riduzione che mi permette di vedere un "totale". Questi sono completamente fuori dal bracciale, ma dovrebbero darti un'idea di come sarebbe una vista di lavoro.

Mappa

function(doc) 
{ 
    if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) { 
        emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev }); 
    } 
}

Questo mi dà un elenco di "biglietti" disponibili per chiave di prodotto. Potrei afferrare un gruppo di questi quando qualcuno vuole comprare un martello, quindi scorrere attraverso l'invio di aggiornamenti (usando id e _rev ) fino a quando non richiedo con successo uno (biglietti precedentemente rivendicati comporterà un errore di aggiornamento).

Riduzione

function (keys, values, combine) {
    return values.length;
}

Questa funzione di riduzione restituisce semplicemente il numero totale di articoli Inventory_ticket non reclamati, in modo da poter sapere quanti " martelli " sono disponibili per l'acquisto.

Avvertimenti

Questa soluzione rappresenta circa 3,5 minuti di riflessione totale per il problema specifico che hai presentato. Potrebbero esserci modi migliori per farlo! Ciò detto, riduce sostanzialmente gli aggiornamenti in conflitto e riduce la necessità di rispondere a un conflitto con un nuovo aggiornamento. Con questo modello, non avrai più utenti che tentano di modificare i dati nella voce di prodotto principale. Nel peggiore dei casi, avrai più utenti che tentano di richiedere un singolo biglietto e se hai preso molti di quelli dal tuo vi

Altri suggerimenti

Ampliamento della risposta di MrKurt. Per molti scenari non è necessario riscattare i biglietti azionari in ordine. Invece di selezionare il primo biglietto, è possibile selezionare in modo casuale tra i biglietti rimanenti. Dato un gran numero di biglietti e un gran numero di richieste simultanee, otterrai una contesa molto ridotta su quei biglietti, rispetto a tutti coloro che cercano di ottenere il primo biglietto.

Un modello di progettazione per le transazioni riposanti è quello di creare una "tensione" nel sistema. Nel caso d'uso di esempio popolare di una transazione con un conto bancario, è necessario assicurarsi di aggiornare il totale per entrambi i conti coinvolti:

  • Crea un documento di transazione "trasferisci 10 USD dal conto 11223 al conto 88733". Questo crea la tensione nel sistema.
  • Per risolvere eventuali scansioni di tensione per tutti i documenti di transazione e
    • Se l'account di origine non viene ancora aggiornato, aggiornare l'account di origine (-10 USD)
    • Se l'account di origine è stato aggiornato ma il documento di transazione non lo mostra, aggiornare il documento di transazione (ad es. impostare flag "quotato" nel documento)
    • Se l'account di destinazione non viene ancora aggiornato, aggiornare l'account di destinazione (+10 USD)
    • Se l'account di destinazione è stato aggiornato ma il documento di transazione non lo mostra, aggiornare il documento di transazione
    • Se entrambi gli account sono stati aggiornati, è possibile eliminare il documento di transazione o conservarlo per il controllo.

La scansione per la tensione deve essere eseguita in un processo di back-end per tutti i "documenti sulla tensione". per mantenere brevi i tempi di tensione nel sistema. Nell'esempio sopra ci sarà una breve incoerenza anticipata quando il primo account è stato aggiornato ma il secondo non è stato ancora aggiornato. Questo deve essere preso in considerazione nello stesso modo in cui gestirai l'eventuale coerenza se il tuo Couchdb è distribuito.

Un'altra possibile implementazione evita completamente la necessità di transazioni: basta archiviare i documenti di tensione e valutare lo stato del sistema valutando ogni documento di tensione coinvolto. Nell'esempio sopra ciò significherebbe che il totale per un conto è determinato solo come i valori di somma nei documenti di transazione in cui questo conto è coinvolto. In Couchdb puoi modellarlo molto bene come mappa / vista ridotta.

No, CouchDB non è generalmente adatto ad applicazioni transazionali perché non supporta operazioni atomiche in un ambiente cluster / replicato.

CouchDB ha sacrificato la capacità transazionale a favore della scalabilità. Per avere operazioni atomiche è necessario un sistema di coordinamento centrale, che limiti la tua scalabilità.

Se puoi garantire di avere solo un'istanza di CouchDB o che tutti coloro che modificano un determinato documento si collegano alla stessa istanza di CouchDB, puoi utilizzare il sistema di rilevamento dei conflitti per creare una sorta di atomicità usando i metodi sopra descritti ma se successivamente ridimensioni a un cluster o utilizzare un servizio ospitato come Cloudant si guasterà e dovrai rifare quella parte del sistema.

Quindi, il mio suggerimento sarebbe di usare qualcosa di diverso da CouchDB per i saldi del tuo account, sarà molto più facile in questo modo.

Come risposta al problema del PO, Couch probabilmente non è la scelta migliore qui. L'uso delle viste è un ottimo modo per tenere traccia dell'inventario, ma il bloccaggio a 0 è più o meno impossibile. Il problema è la condizione di gara quando leggi il risultato di una vista, decidi che puoi usare un "hammer-1" elemento, quindi scrivere un documento per usarlo. Il problema è che non esiste un modo atomico di scrivere il documento per usare il martello solo se il risultato della vista è che ci sono > 0 martello-1. Se 100 utenti interrogano tutti la vista contemporaneamente e visualizzano 1 hammer-1, possono tutti scrivere un documento per utilizzare un hammer 1, risultando in -99 hammer-1. In pratica, le condizioni di gara saranno abbastanza piccole - molto piccole se il tuo DB esegue localhost. Ma una volta ridimensionato e avendo un server o cluster DB fuori sede, il problema diventerà molto più evidente. Indipendentemente da ciò, è inaccettabile avere una condizione di razza di quel tipo in un sistema critico - relativo al denaro.

Un aggiornamento alla risposta di MrKurt (potrebbe essere solo datato o potrebbe non essere a conoscenza di alcune funzionalità di CouchDB)

Una vista è un buon modo per gestire cose come i saldi / inventari in CouchDB.

Non è necessario emettere docid e rev in una vista. Ottieni entrambi quelli gratuitamente quando recuperi i risultati della vista. Emetterli, specialmente in un formato dettagliato come un dizionario, aumenterà la tua vista inutilmente grande.

Una semplice visualizzazione per tenere traccia dei saldi di inventario dovrebbe apparire più simile a questa (anche dalla parte superiore della mia testa)

function( doc )
{
    if( doc.InventoryChange != undefined ) {
        for( product_key in doc.InventoryChange ) {
            emit( product_key, 1 );
        }
    }
}

E la funzione di riduzione è ancora più semplice

_sum

Utilizza una funzione di riduzione integrata che somma semplicemente i valori di tutte le righe con chiavi corrispondenti.

In questa vista, qualsiasi documento può avere un membro " InventoryChange " che mappa product_key's a una modifica del loro inventario totale. vale a dire.

{
    "_id": "abc123",
    "InventoryChange": {
         "hammer_1234": 10,
         "saw_4321": 25
     }
}

Aggiungerebbe 10 hammer_1234 e 25 saw_4321.

{
    "_id": "def456",
    "InventoryChange": {
        "hammer_1234": -5
    }
}

Brucerebbe 5 martelli dall'inventario.

Con questo modello, non aggiorni mai alcun dato, ma solo l'aggiunta. Ciò significa che non vi è alcuna possibilità di conflitti di aggiornamento. Tutte le questioni transazionali relative all'aggiornamento dei dati scompaiono :)

Un'altra cosa interessante di questo modello è che QUALSIASI documento nel DB può sia aggiungere che sottrarre articoli dall'inventario. Questi documenti possono contenere tutti i tipi di altri dati. Potresti avere un " Spedizione " documento con un mucchio di dati sulla data e ora ricevute, magazzino, dipendente ricevente ecc. e fintanto che quel documento definisce un InventoryChange, aggiornerà l'inventario. Come potrebbe una "vendita" doc e un " DamagedItem " doc ecc. Guardando ogni documento, leggevano molto chiaramente. E la vista gestisce tutto il duro lavoro.

In realtà, puoi farlo in un certo senso. Dai un'occhiata alla API documento HTTP e scorri verso il basso fino all'intestazione " Modifica più documenti con una singola richiesta " ;.

Fondamentalmente puoi creare / aggiornare / eliminare un mucchio di documenti in una singola richiesta post a URI / {dbname} / _ bulk_docs e tutti avranno successo o tutti falliranno. Il documento avverte tuttavia che questo comportamento potrebbe cambiare in futuro.

EDIT: come previsto, dalla versione 0.9 i documenti collettivi non funzionano più in questo modo.

Basta usare il tipo SQlite di soluzione leggera per le transazioni e quando la transazione è completata replicarla correttamente e contrassegnarla come replicata in SQLite

Tabella SQLite

txn_id    , txn_attribute1, txn_attribute2,......,txn_status
dhwdhwu$sg1   x                    y               added/replicated

È inoltre possibile eliminare le transazioni replicate correttamente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top