ラベル Google App Engine の投稿を表示しています。 すべての投稿を表示
ラベル Google App Engine の投稿を表示しています。 すべての投稿を表示

2013-09-16

Google App Engine から backlog.jp の API にアクセスする

Google App Engine から backlog.jp の API にアクセスしようとしたら、何やらエラーが出ました。 libbacklog は xmlrpc を使っていて、xmlrpc は socket を使っていて、Google App Engine では無料枠では socket 使えないのですよね。ちょっとしたツールを作ることが目的だったので、まずはその場しのぎすることにしました。Making XML-RPC calls from a Google App Engine application をちょっと変えただけです。

結局、このツールは使う必要がなくなったのですが、もしものときのために書き置きしておきます。

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日目

Python 温泉 2012-10 の1日目 は、Google App Engine の応答が悪くてふて寝で終わったわけですが、午前3時ごろに監視ツールから「正常に戻りました」メールが届いて起床しました。

というわけで、意地になっているチューニングを継続しました。データの流れは、

フロントエンド → タスクキュー → バックエンドインスタンス

になっていました。フロントエンドは自動スケールで台数がいくらでも多くなります。タスクキューとバックエンドインスタンスは 1:1 対応していて、 lease_tasks() している最中に、フロントエンドが add() するのに時間がかかるように見えます。

てなわけで、タスクキュー数とバックエンドインスタンス数 を N:1 にしてみたら、ちょっと応答がよくなってる感じもありつつ、しかし、あんまり優位な差ではないなあ、というところです。ひととおりの変更をしたので、月曜にちゃんと負荷かけてみます。

細々とした修正をしました。月曜に備えて、明日はゆっくりしようと思っています。リファクタリングとか。

pyspa 2012-10 1日目

本日から Python 温泉 2012-10 に参加しています。当初はだらだらと「いつかやる」リストに入っていることをやろうと考えていました。が、仕事で作っているプロダクトの性能要件が、昨日の夜に上がってしまったので、そのあたりをやろうとしています。まあ、なんとかなると思うので、やらなくてもいいんですが、ちょっと意地になっています。

Google App Engine で、アンケート集計をするようなサービスを作っています。アンケートへの回答が秒間5000 くらいです。一瞬だけであればさばけますが、これが長時間続くとキツイなぁというところです。memcache に置ければいいのですが、誰が回答したかをあとで取り出す必要があるので、揮発する memcache には置かない方針です。ちょっと意地になっています。

で、モヒカンたちに相談したところ...
  • 無理だろ m9
  • そういうことに使うなよ m9
  • GAE のキューは datastore なんだから遅ぇよ m9
  • いくらインフラ弱いっつっても、そろそろ潮時だろ m9
と...。だんだん、意地になってきました。

ボトルネックは backends のインスタンスが、 pull キューから lease_tasks()/delete_tasks() するとき、add() にすごく時間がかかることです。数秒かかります。ここをなんとか工夫したいなぁと思っていたのですが...



Python 不安定になっとるやんけ! というわけで、今日は寝る。

2012-07-18

データモデルが変わるときのデプロイ

運用中のウェブサービスで、保存してあるデータのスキーマを変えたいときがあります。このとき、プログラムのコードとデータベースの間で互換性が保ちつつ、双方を更新する必要があります。Google App Engine を使ったサービスでのやったことのメモです。



プロパティの増減

最初、こんなデータがあったとします。

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 のデプロイ

Google App Engine の backends のデプロイで困りました。
$ appcfg.py update .
が成功するのに、変更した挙動が backends サーバに反映されていないように見えました。そして、実際に反映されていません。
$ appcfg.py backbends . update
のように、backends コマンドに update アクションをつけて実行します。backends.yaml の設定を反映させるだけに使うと思っていたんだけど、コードのデプロイをする役目もあります。

2012-05-31

App Engine の NDB に StringListProperty がないっ

Google App Engine で新しくアプリをつくることになったので、調子にのって ndb を使おうとして、いきなり泣きを見ました。
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」を訳しました。


記事は Java 前提で、私は Python で Google App Engine を使っているのですが、それでも面白いなと思ったので、有効な方法は使っていこうとしています。ついでなので翻訳したものを残しておくことにしました。




軽量な査定用フィードバックサービスを提供している Small Improvements が、Performance issues on GAE, and how we resolved them という素晴らしい記事を書いている。どのようにして、ほとんどのリクエストを 300ms から 800ms でさばき、memcache が効いていなくても2秒、速いページなら150ms 程度でさばけるようになったかの事例になっている。全体として、ものすごく速くしているわけではないけれど、PaaS として GAE が気に入っているなら、検討すべき内容だろう。

パフォーマンスが悪いときに、できることが限られているというのが、PaaS のやっかいなところだ。でも、Small Improvements の人たちは賢くかつ辛抱強く改良をして、詳細な方法とその結果を提供してくれている。アドバイスはGAE に特化したいるけれど、別の状況にも当てはまるだろう。以下に、彼らが実施した、パフォーマンスの小さな向上(small improvemens) の15の方法をあげる。

  1. App Engine が遅い日もある、と知る。 App Engine はパフォーマンスが悪い日がある。設計段階で、潜在的にレイテンシが変化することを考慮しておく必要がある。常にベストな状況であることを想定しないこと。
  2. クエリよりも GET する。 Datastore から、ID で直接取得するのは 250ms 。クエリするのには 800ms かかる。
  3. GET のバッチと、スレッドローカルキャッシュを組み合わせる。キーごとに個別にGETせず、ひとつのバッチにまとめて GETする。結果を HashMap に保存しておいてレンダリングに使う。モジュール化したアプリケーションでは、内部状態の小さな断片を、引き回すのは避けたいだろうから、ThreadLocal に置いておき、いつでも各モジュールからアクセスできるようにする。
  4. クエリの結果は Memcache に保存する。 appstats ツールを使ってどのクエリが、キャッシュするに値するほど使われているからを見つけ出す。
  5. ログイン前にmemcache を温める。 ユーザが完全にログインするより前に Ajax を発行して、データをキャッシュしておく。
  6. ログイン後に memcache を温める。 ユーザがログインしたら、次に読み出しそうな5ページをキャッシュしておく。
  7. 救いようがないほど遅いときには、つなぎのページを表示する。 最初のクエリの時間を測り、もしも遅ければより速いページへ遷移させる。そのページが閲覧されている間に、元のページをキャッシュする。
  8. memcache を使えないといは、非同期に読み出すことで、行儀よくしょぼくする。クエリが遅いなら、その場では全情報をレンダリングしない。クエリが終わったところから順番に描画する。
  9. アクティブでないときにも再キャッシュ。ユーザがしばらくアクティブでない状態が続いたら、キャッシュが失効しそうなデータを再キャッシュするようなリクエストを、ブラウザから飛ばす。こうすればユーザが戻ってきたときにキャッシュに残っている。
  10. データを非正規化する。「このマネージャの部下は誰か」のような 300ms かかるようなクエリは、結果を予め保存しておき、人同士の関係が変わったときに再計算する。こうすれば GET で結果を取得できるので、速い。
  11. JAR を使う。 これ最強。何千ものクラスがあったら、ロードするのに 400ms はかかる、これはディスクアクセスがあるからだ。すべてのクラスを JAR に入れておけば、ロード時間が飛躍的に向上する。
  12. ウォームアップリクエスト。 できるかぎり GAE の warmup 機能を使う。コードパスを吟味して memcache にデータを書き、主な UI を描画し、鍵(キー)となるクエリを発行し、ログインをシミュレートし、時刻の計算をする。こういう処理は VM とデータをウォームアップする。
  13. 書込は後回しにする。 時間がかかったり、たくさんのクエリを発行した後は、書込処理をタスクキューにいれる。
  14. 非同期でメールする。 メール送信はタスクキューから行う。
  15. 非同期でクエリする。複数のクエリを並列に実行する。

2011-10-13

@tokibito さんの TestBedTestCase をいじる

@tokibito さんが、Google App Engine のスタブを使う TestCase クラスを紹介しています。私の仕事でも使っています。

サブクラスを作ったときに、TestBedTestCase の setUp と tearDown を明示的に呼び出さないといけないのがちょっと面倒です。

class MyTestCase(TestBedTestCase):
    def setUp(self):
        super(MyTestCase, class).setUp()
        # … 準備 ...

    def tearDown(self):
        # … 片付け …
        super(MyTestCase, class).tearDown()

Pythonic なので、これはこれで別に問題ないんだろうけど、これ結構めんどくさい。あと、nose 使えば、モジュールごとに setUp を定義しておくと、こういうのもやってくれるはずだけど、老害じじいは nose とか分かんないので、とりあえずスルー。

という訳で、ベースクラスの setUp と tearDown を自動的に呼び出すようにして使っています。setUp の呼び出し順は、基底クラスの setUp の後で、派生クラスの setUp。tearDown は逆です。それぞれ、C++ なんかのコンストラクタとデストラクタと同じにしました。

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 さんのコード

TestBedTestCase のメタクラスを直接指定していないのは、testbed の setUp よりも「前に」何かしたいときと、「後に」何かしたいときがあると思ったからです(でも今のところない…)。

class MyTestCase(TestBedTestCase, MyBaseTestCase):
    def setUp(self):
        ...

と定義すると、TestBedTestCase.setUp, MyBase.setUp, MyTestCase.setUp の順に呼び出されます。

TestBedTestCase のsetUp と tearDown が呼び出されるのが明示的じゃない気がしますが、そこの明示性は諦めました。

やっぱ nose かなぁ…。

2011-09-24

Google App Engine の backends インスタンスをデータベース的に使おうとしてみた

ウィルスとかフィッシングとかにやられる奴ってなんなの? ひっかかる努力をしないと、ひっかからないだろあんなもん、と思っていた時期が私にもありました。まんまとフィッシングに引っかかった情弱です。

現実を見たくなかったので、Google App Engine の backends インスタンスを、DB サーバ的に使えないかなぁと、試してみました。単純なケースでは、パフォーマンス的にも、コスト的にもあまりメリットがないです。

ソースコードは https://github.com/torufurukawa/backendtest に置いてあります。

コード

backends.yaml
backends:
- name: memdb
  start: memdb.py
「memdb」という名前で識別するインスタンスで、起動時には memdb.py を見るように設定します。

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 インスタンスが起動するときに呼ばれる /_ah/start に対してハンドラを設定します。ここでは動作確認用に、DATA 辞書をちょっといじっています。

memdb モジュールの DATA 辞書を、 Key-Value ストアとして使うことにします。

まずデータを書き込むとき用のハンドラ群。

main.py (抜粋)

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


外部から、通常のインスタンス上 の BackendWriteHandler の get が呼ばれます。ランダムに作ったキーと定数値を指定して、memdb インスタンスの /memdb/set/<key>/<value> を GET します。

memdb インスタンスのハンドラが、MemdbSethandler です。モジュールグローバルの DATA 辞書を、key と value で更新します。これで memdb 上の値の更新ができます。

続いて、呼び出しです。main.py から抜粋。

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)
外部からアプリの通常のインスタンスへは、BackendReadHandler が呼ばれます。memdb インスタンスに対して /memdb/get/<key> を呼び出します。

memdb のハンドラは MemdbGetHandler で、DATA 辞書から値を取り出して返す、というものです。

実行してみた

Datastore、Memcache、backends で読み書きをしてみました。きちんとやってなくてさーせん。なんどかブラウザからちょこちょこ URL にアクセスして、安定していたあたりの10件の平均時間 [ミリ秒] と標準偏差です。

Storage     Write     Read
Datastore     14±1     4±0
Mecache     2±0     2±0
Backend     49±83     87±88

かなり遅いのと、所要時間がえらく不確定です。アクセスの感覚もゆっくりとったので、Datastore よりも早いんじゃないのかくらいの期待をしていたのですが、ぜんぜんです。

コストのほうを計算してみました。

100ミリ秒(0.1秒)ごとに読み書きのいずれかが発生するとしましょう。楽観的に考えて、スパイクはなし、と。
そうすると、1ヶ月での読み書き回数は、
0.1 [秒] x 3600 [秒/時間] x 24 [時間/日] x 30 [日/月] = 25,920,000 [回/月]

デフォルトの B2 クラスのインスタンスの1ヶ月の使用料は、
0.16 [ドル/時間] x 24 [時間/日] x 30 [日/月] = 115 [ドル/月]
です。

同じ回数のアクセスを、Datastore に対して行うと、
書き込みのみ費用 = 0.01 [ドル/10k回] x 25,920,000 [回/月] = 26 [ドル/月]
読み込みのみ費用 = 0.07 [ドル/10k回] x 25,920,000 [回/月] = 181 [ドル/月]

読み込みだけ発生すれば、backends のほうが安いです。が、実際にはそんなわけないですしねぇ。うーむ。

とは言え…

Datastore から複数のエンティティを取得すると、その分読み込み回数のカウントが増えます。アプリによっては、それを backendsではうまくハンドルできるかも知れません。memcache のデータは揮発する可能性がありますが、backends はメモリに気をつけていれば datastore ほどではないにしてもなんちゃって永続化できます(そのあたりの監視や処理にCPU時間が必要でしょうけど)。

なので、この使い方がすなわちダメではないんでしょうが、個人的にもっとあからさまな速度差や、コスト差が出るのかなぁと妄想していたので、少しざんねんです。今日は残念な日なのでしょう。

2011-09-11

Google App Engine memcache cas (compare and set)

Google App Engine SDK 1.5.3 で memcache cas (Compare and Set) 操作ができるようにななりました。今更ですが。 GvR のブログでも丁寧に解説されています。

で、どんなときに使おうっかなぁと思うわけです。データが揮発してよい、クライアント間のデータ共有の遅延を遅らせたい、整合性は保つ、みたいなときに意味があるのでしょうか。クライアントが自身のID を渡してサービスを呼び出し、最後に呼び出して一定時間以内のクライアント数を数える、みたいな機能とか。


と、思ったんですが、cas のリトライに失敗したときにどうしましょうっかねぇ。どっか、別のエラーの回数を incr() しといて、エラー率とアクセス数を関連付けるとかでしょうか。むずい。


2011-09-11 22:43 追記: シャーディングで、ひとつのキーに集中しないようにする、というのは大前提で。

2011-09-04

Eclipse と virtualenv で App Engine アプリを開発する設定


Emacs で Google App Engine 触っているのですが、もういい加減に IDE 使おうと思いました。Wing IDE、Komodo IDE、PyCharm あたりを考えたのですが、(ここでビール3杯をはさんだのでロジックは忘れたが) Eclipse だろうとなりました。で、ちょっとはまったので、メモです。

前提

  • Mac OS X (Lion)
  • Google App Engine Launcher を使う。
  • プロジェクトごとに virtualenv 環境を用意する。
インストール手順

  1.  (したければ)Python をインストールする。(homebrew の 2.7)
  2. App Engine Launcher をインストールする。(1.5.3)
  3. Eclipse をインストールする。(3.7.0)
  4. PyDev をインストールする。(2.2.2.2011082312)

プロジェクト作成手順

  1. [File]-[New]-[Other…]- [PyDev の下の PyDev Google App engine Project] を選択して [Next]
  2. [User default] のチェックをはずす
  3. Directory にソースコードがあるディレクトリを指定 e.g. /Users/bucho/show
  4. Interpreter が空欄なので「Click here to configure an interpreter not listed」 をクリック
  5. [New] をクリックして適当な名前にする。e.g. python2.5@show
  6. PYTHONPATH に追加すべきディレクトリが出てきます。デフォルトでチェックがはいっている項目に加えて、virtualenv の Python がもともと参照している /lib ディレクトリにもチェックします。例えば homebrew でインストールした Python 2.7 なら、/user/local/Celler/python/2.7/lib/python2.7 とか。(2011-09-04 追記)
    いろいろと PYTHONPATH に追加すべきディレクトリが出てくるけどデフォルトでOK。
  7. ターミナルで、echo $PATH した値をコピー
  8. [Environment] タブをクリック、New ボタンをクリックして、Name に「PATH」、Value 欄にさっきのをペースト。(私の環境で必要だったのは /usr/local/bin です。めんどいので全部ペーストしました)。
  9. [Finish]ボタンをクリック
  10. App engine の場所を聞いてくるので「/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine」 を指定する。
  11.  [Django0.97] にチェック。
補完が...

これでいちおう動くんだけど、標準ライブラリの import 文なんかを、resolve できない。たとえば import unittest だと、下みたいになります。



コードは動くけど、補完してくれなくて、それじゃあ何のために IDE 使ってんだよって話です(Emacs でも補完とかできるんでしょうけど、.emacs とか無理っす)。

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

これが正しいやりかたなのか、まったく分からないわけですが、とりあえずこれで様子見です。