« Swiz Framework - Inversion of Control micro-architecture | Main | Create a google site with the Google sites Java API »

Keeping Google App Engine alive

Working with GAE is simply amazing, but their app cache system take a lot to respond for the first time request on a low volume app.

App Engine Cache System
The Python runtime environment caches imported modules between requests on a single web server, similar to how a standalone Python application loads a module only once even if the module is imported by multiple files. If a handler script provides a main() routine, the runtime environment also caches the script. Otherwise, the handler script is loaded for every request.

The Problem
The cached script remain alive only a few seconds, so if your app has no request for 15 sec or more, the system throw away it.
So if your application dont have a a high traffic volume the handler script is loaded for every request.

Solution
First solution can be an external service that call your application every 10 sec or more. But this is not the best trick to do.

The App Engine Cron Service allows you to configure regularly scheduled tasks that operate at defined times or regular intervals. These tasks are commonly known as cron jobs.

These cron jobs are automatically triggered by the App Engine Cron Service. For instance, you might use this to send out a report email on a daily basis, to update some cached data every 10 minutes, or to update some summary information once an hour.
A cron job will invoke a URL at a given time of day. A URL invoked by cron is subject to the same limits and quotas as a normal HTTP request, including the request time limit.

This sounds perfect for our situation, implement a cron that request a script every 10 seconds, and this will keep our application alive.


### in queue.yaml
queue:
- name: hothandler
rate: 20/m
bucket_size: 1

### in cron.yaml
cron:
- description: ensure hot handler
  url: /_ah/queue/hothandler/start
  schedule: every 4 hours
### in app.yaml
handlers:
- url: /_ah/queue/hothandler/.*
  script: hothandler.py
  login: admin
### in hothandler.py
"""
Copyright (C)  2009  twitter.com/rcb
 
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
 
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""
import random
from wsgiref.handlers import CGIHandler
from google.appengine.api.labs import taskqueue
from google.appengine.api import memcache
from google.appengine.api.capabilities import CapabilitySet
memcache_service = CapabilitySet('memcache', methods=['set','get'])
hot_handler_queue = taskqueue.Queue(name='hothandler')
HOT_HANDLER_PREFIX = '/_ah/queue/hothandler/'
def wsgi_app(env, res):
    """ visit '/_ah/queue/hothandler/start' as admin to start a task """
    token = env['PATH_INFO'].replace(HOT_HANDLER_PREFIX,'')
    cur_token = memcache.get(HOT_HANDLER_PREFIX)
    if cur_token is None:
        if not memcache_service.is_enabled():
            cur_token = token
    if token in [cur_token, 'start']:
        next_token = str(random.random())
        url = '%s%s'%(HOT_HANDLER_PREFIX, next_token)
        next_task = taskqueue.Task(countdown=10, url=url)
        hot_handler_queue.add(next_task)
        memcache.set(HOT_HANDLER_PREFIX, next_token)
    res('200 OK',[('Content-Type','text/plain')])
    return ['ok']
def main():
    CGIHandler().run(wsgi_app)
    
if __name__ == '__main__':
    main()

### how to launch
1) visit "/_ah/queue/hothandler/start" in your browser
2) login as an admin which inserts the first task
3) view your logs

By the way this is on the production server, the dev server does not run the tasks automatically.

Literally like clockwork, a task hits the app once every 10 seconds and keeps it hot.

6 req/minute * 60 minutes/hour * 24 hours/day = 8640 req/day

It is a very small price to pay for good response time with a low volume app.

Memcache is used to ensure there is at most 1 active task, and cron is used to ensure there is at least 1 active task every 4 hours, so no maintenance.

CapabilitySet is used to test if memcache is available.

Apparently, if 1 handler remains warm, all other warm handlers remain warm too, so hothandler can exist in its own handler called hothandler.py, which is more modular.

Reference
Scheduled Tasks With Cron for Python
GAE App Caching
Hot Handler on Google Cookbook

TrackBack

TrackBack URL for this entry:
http://blog.comtaste.com/mt-tb.cgi/113

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

About

This page contains a single entry from the blog posted on April 16, 2010 5:02 PM.

The previous post in this blog was Swiz Framework - Inversion of Control micro-architecture.

The next post in this blog is Create a google site with the Google sites Java API.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type 3.33