memcacheを使ってみました

前回のcookieを用いたセッションを書いてみました - 主にプログラムを勉強するブログという記事ではサーバー側のセッションの保存にデータストアを使用していたのですが、memcacheを使用するように書き換えてみました。

データストアを使用する場合、エンティティに寿命を設定できないため不要になったセッションを定期的にcronを使って消去するといった処理が必要でしたがmemcacheは有効期限を設定しておくことができるため消去処理が不要なのがメリットです。
一方、データストアは

db.StringListProperty()

を使用することによりリストをそのまま保存することができますが、memcacheはリストを保存することができないためJSON化した上で保存し、読みだす時はそれをデコードしてリストに戻しています。
9/6追記。上の記述は私の間違いで、実際はmemcacheにリストを保存することもできます。

memcacheは有効期限より長く生存することはありませんが、容量がいっぱいになった場合期限を待たず消えてしまうことがあるのでどんな用途でもデータストアの代用ができるわけではありません。

ところで今回テストしていてハマってしまったのですが、開発サーバのMemcache Viewerでmemcacheの状態を見ると

・Cache contains items up to 17 minutes old.

といった項目があります。
私はこれを保存されているアイテムのうち最も古いものが何分前に保存されたかを表しているのだと思っていました。(上の例だと17分)
コードではmemcacheの期限を330秒に設定しているのになんで17分たっても消えないんだ!?と理由がわからず困っていたのですが、実際はこの項目は
「前回キャッシュが完全にクリアされてからの時間」を表しており、現在保存されているデータの有無とは関係がありません。
Memcache Viewer内にあるFlush Cacheボタンを押されてからの時間、またはサーバが起動されてからの時間ということになります。
9/6追記。上の記述に間違いがありました。詳細はこちらに書きました。

# coding: utf-8

import webapp2
import cgi
import os
import binascii
import jinja2
import json
from google.appengine.api import memcache

jinja_environment = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))

DEFAULTUSER = {'name': 'GUEST', 'words': []}


class MainPage(webapp2.RequestHandler):
    def _screenwrite(self, session_cookie=False, user=DEFAULTUSER):
        """
        HTMLを出力する。
        session_cookie:新たにcookieを発行する場合のID
        user:mamcacheから取得した該当user
        """
        self.response.headers['Content-Type'] = 'text/html;charset=utf-8'

        if session_cookie:
            self.response.set_cookie('sessionID', session_cookie, max_age=300)

        template = jinja_environment.get_template('sessiontest.html')
        self.response.out.write(template.render(user=user))

    def get(self):
        sessionID = self.request.cookies.get('sessionID')

        if sessionID and memcache.get(sessionID):
            # すでにセッションがある場合はpostの場合を実行
            self.post()
            return

        #新しくIDを作成
        sessionID = binascii.hexlify(os.urandom(8))
        #JSON化してmemcacheに保存
        memcache.set(key=sessionID, value=json.dumps(DEFAULTUSER), time=330)

        self._screenwrite(session_cookie=sessionID)

    def post(self):
        sessionID = self.request.cookies.get('sessionID')

        if not (sessionID and memcache.get(sessionID)):
            #sessionIDが未設定、もしくはデータストアにない場合はget()を実行
            self.get()
            return

        #該当するセッションを取得
        session = json.loads(memcache.get(sessionID))

        session['name'] = cgi.escape(self.request.get('username')) or session['name']
        if self.request.get('word'):
            session['words'].append(cgi.escape(self.request.get('word')))

        memcache.replace(key=sessionID, value=json.dumps(session), time=330)

        self._screenwrite(user=session)

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