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-19

半島を出よ探訪

村上龍の「半島を出よ」の舞台あたりに行く機会がありました。えーっと博多のほうですよ。



ホテルから野営地までは、団体出入り口から外に出て四車線の広い道路を渡るのが最短の道筋だ。しかし高麗遠征軍制圧区域に出入りする車はほとんどないので、道路はまるで夜の平壌のように換算としている。狙撃の危険があるわけではないのだが、車の行き来も遮蔽物もまったくない幅三十メートルの道路を横断するのは気持ちが悪かった。チェ・ヒョイルは、ホテルから野営地に戻るときはいつも福岡ドームの正面ゲートのほうから迂回することにしていた。


ホテルから野営地のモデルとなる広場を見た様子。前の道路が、チェ・ヒョイルが横断するのをためらっている道路。右側は、まっすぐ伸びていってて、遮蔽物はまったくない。正面に見える大きな建物が、九州医療センター。

作品では、開発がとまってこの広場は放置されていた、と書かれています。若干手入れされていますが、それほど積極的に使われていないようです。ヒルトンと病院の間のこの空間「どーすんだよ、ここw」みたいな感じになっているのかも知れません。




左のほうは道が右にゆっくり曲っていってきます。チェ・ヒョイルはこちらのほうから、迂回して渡っている。カラフルな建物群がホークスタウンで、物語の中では、高麗遠征軍による広場の制圧後は、営業していない、という設定。


なるほど、というようにチェ・ヒョイルは真剣な顔を崩さずにうなずき、橋を指差して、ずいぶん長い橋ですね、と言った。 [...] こんなに眩しいのにどうしてブラインドを下ろさないのだろうか。チェ・ヒョイルがそのことを不審に思ったとき、突然窓ガラスが割れ、外の空気が流れこんできた。チェ・ヒョイルが思わず身をかがめたとき、缶入りコーヒーに似た黒い金属の筒が前方の床に転がった。

観月橋から、岸の方を見た様子。右側の2階建ての建物に、戦闘劇の現場になったレストランが入っています。窓は南向きです。左側の岸の向こうのほうに、駐車場の車が見えます。

岸にいると、バルコニーに誰かが隠れているかどうかは、「誰かがいるんじゃないのか?」っていうつもりで覗きこまないと見えません。池にかかっている橋まできて、やっと見えます。

海からの風で煙がこちら側に漂ってきて、ホテルの根元の黒い穴が露になり、キム・ヒャンモクは奇妙な感覚にとらわれた。自分のからだが左に傾いていくような感じがしたのだ。視界が右に傾いていくようだった。カタツムリのような形をしたガラス張りの建築物が粉々になり、ガラスの細かな破片が飛び散るのを見て、ホテルが右に倒れているのだと気づいた。ナイフのよう形を保ったままホテルはゆっくりと傾いていき、地鳴りのような音と猛烈な風が起こった。 



作中でキム・ヒャンモクがいた病院から道をはさんだ右側、ホークスタウンからの様子です。左から、ヒルトン福岡シーホーク、同じく4Fのアトリウム(作中/当時ラグナグ)、ヤフードーム(作中/当時福岡ドーム)です。

こんなものが倒壊したら、ひどいもんです。この写真をとっていた場所や、すぐ近くの野営地にいたら、爆風と飛んでくる瓦礫でアウトでしょう。作中では実際アウトだったわけですが。

そのうち他の写真もまとめるかも。

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」と入力する。

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

2011-09-01

バスキュール号に入社しました

@shin_no_suke
どういうことなの? (hg)changeset:   164:aedce6151bf9 user:        Shinya Okano<...> date:        Wed Aug 31 17:23:39 2011 summary:     buchoはオワコン
( http://twitter.com/#!/shin_no_suke/status/108831096287920128 より)


昨日で株式会社ビープラウドを退職し、本日より株式会社バスキュール号で働きます。

なんで辞めるの?と聞かれるのですが、因果関係としては、ビープラウドを辞めるからバスキュール号に行くのではなくて、バスキュール号に行くのでビープラウドを離れます。

以前、マーケティングをかじっていたことがあります。プロモーションのためのシステムを開発するという、バスキュール号のビジネスに関わりたいと思うようになりました。サーバサイドのエンジニアとしての採用ですが、企画にも口を出してよいということなので、そういう現場で働いてみたいという思いです。別に尻文字を間近で見たいからではありませんよ。見たいですけど。

業務としてのソフトウェア開発未経験の私を拾ってくれて、ここまで育ててくれたビープラウドには感謝しています。技術的なスキルも、その他のスキルもまだまだビープラウドで学ぶ余地はいっぱいあるので、そこはもったいないなぁと思います。また、ちゃんとバリューを出せていたのだろうか、と憂うことなど多々あります。が、気に病んでいても私の価値は変わりませんし、私にとってのベストをつくしたので、評価はビープラウドのメンバーとそのお客様に任せるということで。

仕事では当分 Python を使い続けますし、仕事におけるタスク自体は似ていますし、公私共にソーシャルグラフは大きく変わることはないので、これまでどおり、自分が出せるバリューを出していく所存でございます。

そうそう、ビープラウドバスキュール号も、Pythonista を探しています。