3

we have a button in a web game for the users to collect reward. That should only be clicked once, and upon receiving the request, we'll mark it collected in DB.

we've already blocked the buttons in the client from repeated clicking. But that won't help if people resend the package multiple times to our server in short period of time.

what I want is a method to block this from server side.

we're using Playframework 2 (2.0.3-RC2) for server side and so far it's stateless, I'm tempted to use a Set to guard like this:

if processingSet has userId then BadRequest
else put userId in processingSet and handle request
     after that remove userId from that Set

but then I'd have to face problem like Updating Scala collections thread-safely and still fail to block the user once we have more than one server behind load balancing.

one possibility I'm thinking about is to have a table in DB in place of the processingSet above, but that would incur 1+ DB operation per request, are there any better solution~?

thanks~

Community
  • 1
  • 1
Chris
  • 1,094
  • 1
  • 11
  • 26
  • 1
    It was better for all if you wrote the target version of the Play – biesior Aug 08 '12 at 12:28
  • What's the concern with marking the reward as collected more than once? Is it that the reward will also get applied to the user more than once? And what DB are you using? RDBMS or NoSql? – Brian Smith Aug 08 '12 at 12:43
  • @biesior the Play version is 2.0.3-RC2 – Chris Aug 08 '12 at 13:41
  • @BrianSmith it'll break the game if the reward could be collected more than once~ And I'm using MongoDB – Chris Aug 08 '12 at 13:43
  • @Chris OK, just try to remember that adding a Play's version tag is quite important, as there are large differences between Play `2.0` and `1.x` – biesior Aug 08 '12 at 13:44

2 Answers2

3

Additional DB operation is relatively 'cheap' solution in that case. You should use it if you'e planning to save the buttons state permanently.

If the button is disabled only for some period of time (for an example until the game is over) you can also consider using the cache API however keep in mind that's not dedicated for solutions which should be stored for long time (it should not be considered as DB alternative).

biesior
  • 55,576
  • 10
  • 125
  • 182
  • the buttons state is to be stored permanently, and that's handled already~ thanks for the link to cache API, maybe it can be used to implement the guard, for the little time it takes for the DB to finish storing the button state. It should have a better performance than using DB, do you think so~? – Chris Aug 08 '12 at 14:05
  • It depends on many factors, like hardware, RAM availability, requests count. The best way is to compare both. Anyway remember to check the DB if you won't find the value in cache. And always persist the value in DB right after setting new cache entry. In such case most probably you'll keep best `performance+safety` combination – biesior Aug 08 '12 at 14:21
3

Given that you're using Mongo and so don't have transactions spanning separate collections, I think you can probably implement this guard using an atomic operation - namely "Update if current", which is effectively CompareAndSwap.

Assuming you've got a collection like "rewards" which has a "collected" attribute, you can update the collected flag to true only if it is currently false and if that operation doesn't fail you can proceed to apply the reward knowing that for any other requests the same operation will fail.

Brian Smith
  • 3,383
  • 30
  • 41