2013-09-16
Google App Engine から backlog.jp の API にアクセスする
結局、このツールは使う必要がなくなったのですが、もしものときのために書き置きしておきます。
2013-05-08
Go の型は First-class ではない
Go の型は First-class ではない、ということにゴールデンウィーク最終日に気づき、悶々としています。(何も Go が悪いわけではない)
ことの発端は、以下の様な関数を定義したところから始まります。
func getName(x interface{}) string {
return reflect.TypeOf(x).Elem().Name()
}
この関数に、任意の型のポインタをわたすと、型の名前を取得できます。けど、ポインタを渡さなければならないのですね。
var foo *Foo name := getName(foo)
foo は使わないのに! 使わないというのはウソですね。けど、ちょっと違うんですよ。まあこれは私がPython 脳だからであって、別にGoが悪いわけじゃない。
何をしたいかと言うとですね、Google App Engine datastore のクエリの生成をラップしたいのです。標準ライブラリを使うと、以下のように書きます。
c := appengine.NewContext(r)
q := datastore.NewQuery("Foo") // ☜(◉ɷ◉ )
q.Filter("Y =", "xaxtsuxo")
foos := make([]Foo, 0, 10)
keys, err := q.GetAll(c, &foos)
Foo が型が定義されているのに、文字列で "Foo" を渡すのが悔しいわけです。そこはコンパイル時にスペルミスをひっかけて欲しいわけですよ。ストアされているデータと、Foo の定義が違うと、それはそれで実行時エラーになるんだけど。なので、
func createQuery(AnyType) *datastore.Query
のような関数を定義したいな、と思ったのですね。ところが、型は渡せないのです。以下のような方法に落ち着きます。
func createQuery(x interface{}) *datastore.Query {
kind := reflect.TypeOf(x).Elem().Name()
q := datastore.NewQuery(kind)
return q
}
…
var foo *Foo
q := createQuery(foo).Filter("Y =", "xaxtsuxo")
var foos := make([]Foo, 0, 10)
keys, err := q.GetAll(c, &foos)
あぁ foo 使わないのに。いや、 配列の foos があるんで、そっち使えよって話なんだけど。なんなら GetAll せずに、foo を使ってイテレートするのが筋なのかも知れません。
ああ、そんなこと言い出したら、Filter にだって文字列を渡してるぞ。そう考えると GAE/Python の NDB はよくできてるなぁ、あのライブラリ作った人は Python のことよく分かってるなぁ。薄いラッパにしたかったんだけど、やっぱり厚くなってしまうのかなぁ。PropertyList とか PropertyLoadSaver をうまく使ったらよいのか? ぎゃぁ。というわけで連休が終わってしまったので、ペースダウンしていきます。
2013-05-06
Go の struct に任意の名前のフィールドがあるか調べる
静的型付け言語たる Go ですが、実行時にごにょごにょすることを reflection と言うらしく、reflect パッケージが標準で入っています。
v := Foo{}
structField, found := reflect.TypeOf(v).FieldByName("x")
Foo 型に x フィールドが存在すれば、found に true が入ります。http://play.golang.org/p/F9TY4RcBEC
値とポインタは異なった型なので、もとの struct にたどり着くまでの道筋が異なります。 http://play.golang.org/p/_V19gURisU
v := Foo{}
typeFromValue := reflect.TypeOf(v)
p := new(Foo)
typeFromPointer := reflect.TypeOf(p).Elem()
それから struct 定義のとき、各フィールドにタグをつけられますが、これも reflect パッケージを使って取得できます。http://play.golang.org/p/K__zcCZ7Nd
type Foo struct {
int
x string `wozozo:"show" hoge:"fuga"`
}
func main() {
v := Foo{}
t := reflect.TypeOf(v)
field, _ := t.FieldByName("x")
fmt.Println(field.Tag) // => `wozozo:"show" hoge:"fuga"`
fmt.Println(field.Tag.Get("wozozo")) // => show
}
フィールドにつけるタグは文字列ならなんでもいいのですが、key:"value" のフォーマットをスペース区切りにしたものを定義すると、structTag.Get(key) で value を取得できます。Google App Engine では、エンティティのフィールド定義に struct を書くときに、インデックスしてくれるな、とか、struct には含むけどエンティティとして保存するな、のような指示に使われています。
2012-10-27
pyspa 2012-10 2日目
というわけで、意地になっているチューニングを継続しました。データの流れは、
フロントエンド → タスクキュー → バックエンドインスタンス
になっていました。フロントエンドは自動スケールで台数がいくらでも多くなります。タスクキューとバックエンドインスタンスは 1:1 対応していて、 lease_tasks() している最中に、フロントエンドが add() するのに時間がかかるように見えます。
てなわけで、タスクキュー数とバックエンドインスタンス数 を N:1 にしてみたら、ちょっと応答がよくなってる感じもありつつ、しかし、あんまり優位な差ではないなあ、というところです。ひととおりの変更をしたので、月曜にちゃんと負荷かけてみます。
細々とした修正をしました。月曜に備えて、明日はゆっくりしようと思っています。リファクタリングとか。
pyspa 2012-10 1日目
- 無理だろ m9
- そういうことに使うなよ m9
- GAE のキューは datastore なんだから遅ぇよ m9
- いくらインフラ弱いっつっても、そろそろ潮時だろ m9
2012-07-18
データモデルが変わるときのデプロイ
プロパティの増減
最初、こんなデータがあったとします。
from google.appengine.ext import db
class User(db.Model):
name = db.StringProperty()
これでたくさんのデータが保存されている状態になったとしましょう。ここで、プロパティが増えます。
class User(db.Model):
name = db.StringProperty()
birthday = db.DateProperty()
Google App Engine のデータストアには ALTER TABLE みたいなのがありません。が、こういう場合はデータストアに何もしなくていいです。birthday を指定せずに保存されたエンティティは birtyday に None が入っています。
プロパティが減る場合は、単純にモデルの定義からプロパティを消せばいいでしょう。データストアには残っているけれど、あっても害がないから。けど、後々、違う型で復活したりすると、よろしくない気がします。
クエリが増える
プロパティを変更したととき、あたらしいクエリを発行することになった場合には、 (1) インデックスの更新してから、(2) コードをデプロイします。
これまで使っていなかったけど、新たに以下のようなクエリをするようになったとしましょう。
User.all().query(birthday<xxx).query(name<yyy)
dev_appserver をローカルで動かして、このコードを一度でも実行すると index.yaml が更新されます。以下のコマンドで App Engine のインデックスを更新します。
appcfg.py update_indexes .
ダッシュボードの Datastore Indexing のページを見ると、Building… と書かれている箇所があります。何度かリロードして、Ready になってから、appcfg.py update します。先にインデックスを更新しないと、アプリケーションコードがクエリしようとしたとき、「インデックスができてねーよ」エラーが出ます。
より複雑な変更
ほんとに困るのは、プロパティの型が変わってしまうとか、複数のプロパティでひとつの状態を表したくなるとかいう場合です。
class User(db.Model):
name = db.StringProperty()
birthday = db.StringProperty() # DateProperty から StringProperty へ変更
このコードをデプロイして user.birthday にアクセスしたとき、以前の DateProperty で保存されていると、ここでエラーが出ます。だからといって、先にデータを書き換えてしまうと、現行コードでエラーが出ます。
書籍「継続的デリバリー」は、新旧どちらスキーマでも動くようにアプリケーションを変更したあと、データベースのスキーマを変更する、という方針を提案しています。 Google App Engine のデータストアであれば、新旧どちらのプロパティの型であってもいけるようなモデルに変更する、ということになります。
class User(db.Model):
name = db.StringProperty()
date_birthday = db.DateProperty(name='birthday')
str_birthday = db.StringProperty(name='str_birthday')
def get_birthday(self):
if self.date_birthday:
return self.date_birthday.strftime('%Y-%m-%d')
else:
return self.str_birthday
def set_birthday(self, val):
if isinstance(val, date):
self.str_birthday = '%Y-%m-%d' % (val.year, val.month, val.day)
elif isinstance(val, str):
self.str_birthday = val
else:
raise TypeError
self.date_birthday = None
birthday = property(get_birthday, set_birthday)
ハンガリアンっぽくてキモいです。あと文字列に変換するところは、関数に切り出したほうがいいですね。で、これでデプロイします。データの個数が少なければ remote_api で、多ければ mapreduce のライブラリを使って、プロパティの型を変更します。すべてのデータが新しい型に対応したら、不要なコードを取り除いてデプロイします。
class User(db.Model):
name = db.StringProperty()
birthday = db.DateProperty(name='str_birthday')
と、偉そうに書いたものの、最後の書き換えをせずに残っている箇所もあります。
2012-06-01
Google App Engine の backends のデプロイ
$ appcfg.py update .が成功するのに、変更した挙動が backends サーバに反映されていないように見えました。そして、実際に反映されていません。
$ appcfg.py backbends . updateのように、backends コマンドに update アクションをつけて実行します。backends.yaml の設定を反映させるだけに使うと思っていたんだけど、コードのデプロイをする役目もあります。
2012-05-31
App Engine の NDB に StringListProperty がないっ
class Foo(db.Model):
def tags = db.StringListProperty()
というような、文字列配列を持つような StringListProperty が、ndb にはない! 焦りつつぐぐったら、すぐに見つかりました。repeated というキーワード引数つきで定義すればよいみたいです。class Foo(ndb.Model):
def tags = db.StringProperty(repeated=True)
ある値を含むエンティティをクエリするには、以下のようにします。Foo.query(Foo.tags == 'python')複数の値のうち、いずれかを持つエンティティをクエリするには、以下のようにします。
Foo.query(Foo.tags.IN(['python','ruby'])) Foo.query(ndb.query.OR(Foo.tags == 'python', Foo.tags == 'ruby'))
参考:
2012-01-07
Google App Engine の開発サーバで pdb.set_trace() を使う
Google App Engine で開発していて、標準デバッガの pdb を使おうとして困ったのでその顛末のメモです。
import pdb; pdb.set_trace()
と書いても、期待通りに動作しません。stdin と stdout の向き先が変わっているので、画面への表示とキーボードからの入力ができないのだと思います。
有用なブログのコメントを見つけましたので、そのままパクり。
gaedb.py というファイルを作っておきます。
# gaedb.py
def set_trace():
import pdb, sys
debugger = pdb.Pdb(stdin=sys.__stdin__,
stdout=sys.__stdout__)
debugger.set_trace(sys._getframe().f_back)
で、アプリケーションコード内で、
import gaedb; gaedb.set_trace()
と書けば、期待通りの pdb.set_trace() 動作します。ちゃんちゃん。
2011-12-31
翻訳: Google App Engine で、アプリケーションの応答をよくする 15 の方法
大規模ウェブサービスのプラクティスを紹介している、High Scalability の記事「15 Ways To Make Your Application Feel More Responsive Under Google App Engine」を訳しました。
- App Engine が遅い日もある、と知る。 App Engine はパフォーマンスが悪い日がある。設計段階で、潜在的にレイテンシが変化することを考慮しておく必要がある。常にベストな状況であることを想定しないこと。
- クエリよりも GET する。 Datastore から、ID で直接取得するのは 250ms 。クエリするのには 800ms かかる。
- GET のバッチと、スレッドローカルキャッシュを組み合わせる。キーごとに個別にGETせず、ひとつのバッチにまとめて GETする。結果を HashMap に保存しておいてレンダリングに使う。モジュール化したアプリケーションでは、内部状態の小さな断片を、引き回すのは避けたいだろうから、ThreadLocal に置いておき、いつでも各モジュールからアクセスできるようにする。
- クエリの結果は Memcache に保存する。 appstats ツールを使ってどのクエリが、キャッシュするに値するほど使われているからを見つけ出す。
- ログイン前にmemcache を温める。 ユーザが完全にログインするより前に Ajax を発行して、データをキャッシュしておく。
- ログイン後に memcache を温める。 ユーザがログインしたら、次に読み出しそうな5ページをキャッシュしておく。
- 救いようがないほど遅いときには、つなぎのページを表示する。 最初のクエリの時間を測り、もしも遅ければより速いページへ遷移させる。そのページが閲覧されている間に、元のページをキャッシュする。
- memcache を使えないといは、非同期に読み出すことで、行儀よくしょぼくする。クエリが遅いなら、その場では全情報をレンダリングしない。クエリが終わったところから順番に描画する。
- アクティブでないときにも再キャッシュ。ユーザがしばらくアクティブでない状態が続いたら、キャッシュが失効しそうなデータを再キャッシュするようなリクエストを、ブラウザから飛ばす。こうすればユーザが戻ってきたときにキャッシュに残っている。
- データを非正規化する。「このマネージャの部下は誰か」のような 300ms かかるようなクエリは、結果を予め保存しておき、人同士の関係が変わったときに再計算する。こうすれば GET で結果を取得できるので、速い。
- JAR を使う。 これ最強。何千ものクラスがあったら、ロードするのに 400ms はかかる、これはディスクアクセスがあるからだ。すべてのクラスを JAR に入れておけば、ロード時間が飛躍的に向上する。
- ウォームアップリクエスト。 できるかぎり GAE の warmup 機能を使う。コードパスを吟味して memcache にデータを書き、主な UI を描画し、鍵(キー)となるクエリを発行し、ログインをシミュレートし、時刻の計算をする。こういう処理は VM とデータをウォームアップする。
- 書込は後回しにする。 時間がかかったり、たくさんのクエリを発行した後は、書込処理をタスクキューにいれる。
- 非同期でメールする。 メール送信はタスクキューから行う。
- 非同期でクエリする。複数のクエリを並列に実行する。
2011-10-13
@tokibito さんの TestBedTestCase をいじる
class MyTestCase(TestBedTestCase):
def setUp(self):
super(MyTestCase, class).setUp()
# … 準備 ...
def tearDown(self):
# … 片付け …
super(MyTestCase, class).tearDown()
class CascadingTestCaseMeta(type):
"""Metaclass for TestCase class
This metaclass make setUp method calls all setUp methods
in base classes and then calls defined setUp method.
Likewise tearDown but in opposite order.
"""
def __init__(cls, name, bases, ns):
for method_name, reverse in [('setUp',False), ('tearDown', True)]:
setattr(cls, method_name,
cls._create_method(method_name, bases, ns, reverse=False))
@classmethod
def _create_method(self, method_name, bases, ns, reverse=True):
"""return a method that calls all methods with given
name in class hierarchy
"""
# create method sequence in parent and current classes
methods = [getattr(base, method_name, lambda self: None)
for base in bases]
methods.append(ns.get(method_name, lambda self: None))
# reverse order if necessary
if reverse:
methods.reverse()
# define method to call all methods
def call_methods(self):
for method in methods:
method(self)
# return the caller method
return call_methods
class CascadingTestCase(unittest.TestCase):
__metaclass__ = CascadingTestCaseMeta
class TestBedTestCase(CascadingTestCase):
# 以下、@tokibito さんのコード
class MyTestCase(TestBedTestCase, MyBaseTestCase):
def setUp(self):
...
2011-09-24
Google App Engine の backends インスタンスをデータベース的に使おうとしてみた
backends:
- name: memdb
start: memdb.py
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
DATA = {}
class InitializeHandler(webapp.RequestHandler):
def get(self):
global DATA
DATA['foo'] = 'foo'
application = webapp.WSGIApplication([('/_ah/start', InitializeHandler)], debug=True)
util.run_wsgi_app(application)
MEMDB_BACKEND_ID='memdb'
MEMDB_HOSTNAME=backends.get_hostname(MEMDB_BACKEND_ID)
DATA='spam'*10
def stop_watch(op_name):
"""ロギングと共通レスポンス用のデコレータ。
関数を実行して、実行時間をログとレスポンスに書き出す。
"""
def outer(func):
def wrapper(self):
start_at=time.time()
func(self)
end_at=time.time()
log='[%s] %s'%(op_name,end_at-start_at)
logging.info(log)
self.response.out.write(log)
return wrapper
return outer
class BackendWriteHandler(webapp.RequestHandler):
@stop_watch('backend:write')
def get(self):
hostname=MEMDB_HOSTNAME
response=fetch('http://%s/memdb/set/%s/%s'%(hostname,get_key_name(),DATA))
class MemdbSetHandler(webapp.RequestHandler):
"""/memdb/set/(.+)?/(.+) で呼ばれるハンドラ"""
d ef get(self,key,value):
importmemdb
memdb.DATA[key]=value
self.response.out.write(value)
def get_key_name():
return''.join([random.choice('abcdefghijklmnopqrstuvwzyz')for i inrange(10)])
class BackendReadHandler(webapp.RequestHandler):
@stop_watch('backend:read')
def get(self):
hostname = MEMDB_HOSTNAME
response = fetch('http://%s/memdb/get/%s' % (hostname, get_key_name()))
data = response.content
class MemdbGetHandler(webapp.RequestHandler):
"""/memdb/set/(.+)?/(.+) で呼ばれるハンドラ"""
def get(self, key):
import memdb
value = memdb.DATA.get(key)
self.response.out.write(value)
2011-09-11
Google App Engine memcache cas (compare and set)
2011-09-11 22:43 追記: シャーディングで、ひとつのキーに集中しないようにする、というのは大前提で。
2011-09-04
Eclipse と virtualenv で App Engine アプリを開発する設定
- Mac OS X (Lion)
- Google App Engine Launcher を使う。
- プロジェクトごとに virtualenv 環境を用意する。
- (したければ)Python をインストールする。(homebrew の 2.7)
- App Engine Launcher をインストールする。(1.5.3)
- Eclipse をインストールする。(3.7.0)
- PyDev をインストールする。(2.2.2.2011082312)
- [File]-[New]-[Other…]- [PyDev の下の PyDev Google App engine Project] を選択して [Next]
- [User default] のチェックをはずす
- Directory にソースコードがあるディレクトリを指定 e.g. /Users/bucho/show
- Interpreter が空欄なので「Click here to configure an interpreter not listed」 をクリック
- [New] をクリックして適当な名前にする。e.g. python2.5@show
- PYTHONPATH に追加すべきディレクトリが出てきます。デフォルトでチェックがはいっている項目に加えて、virtualenv の Python がもともと参照している /lib ディレクトリにもチェックします。例えば homebrew でインストールした Python 2.7 なら、/user/local/Celler/python/2.7/lib/python2.7 とか。(2011-09-04 追記)
いろいろと PYTHONPATH に追加すべきディレクトリが出てくるけどデフォルトでOK。 - ターミナルで、echo $PATH した値をコピー
- [Environment] タブをクリック、New ボタンをクリックして、Name に「PATH」、Value 欄にさっきのをペースト。(私の環境で必要だったのは /usr/local/bin です。めんどいので全部ペーストしました)。
- [Finish]ボタンをクリック
- App engine の場所を聞いてくるので「/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine」 を指定する。
- [Django0.97] にチェック。

[Eclipse]-[環境設定]-[PyDev]-[Interpreter]-[Python] を選択インタプリタ設定を選択してから、「Forced builtin」を選択。[New] ボタン → 「unittest」と入力する。


