Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I do transactions and locks in CouchDB?

I need to do transactions (begin, commit or rollback), locks (select for update). How can I do it in a document model db?

Edit:

The case is this:

  • I want to run an auctions site.
  • And I think how to direct purchase as well.
  • In a direct purchase I have to decrement the quantity field in the item record, but only if the quantity is greater than zero. That is why I need locks and transactions.
  • I don't know how to address that without locks and/or transactions.

Can I solve this with CouchDB?

like image 454
user2427 Avatar asked Nov 18 '08 18:11

user2427


People also ask

Does CouchDB support transactions?

CouchDB does not support transactions in the traditional sense (although it works transactionally), so you could conclude CouchDB is not well suited to store bank data.

Is CouchDB a distributed database?

CouchDB is a peer-based distributed database system. It allows users and servers to access and update the same shared data while disconnected.

What is transaction and locking in database?

Transactions use locks to control concurrent access to data, achieving two important database goals: Consistency ensures that the data you are viewing or changing is not changed by other users until you are finished with the data.

How is data stored in CouchDB?

Data in CouchDB is stored in semi-structured documents that are flexible with individual implicit structures, but it is a simple document model for data storage and sharing. If we want see our data in many different ways, we need a way to filter, organize and report on data that hasn't been decomposed into tables.


2 Answers

No. CouchDB uses an "optimistic concurrency" model. In the simplest terms, this just means that you send a document version along with your update, and CouchDB rejects the change if the current document version doesn't match what you've sent.

It's deceptively simple, really. You can reframe many normal transaction based scenarios for CouchDB. You do need to sort of throw out your RDBMS domain knowledge when learning CouchDB, though. It's helpful to approach problems from a higher level, rather than attempting to mold Couch to a SQL based world.

Keeping track of inventory

The problem you outlined is primarily an inventory issue. If you have a document describing an item, and it includes a field for "quantity available", you can handle concurrency issues like this:

  1. Retrieve the document, take note of the _rev property that CouchDB sends along
  2. Decrement the quantity field, if it's greater than zero
  3. Send the updated document back, using the _rev property
  4. If the _rev matches the currently stored number, be done!
  5. If there's a conflict (when _rev doesn't match), retrieve the newest document version

In this instance, there are two possible failure scenarios to think about. If the most recent document version has a quantity of 0, you handle it just like you would in a RDBMS and alert the user that they can't actually buy what they wanted to purchase. If the most recent document version has a quantity greater than 0, you simply repeat the operation with the updated data, and start back at the beginning. This forces you to do a bit more work than an RDBMS would, and could get a little annoying if there are frequent, conflicting updates.

Now, the answer I just gave presupposes that you're going to do things in CouchDB in much the same way that you would in an RDBMS. I might approach this problem a bit differently:

I'd start with a "master product" document that includes all the descriptor data (name, picture, description, price, etc). Then I'd add an "inventory ticket" document for each specific instance, with fields for product_key and claimed_by. If you're selling a model of hammer, and have 20 of them to sell, you might have documents with keys like hammer-1, hammer-2, etc, to represent each available hammer.

Then, I'd create a view that gives me a list of available hammers, with a reduce function that lets me see a "total". These are completely off the cuff, but should give you an idea of what a working view would look like.

Map

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

This gives me a list of available "tickets", by product key. I could grab a group of these when someone wants to buy a hammer, then iterate through sending updates (using the id and _rev) until I successfully claim one (previously claimed tickets will result in an update error).

Reduce

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

This reduce function simply returns the total number of unclaimed inventory_ticket items, so you can tell how many "hammers" are available for purchase.

Caveats

This solution represents roughly 3.5 minutes of total thinking for the particular problem you've presented. There may be better ways of doing this! That said, it does substantially reduce conflicting updates, and cuts down on the need to respond to a conflict with a new update. Under this model, you won't have multiple users attempting to change data in primary product entry. At the very worst, you'll have multiple users attempting to claim a single ticket, and if you've grabbed several of those from your view, you simply move on to the next ticket and try again.

Reference: https://wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F

like image 144
MrKurt Avatar answered Oct 13 '22 05:10

MrKurt


Expanding on MrKurt's answer. For lots of scenarios you don't need to have stock tickets redeemed in order. Instead of selecting the first ticket, you can select randomly from the remaining tickets. Given a large number tickets and a large number of concurrent requests, you will get much reduced contention on those tickets, versus everyone trying to get the first ticket.

like image 36
Kerr Avatar answered Oct 13 '22 04:10

Kerr