3

I'm originally familiar with relational-style data stores. I'm currently looking into NoSQL and trying to learn about its use-cases. Here's something that's been bugging me lately.

How do you do the following operation using the typical NoSQL product?

  • reads multiple inputs
  • computes new values for them (each output depends on ALL inputs)
  • writes the values back

An example of this, from past experience with other issues, is as follows. You have a web game with various user accounts. Users can initiate attacks on each other, where the attack formula is complicated black-box game-logic which determines the mutual outcome based on both inputs and randomness. You need to enforce that an attack happens atomically, and that the inputs reflect a consistent point in time, as do the outputs, with regard to some serialization of the sequence of attacks performed in the game.

It's important that:

  • all outputs depend on all inputs in some complex way we can't break apart (i.e. we can't just convert this to the textbook bank accounts to transaction ledgers example, we can't use only write-ahead logging, etc)
  • in other words, we aren't doing P1 += $10, P2 -= $10
  • more like, we're [for the sake of argument] doing [essentially something as complex/irreducible as] (P1, P2) = (sha1(P1+P2), sha1(P1-P2)) where we want a consistent snapshot of the values of P1 and P2 [not to say that particular line is a good example for an actual game]
  • the inputs will be an arbitrary choice of small subset of records, so we can't just used a compound record
  • an external observer sees a uniform/consistent state either before or after ALL the operations but not in between

My particular example was an issue we encountered. We were actually using a relational database, but we were not using its transactional features like we should have. As a result, online players attacking each other repeatedly tended to generate incorrect results and generate phantom resources for both attackers.

In the relational model, I would have done this using a transaction. Come to think of it, this may be a textbook example of transactions.

How would I go about achieving this in NoSQL?

Here are answers I have seen on SO and elsewhere which I consider a bad fit for this particular instance:

  • ignore the possibility of race conditions
  • redesign the game so that this isn't an issue
  • manually implement 2PC, bakery algorithm, etc. using the data store
  • use an external locking service

... although if you believe any or all of them is a good solution, please let me know how and why.

I would appreciate some pointers on how to implement this sort of thing in practice.

Thanks!

Community
  • 1
  • 1
Ming
  • 1,613
  • 12
  • 27
  • 3
    Since NoSQL encompass a large group of different products/technologies, I think answers would be different depending on which NoSQL solution you want to use. I don't think there will be an answer that will be the best one for all existing NoSQL solutions. Do you have any specific NoSQL solution in mind? – Laurent Parenteau May 25 '12 at 13:26
  • I've been looking at a few, and I do realize they're quite different from each other. But one common point I noticed was that none of them seemed to offer general support for operations like this. If a concrete example is necessary, let's ask: how would you do this in MongoDB? Or, how would you do this in CouchDB? – Ming May 25 '12 at 19:54
  • 1
    See, some NoSQL solutions (like GT.M) offers transactions. So your "relational model" solution of using transactions would work there. Now that you are asking specifically about MongoDB or CouchDB, let see what the experts of those solutions have to say. – Laurent Parenteau May 26 '12 at 00:25

1 Answers1

2

The two things you need to know about MongoDB regarding this topic are:

  • The unit of atomicity is a single document.
  • There are no transactions but you can simulate them.

Keeping that in mind, this sort of thing isn't MongoDB's strength as, and you point this out yourself, it is essentially a transaction.

However, if you were to try and model this you could start by creating a collection for attacks where the documents look like this:

{
  attacker: {user_document},
  attackee: {user_document},
  in_progress:  boolean (true or false),
  outcomes: [array of results based on calculation]
}

To start an attack you would query the attacks collection using a findAndModify with an upsert having a query document that contains both user ids. If one does exist, it will not create a new attack. If one doesn't exist, it will begin the attack by inserting the document and setting in_progress to true. Put all the necessary user details in there.

Then, do the black box calculation and push a series of updates that would need to occur to both user documents as a result of the attack onto the outcomes array and set in_progress to false.

When finished, apply the outcomes to the users collection one by one.

If no more outcomes exist, remove the attack document so that a new attack can begin.

Not sure if this would make complete sense given your requirements but hopefully this helps you think about how it could be done.

Tyler Brock
  • 29,626
  • 15
  • 79
  • 79