3

I've read a lot of the GAE articles on counters and this pesky write limit in GAE. I've seen solutions with sharding, cron tasks, memcache, etc. Then I learned enough about java threads to be able to ask the question:

Q: Can we implement a counter in servlet threads using concurrency / servlet static varibales?

This would have the added benefit of fewer writes to the datastore and memcache (cost the same $), and remove the cron requirement as the last servlet in a series of fast counter hits would update the datastore.

I don't know enough about concurrent programming to come up with a solution, but I imagine something with static servlet variables, maybe "atomic-integers" and a last-update flag to enable checking if THIS servlet was the last servlet to update the static var, in the last 200ms, thus triggering a save to the datastore.

// Servlet gets a hit
// static var counter++
// Checks last datastore save time of the static var, if longer than 200ms
// save to datastore & save "last update time"
// If shorter than 200 ms ago, let the next servlet call update the datastore

Can this be done? Any proposals? Mucho appreciado on your thoughts.

DougA
  • 1,213
  • 9
  • 17
  • 2
    Similar question [here](http://stackoverflow.com/q/1095123/1101070). The thing with static variables is that if/when App Engine spins up another instance of your app on a **separate** server, your counter is no longer valid. As you see in that question/answer, a better approach would be to use memcache and write it out to the DB every now and then. – Marvin Pinto Mar 02 '12 at 15:50
  • Where is stated that writes to memcache is billed? I think they're free. – Christopher Ramírez Mar 02 '12 at 16:14
  • 1
    I originally saw the memcache charges here: [Memcache](http://code.google.com/appengine/docs/java/memcache/overview.html) but now I see the **Memcache API Calls quota** don't appear in the quotas page [Quotas](http://code.google.com/appengine/docs/quotas.html), so maybe that's an outdated reference to charging for memcache – DougA Mar 02 '12 at 18:06

3 Answers3

5

Just to elaborate a bit more on my comment:

As you are probably aware, App Engine spins up a new instance of your app whenever it reaches some traffic threshold. Given that new instances could be spun up on a completely different server, your static counter would be rendered invalid.

Now just to clarify, the correct way of implementing a counter in App Engine would be to use Sharding and split it across multiple entites.

Now assuming you wanted to minimize your datastore calls, you could instead use memcache to store your counter data and write it out to the datastore at a specified frequency (say via cron). Your memcache data will be consistent across all instances of your app.

You of course run the risk of losing your memcache counter in the event memcache goes down, but that's what writing it out to the datastore takes care of. Again, this isn't a 100% foolproof solution, sharding is; but it's one way of minimizing your datastore calls.

Marvin Pinto
  • 30,138
  • 7
  • 37
  • 54
0

Guido van Rossum talks about this in an article on his blog. As I understand, you're in a race condition problem. I don't know anything about Java. But guido links to an API of Memcache service for Java in appengine. Maybe this could help you to find a solution.

Christopher Ramírez
  • 1,710
  • 10
  • 13
0

If you're counting something that's 'mission critical', don't assume that the same instance will be handling the next request, or any subsequent request. The flip side of App Engine being able to scale quickly to handle spikes in demand is that instances go away when the spike passes. Instances can go away for other reasons, too. Sharded counters are the way to go when the count has be to right.

It's also generally a mistake to count on memcache as anything other than a cache that can have an arbitrarily shortened lifetime.

If an approximate count will do, then you have a few options. You can do something like accumulate counts in memcache, sweeping the count off to the datastore every Nth hit, or you can do the same using lock-protected globals.

Dave W. Smith
  • 24,318
  • 4
  • 40
  • 46