2009年12月16日水曜日

bulkloader.py を使って Google App Engine の本番サーバーから開発サーバーにデータを移す

Google App Engine SDKの開発サーバーのデータストアはtmpディレクトリにデータを保存するため、マシンを再起動するとデータの中身が全部消えてしまいます。毎回テストデータを用意するのが面倒なので、本番サーバーからデータを移してくるための方法を調べてみました。するとbulkloader.pyというユーティリティを使うと、簡単に本番サーバーのデータをダウンロードして保存し後からリストアすることができることがわかりました。ということで早速試してみます。
参考にしたのは以下のサイト。
http://code.google.com/intl/en/appengine/docs/python/tools/uploadingdata.html

※2010/01/31追記:--db_filenameオプションの使い方を掲載しました。


■前準備
app.yamlに以下の設定を追加してから、appcfg.py updateで本番デプロイを行います。
- url: /remote_api
script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py
login: admin
これだけで準備ばっちりです。余談ですが、app-engine-patchを使うと、最初からこの設定がapp.yamlに含まれています。


■使用方法
本番サーバーからローカルにデータをダンプするときは以下のコマンドを実行します。
bulkloader.py --dump --kind=<kind> --url=http://<appname>.appspot.com/remote_api --filename=<data-filename>
たとえば私のアプリからhon_heroという名前のModelのデータをダンプしたいときには、
bulkloader.py --dump --kind=hon_hero --url=http://akisutesama.appspot.com/remote_api --filename=hon_hero.dump
などとするとうまくいきます。

ダンプしたデータを本番サーバーにリストアするときは以下のコマンドを実行します。
bulkloader.py --restore --kind=<kind> --url=http://<appname>.appspot.com/remote_api --filename=<data-filename>
--dumpが--restoreになっただけです。本番障害でデータが失われたときにリストアするためのコマンドなので、実行すると本番サーバーのデータがすべてダンプした地点のデータに置き換えられます。ご利用の際には細心の注意を!

ダンプしたデータをローカルサーバーに入れるときは以下のコマンドを実行します。
bulkloader.py --restore --kind=<kind> --url=http://localhost:8080/remote_api --filename=<data-filename> --app_id=<appname>
本番だけではなくてローカルサーバーにもリストアができます。ローカルサーバーにリストアする際の注意点として、
  • dev_appserver.pyでアプリケーションが動いている必要があります。
  • --urlオプションをローカルホストに書き直す必要があります。
  • --app_idオプションで、自分のアプリケーションのapp_idを指定してやる必要があります。
これでローカルテストがだいぶ楽になりました。

同様にして、本番からデータをダンプしてローカルに入れるだけでなく、ローカルからデータをダンプして本番に入れることもできます。新しく実装した箇所に最初からデータを入れたい場合などに使えると思います。


■log, progress, resultを賢く使う
bulkloader.pyを実行すると、デフォルトでは以下のような名前のファイルが自動的に生成されると思います。
bulkloader-log-20091211_145622.log
bulkloader-progress-20091211_145622.sql3
bulkloader-result-20091211_145622.sql3
これらはそれぞれ「ログファイル」「現在どのエンティティまでダンプされたかを格納するデータベース」「実際にダンプした内容を格納するデータベース」となっており、上手く使えば2回目以降のダンプ実行時間を劇的に短くすることができます。

ログファイルを指定する際には--log_fileオプションを、データベースを指定する際には--db_filenameと--result_db_filenameを、それぞれ指定してください。たとえば、以下のようなシェルスクリプトを用意しておくと便利です。
#!/bin/sh

# 前回の実行結果が残っているとエラーになる(上書きしてくれない)ので、まずいったん消す
if [ -e hon_hero.dump ]; then
rm hon_hero.dump
fi
# bulkloader.pyを実行する
# db_filenameとresult_db_filenameは一緒のファイルを指定してもかまいません。
# (その場合は、一つのsqlite3データベースに一緒に格納されます)
# db_filenameとresult_db_filenameは、db_fileやresult_db_fileのように短縮して書いても動作するみたいです
bulkloader.py --dump \
--url=http://akisutesama.appspot.com/remote_api \
--kind=hon_hero \
--filename=hon_hero.dump \
--log_file=bulkloader-log-hon_hero.log \
--db_file=bulkloader-progress-hon_hero.sql3 \
--result_db_file=bulkloader-progress-hon_hero.sql3
こうすることで、2回目以降はbulkloader-progress-hon_hero.sql3の中身とリモートのdatastoreの中身を比較して、追加更新のあったもののみをダウンロードし、それ以外はローカルに保存してあるデータベースの中身を利用するので、劇的に処理が速くなります。結果はこちら。
[INFO    ] Connecting to akisutesama.appspot.com/remote_api

[INFO ] Have 2610 entities, 2610 previously transferred
[INFO ] 2610 entities (974 bytes) transferred in 0.4 seconds
1回目のロードには130秒かかったのですが、2回目はすべてローカルに保存されているデータを使ったので、なんと0.4秒で済んでいます。

こうしてダンプしたデータをローカルサーバーでリストアするときは、たとえば以下のようなシェルスクリプトを使います。
#!/bin/sh

# --db_fileと--result_db_fileには先ほどのダンプ時に指定したものとは別のファイルが必要になります
# (先ほどのデータベースはappspot.com用として設定されるので、localhostで使用するとエラーになってしまいます)
# --db_fileと--result_db_fileには特別な名前としてskipを指定することができます
# この名前を使用するとデータベースへの書き込み/読み込みを行いません
bulkloader.py --restore \
--url=http://localhost:8000/remote_api \
--app_id=akisutesama \
--kind=hon_hero \
--filename=hon_hero.dump \
--log_file=bulkloader-log-restore.log \
--db_file=skip \
--result_db_file=skip


■注意点
appspot.com以外のサーバー(たとえばlocalhostなど)からデータをdumpしたりrestoreしたりする際には、必ず--app_idオプションを指定する必要があります。これを忘れていて詰まりました><

もし特定の条件を満たすデータのみをダウンロードしたいという場合などは、設定ファイルをPythonで書く必要がありますが、appcfg.py download_dataを使うと良いと思います。

2009年12月13日日曜日

Highcharts.js を使ってみた


最近IDEA*IDEAさんで取り上げられていたHighcharts.jsを試してみましたので、躓いた点などをメモしておきます。


■公式サイト
http://www.highcharts.com/
ダウンロード、更新履歴はこちらから見られます。ライセンスもこちらに記載があります。
個人利用、教育目的の利用、および非営利組織での利用については無料ですが、商用利用の際にはライセンスの購入(1サイト$80、複数サイト$360)が必要になるので注意です。


■公式リファレンス
http://www.highcharts.com/ref/
おそらく全部JSで実装されているのだと思いますが、凄いクオリティ高いです・・・


■実際に導入してみて躓いたポイント
以下、すべてバージョン1.0.2(2009/12/09)での気づきです。
グラフを描画する領域のサイズは、CSSでは定義できない、直接style属性に書く必要がある
たとえばCSSを使って、
<style>#chart_area {width:800px; height:400px;}</style>
<div id="chart_area" ></div>
のようにしても描画時に無視され、高さが0になります。必ず直接style属性を用いて
<div id="chart_area" style="width:800px; height:400px;"></div>
のように書く必要があります。

Zoom機能を使う際、画面のスクロールが発生すると選択領域がバグる
Mac OS X 10.6上のFirefox3.5.6とSafari4.0.4にて再現。他のブラウザは未チェックです。論より証拠、こちらのページを開いて、少し画面全体を下にスクロールしてから、チャートを縦にドラッグしてY軸ズームしようとしてみてください。まぁ、ひどいバグなのですぐに直るとは思いますが。

seriesに与えるデータは、必ずX軸方向の昇順にソートされている必要がある
特にxAxisにdatetimeを指定しているときが危険です。たとえばこのようなデータはNGです。
series: [{
name: 'Plague Rider',
data: [[Date.UTC(2009, 12, 4), 57.4],
[Date.UTC(2009, 12, 3), 57.6],
[Date.UTC(2009, 12, 2), 59.1]]
}]
かならずこのようにソートしなければなりません。
series: [{
name: 'Plague Rider',
data: [[Date.UTC(2009, 12, 2), 59.1],
[Date.UTC(2009, 12, 3), 57.6],
[Date.UTC(2009, 12, 4), 57.4]]
}]
ソートされていないと、チャートにマウスカーソルを合わせてもラベルが表示されなくなります。気づきにくいバグでした。

seriesに与えるデータにnullがあると、0として扱われる
そのため、たとえば12月1日は100、12月2日は110、12月3日はデータが分からないのでnull・・・とすると、12月3日のところだけいきなり0になってグラフが滅茶苦茶になります。回避方法としてはxAxisのtypeをdatetimeにするか、yAxisのminを自分で計算して指定してください。

xAxisのtypeがdatetimeのとき、連続していないデータがあるとsplineは使用できない(lineは使用できる)
原因は不明です。たぶんバグじゃないだろうかと思ってますが、公式のxAxisのtypeがdatetimeなサンプルを見てもsplineではなくてlineを使っているのでひょっとしたら仕様かもしれません。

■まとめ
公開されたのがつい先月の末ということで、まだちょっとBuggyな感じです。また、エラーメッセージがまともに表示されないため、JSファイルのどこでエラーになっているかはわかっても原因が何か分からないなど、デバッグが結構大変です。しかしながらデザイン美麗で機能豊富、カスタマイズも容易と、一通り優れた点は持ち合わせていると思います。PrototypeやjQueryなど他の外部ライブラリに依存しないのも良い点です。$80なら支払ってもよいのでは?


■Google Charts APIとの比較
やはり気になるのはGoogle Charts APIと比べてどちらが使えるかでしょう。ちょっと調べてみました。ただしGoogle Charts APIについては実際に試したことがないのであくまで公式ページのドキュメントを見て気づいた点のみです。間違っていたらごめんなさい。
Highcharts.jsの優れている点
  • 美麗なデザイン
  • ズームや特定データの一時消去、ラベル表示など、対話性に優れたUI
  • 単体のJSファイルなので外部のサーバに依存しない
  • Ajax対応が容易

Google Charts APIの優れている点
  • より多数のグラフに対応(レーダーチャートや地図など)
  • 商用利用でも完全に無料

2009年12月11日金曜日

Django の Template Filter には任意の変数を使用することができる

公式ドキュメントに記載がなかったので、自分用メモ。

たとえばDjangoのフィルターで、
{{ some_value|floatformat:1 }}
としているところを、viewから変数を渡して
{{ some_value|floatformat:floatpoint }}
のように書くことができます。他にも
{{ now "%Y %m %d" }}
{{ now date_format }}
みたいに書くとか。・・・常識?