{"id":628,"date":"2009-05-30T12:24:40","date_gmt":"2009-05-30T16:24:40","guid":{"rendered":"http:\/\/www.dr-chuck.com\/wordpress\/?p=628"},"modified":"2011-12-17T12:30:40","modified_gmt":"2011-12-17T16:30:40","slug":"reducing-data-store-contention-in-app-engine-get_or_insert","status":"publish","type":"post","link":"https:\/\/www.dr-chuck.com\/csev-blog\/2009\/05\/reducing-data-store-contention-in-app-engine-get_or_insert\/","title":{"rendered":"Reducing Data Store Contention in App Engine get_or_insert"},"content":{"rendered":"<p>\nI was giving a demo at the Apple Academix Conference during a keynote where I use a Google App Engine application to simulate a clicker-like activity in a course.   It is a simple multi-user number guessing game.   Whenever the audience participation was too good and too many people started clicking on the application I would get errors &#8211; it made the demo in the middle of my talk lose a bit of credibility &#8211; but it was also funny.<\/p>\n<p>\nI finally saw the error and spent a few hours to track the issue down. I was getting the following message:<\/p>\n<pre>\nTransactionFailedError: too much contention on these datastore\nentities. please try again.\n<\/pre>\n<p>\nI had been using get_or_insert() pretty much with reckless abandon and wondered if it was smart enough to optimistically do a get() and see if the object was there before starting a transaction to do the get() and then put() if necessary.  But given the behavior I was seeing &#8211; it looked like it was starting a transaction every time.<\/p>\n<p>\nI had 20 participants and after the first click in the application, there were no new objects in the application and 20 people clicking within a 10 second interval were causing the error to occur.  I suspect but cannot confirm that at some level the AE framework marks you as abusing transactions and then it takes a while before load drops down that it lets you once again use transactions.<\/p>\n<p>\nSo  I decided to reduce my transaction abuse and build an optimistic get or insert that does the get() first and then if the record does not exist do the get_or_insert().  This way once objects start to exist there will be no more transactions.  And yes, my next optimization is to use memcache.<\/p>\n<p>\nSo this was my approach.<\/p>\n<p><!--more--><\/p>\n<p>\nIn my bad code, the pattern looked like this:<\/p>\n<pre>\ncourse = LTI_OrgCourse.get_or_insert(\"key:\"+course_id, parent=org)\n<\/pre>\n<p>\nSo I wrote this function:<\/p>\n<pre>\ndef opt_get_or_insert(obj, key, parent=None):\nlogging.info(\"OPT key=\"+str(key))\nif parent == None:\nmod = obj.get_by_key_name(key)\nif mod != None : return mod\nmod = obj.get_or_insert(key)\nelse:\nmod = obj.get_by_key_name(key, parent=parent)\nif mod != None : return mod\nmod = obj.get_or_insert(key, parent=parent)\nreturn mod\n<\/pre>\n<p>\nAnd then changed all my calls to be:<\/p>\n<pre>\ncourse = opt_get_or_insert(LTI_OrgCourse,\"key:\"+course_id, parent=org)\n<\/pre>\n<p>\nI wish this were part of the App Engine library instead of something I had to write.   This may not be the most general approach and there may be better ways to do this &#8211; but for now I hope that it avoids my dreaded &#8220;TransactionFailedError: too much contention on these datastore entities. please try again.&#8221; message even under low load levels.<\/p>\n<p>\nNow off to write a load test for this to see if I can reproduce it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I was giving a demo at the Apple Academix Conference during a keynote where I use a Google App Engine application to simulate a clicker-like activity in a course. It is a simple multi-user number guessing game. Whenever the audience participation was too good and too many people started clicking on the application I would [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-628","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.dr-chuck.com\/csev-blog\/wp-json\/wp\/v2\/posts\/628","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dr-chuck.com\/csev-blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dr-chuck.com\/csev-blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dr-chuck.com\/csev-blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dr-chuck.com\/csev-blog\/wp-json\/wp\/v2\/comments?post=628"}],"version-history":[{"count":1,"href":"https:\/\/www.dr-chuck.com\/csev-blog\/wp-json\/wp\/v2\/posts\/628\/revisions"}],"predecessor-version":[{"id":2745,"href":"https:\/\/www.dr-chuck.com\/csev-blog\/wp-json\/wp\/v2\/posts\/628\/revisions\/2745"}],"wp:attachment":[{"href":"https:\/\/www.dr-chuck.com\/csev-blog\/wp-json\/wp\/v2\/media?parent=628"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dr-chuck.com\/csev-blog\/wp-json\/wp\/v2\/categories?post=628"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dr-chuck.com\/csev-blog\/wp-json\/wp\/v2\/tags?post=628"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}