GAEでcronを設定する

cronは一般的には

crontab(クロンタブ、あるいはクローンタブ、クーロンタブとも)コマンドはUnix系OSにおいて、コマンドの定時実行のスケジュール管理を行うために用いられるコマンドである。

http://ja.wikipedia.org/wiki/Crontab

とのことですがGoogle App Engineのそれは一定間隔で自アプリの指定URLにHTTP-GETリクエストを送る機能です。
URLリクエストを受け取って行う動作は通常のhttpリクエストを受けた場合同様自由に記述できるので、実質的には任意の間隔で指定した動作をする機能として使用できます。

私は「スコアアタックしりとり」のなかでデータストアをメンテナンスするために使っています。
スコアアタックしりとりではユーザーからのアクセスがあるとセッションIDを発行し、単語が入力される度にサーバでプレイ中のデータとしてデータストアに保存しています。これらのデータはユーザーが「最初からプレイ」のボタンを押すと今回のゲームが終了したのだと判断し削除されますが、プレイの途中でブラウザを閉じた場合などはプレイ途中のデータがデータストアに残ってしまいます。
これをそのまま放置しているとプレイ途中のデータがデータストアにたまっていってしまうため、ゲームの制限時間を30分に設定しそれを過ぎたデータをcronをつかって削除しています。
制限時間は30分ですが実際には誤差を考慮して1時間経過で削除しています。この処理を1時間に1回行います。
これ以外にはてなキーワードに問い合わせた単語のデータも30日間の保存期間が過ぎたら削除しています。こちらは1日に1回行います。

cronを使うにはcron.yamlというファイルを作成し、app.yamlと同じ階層に置いておきます。
スコアアタックしりとりの場合は下のように書いています。

cron:
- description: daily WordsStock maintainer
  url: /maintain/wordsstock
  schedule: every 24 hours

- description: hourly UserProfile maintainer
  url: /maintain/userprofile
  schedule: every 1 hours

descriptionはDashboardに表示されるその処理の説明です。自由に書くことができます。
urlはcronがアクセスするurlです。
scheduleはcronを実行する間隔ということになります。これは書式が決まっています。

上のファイルですと24時間に1回'/maintain/wordsstock'に、1時間に1回'/maintain/userprofile'にcronがGETリクエストを送ることになります。

cronがリクエストを送るurlはブラウザからアクセスできる通常のurlと全く同じものなのでapp.yamlでもcron用に設定しておく必要があります。
スコアアタックしりとりの場合は下のように設定しています。

application: scoreattackshiritori
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /image
  static_dir: image

- url: /js
  static_dir: js

- url: /stylesheets
  static_dir: stylesheets
  
- url: /maintain/wordsstock
  script: maintain.app
  login: admin

- url: /maintain/userprofile
  script: maintain.app
  login: admin

- url: /.*
  script: main.app


libraries:
- name: jinja2
  version: latest

cronの作業内容を記述したファイルはゲーム用のファイルとは別にしてmaintain.pyとしているためscript指定はmaintain.appとなっています。
cron用のurlは通常のブラウザからでもアクセスできる普通のurlなので

  login: admin

を指定しておくことで予期せず第三者がこのurlにアクセスして処理が実行されることを防止できます。cronはAdministrator権限で実行されるためこの記述があっても動作できます。

maintain.pyの中身は下のようになっています。(WordsStockは単語に関する情報を保存するエンティティのクラス、UseProfileはプレイ中のデータを保存するそれです)

# coding: utf-8

import webapp2
import datetime
from google.appengine.ext import db
from main import WordsStock, UserProfile

# WORDS_LIFE_DAYSはWordsStockエンティティの寿命
# 単位は日
WORDS_LIFE_DAYS = 30
# USERPROFILE_LIFE_HOURSはUserProfileエンティティの寿命
# 単位は時間
USERPROFILE_LIFE_HOURS = 1


class MaintainDB(webapp2.RequestHandler):
    @staticmethod
    def _deleteDB(target):
        if target == WordsStock:
            lifetime = datetime.timedelta(days=WORDS_LIFE_DAYS)
            indexname = 'stockdate'
        elif target == UserProfile:
            lifetime = datetime.timedelta(hours=USERPROFILE_LIFE_HOURS)
            indexname = 'reg_date'
        else:
            return

        threshold = datetime.datetime.now() - lifetime

        results = target.all().filter(indexname + ' <', threshold)

        db.delete(results)


class MaintainWordsStock(MaintainDB):
    def get(self):
        self._deleteDB(WordsStock)


class MaintainUserProfile(MaintainDB):
    def get(self):
        self._deleteDB(UserProfile)

app = webapp2.WSGIApplication([('/maintain/wordsstock', MaintainWordsStock),
                               ('/maintain/userprofile', MaintainUserProfile)]
                              )