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)] )