Google App Engine

Using Memcache

Memcache is a high-performance, distributed memory object caching system, primarily intended for fast access to cached results of datastore queries.

  1. The Memcache Pattern
  2. Modifying guestbook.py to use Memcache

The Memcache Pattern

Memcache is typically used with the following pattern:

  • The application receives a query from the user or the application.
  • The application checks whether the data needed to satisfy that query is in memcache.
    • If the data is in memcache, the application uses that data.
    • If the data is not in memcache, the application queries the datastore and stores the results in memcache for future requests.

The pseudocode below represents a typical memcache request:

def get_data():
    data = memcache.get('key')
    if data is not None:
        return data
    else:
        data = self.query_for_data()
        memcache.add('key', data, 60)
        return data

Modifying guestbook.py to use Memcache

The guestbook application in the Getting Started Guide queries the datastore on every request. You can modify the Guestbook application to use memcache before resorting to querying the datastore.

First we'll import the memcache module and create the method that checks memcache before running a query.

from google.appengine.api import memcache

def get_greetings(self, guestbook_name):
    """get_greetings()

    Checks the cache to see if there are cached greetings.
    If not, call render_greetings and set the cache

    Args:
      guestbook_name: Guestbook entity group key (string).

    Returns:
      A string of HTML containing greetings.
    """
    greetings = memcache.get('%s:greetings' % guestbook_name)
    if greetings is not None:
        return greetings
    else:
        greetings = self.render_greetings(guestbook_name)
        if not memcache.add('%s:greetings' % guestbook_name, greetings, 10):
            logging.error('Memcache set failed.')
        return greetings

Next we'll separate out the querying and creation of the HTML for the page. When we don't hit the cache, we'll call this method to query the datastore and build the HTML string that we'll store in memcache.

def render_greetings(self, guestbook_name):
    """render_greetings()

    Queries the database for greetings, iterate through the
    results and create the HTML.

    Args:
      guestbook_name: Guestbook entity group key (string).

    Returns:
      A string of HTML containing greetings
    """
    greetings = db.GqlQuery('SELECT * '
                            'FROM Greeting '
                            'WHERE ANCESTOR IS :1 '
                            'ORDER BY date DESC LIMIT 10',
                            guestbook_key(guestbook_name))
    output = cStringIO.StringIO()
    for greeting in greetings:
        if greeting.author:
            output.write('<b>%s</b> wrote:' % greeting.author)
        else:
            output.write('An anonymous person wrote:')
        output.write('<blockquote>%s</blockquote>' %
                      cgi.escape(greeting.content))
    return output.getvalue()

Finally we will update the MainPage handler to call the get_greetings() method and display some stats about the number of times the cache was hit or missed.

import cgi
import cStringIO
import datetime
import logging
import urllib
import webapp2

from google.appengine.ext import db
from google.appengine.api import memcache
from google.appengine.api import users


class Greeting(db.Model):
    """Models an individual Guestbook entry with an author, content, and date."""
    author = db.StringProperty()
    content = db.StringProperty(multiline=True)
    date = db.DateTimeProperty(auto_now_add=True)


def guestbook_key(guestbook_name=None):
    """Constructs a Datastore key for a Guestbook entity with guestbook_name."""
    return db.Key.from_path('Guestbook', guestbook_name or 'default_guestbook')


class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')

        greetings = self.get_greetings(guestbook_name)
        stats = memcache.get_stats()

        self.response.out.write('<b>Cache Hits:%s</b><br>' % stats['hits'])
        self.response.out.write('<b>Cache Misses:%s</b><br><br>' %
                                stats['misses'])
        self.response.out.write(greetings)

        self.response.out.write("""
          <form action="/sign?%s" method="post">
            <div><textarea name="content" rows="3" cols="60"></textarea></div>
            <div><input type="submit" value="Sign Guestbook"></div>
          </form>
          <hr>
          <form>Guestbook name: <input value="%s" name="guestbook_name">
          <input type="submit" value="switch"></form>
        </body>
      </html>""" % (urllib.urlencode({'guestbook_name': guestbook_name}),
                          cgi.escape(guestbook_name)))

    def get_greetings(self, guestbook_name):
        """
        get_greetings()
        Checks the cache to see if there are cached greetings.
        If not, call render_greetings and set the cache

        Args:
          guestbook_name: Guestbook entity group key (string).

        Returns:
          A string of HTML containing greetings.
        """
        greetings = memcache.get('%s:greetings' % guestbook_name)
        if greetings is not None:
            return greetings
        else:
            greetings = self.render_greetings(guestbook_name)
            if not memcache.add('%s:greetings' % guestbook_name, greetings, 10):
                logging.error('Memcache set failed.')
        return greetings

    def render_greetings(self, guestbook_name):
        """
        render_greetings()
        Queries the database for greetings, iterate through the
        results and create the HTML.

        Args:
          guestbook_name: Guestbook entity group key (string).

        Returns:
          A string of HTML containing greetings
        """
        greetings = db.GqlQuery('SELECT * '
                                'FROM Greeting '
                                'WHERE ANCESTOR IS :1 '
                                'ORDER BY date DESC LIMIT 10',
                                guestbook_key(guestbook_name))
        output = cStringIO.StringIO()
        for greeting in greetings:
            if greeting.author:
                output.write('<b>%s</b> wrote:' % greeting.author)
            else:
                output.write('An anonymous person wrote:')
            output.write('<blockquote>%s</blockquote>' %
                         cgi.escape(greeting.content))
        return output.getvalue()


class Guestbook(webapp2.RequestHandler):
    def post(self):
        # We set the same parent key on the 'Greeting' to ensure each greeting
        # is in the same entity group. Queries across the single entity group
        # will be consistent. However, the write rate to a single entity group
        # is limited to ~1/second.
        guestbook_name = self.request.get('guestbook_name')
        greeting = Greeting(parent=guestbook_key(guestbook_name))

        if users.get_current_user():
            greeting.author = users.get_current_user().nickname()

        greeting.content = self.request.get('content')
        greeting.put()
        self.redirect('/?' + urllib.urlencode({'guestbook_name': guestbook_name}))


app = webapp2.WSGIApplication([('/', MainPage),
                               ('/sign', Guestbook)],
                              debug=True)

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.