2011年11月30日水曜日

Herokuに浮気・・・が始まらない

GAEの新料金体系で、インスタンス課金がどうしても気に入らない(※)ため、Herokuに乗り換えようかと画策中。


だってね、インスタンスはほぼ1日中1個(時々0個か2個)上がってるから、インスタンス使用時間は約24H/Dayなんですが、CPU時間で見ると0.1%くらいしか使ってないわけですよ。それでも継続的にリクエストが来るからインスタンスが上がりっぱなだけで。
CPU使ってないんだからもったいないなあ、、、というのが正直なところ。まあ、メモリは食ってますけどね。

なんだけど、Herokuにアプリを登録するのは、rubyで動くherokuコマンドをインストールする必要があり、gitも必要です。あと、サンプルを動かすためにrailsなんかも必要で、gemから入れようとしたら失敗するし(Windowsだから、という原因もある)、どうすりゃいいんだわたしゃ、と思っている次第。
まあ、railsはgemで入れんでも良いような気がするが、誰か楽なHerokuの始め方を教えてくれー。

ただ、GAEの新料金のネック(Push It!における)はもう一つ、Channel数の制限があるんですが、
Herokuにしたところで、WebSocket(GAEだとChannelにあたる。Channelの方が対応環境が多いが)は標準では対応してなくて、Pusherというアドオンを入れると使えるんだけど、無料だと20connectionまでって、それGAEのChannel数制限と大差ないから!ということに気づく。
無料だと・・・どこ行ってもキツイね・・・。そらそうなんだが。

2011年11月19日土曜日

Datastoreのunittest(Python)

HRDネタが続いてますが、GAEの公式ドキュメントに、
http://code.google.com/intl/en/appengine/docs/python/tools/localunittesting.html#Writing_HRD_Datastore_Tests
こんな記事があるのを見つけました。

unittestのための記事なんですが、Eventual Consistencyな状態を、指定した条件で疑似れる模様。まだ使ってませんが。

あら素敵じゃない。HRDそのものはxx(←自粛)ですが・・・。
でもな〜、ドキュメントこれだけ?リファレンスは?ここに書いてあることが全部?
う〜ん・・・。

2011年11月10日木曜日

High Replication Datastoreの扱いに困る話

第一章 はじめに


前回のエントリで書いたように、GAEのPython2.7で使えるDatastoreは、High Replication Datastore(HRD)というやつ一択で、こいつの扱いが難しいのに困っている。


具体的には、クエリの整合性が担保されないということなのだそうな。

・キーを使ったアクセスは「問題なし」
・祖先クエリ(Entityの親子関係を使ったクエリ)は「問題なし」
・その他のクエリは「過去の状態に基づく結果が返る可能性がある」


なので強い整合性を必要とする場合、memcacheやら何やらを駆使して、解決せよ、というのがGoogle始め、各人の御宣託なのであるが、しかし、memcacheも消えることがあるのだし、どうしようか。というお題である。

第二章 解決策

そこで考えたのが、
  • Datastoreへのアクセスをラッパーするクラスを作って、書き込み時はmemcacheにも必ず書き込む。
  • クエリは、ラッパーを経由することで、Datastoreに対する検索結果とmemcacheのデータをマージして返す。
  • クエリもパターン化されているなら、クエリの結果もキャッシュしておく。
  • と、そこまでやってもmemcacheは消えることもあるので、Datastore操作ログをDatastoreに残す。ログは全数にアクセスできないといけないので、Key名はプログラム的に推定可能なものを採用する。
ということ。

第三章 問題点

よし。ここまでやりゃ大丈夫か。いや、トランザクションとか使うともっとややこしいことになるが。

ただ、機能的には大丈夫なんだが、問題もある。
  • 「Datastoreに対する検索結果とmemcacheのデータをマージ」するコスト(CPU時間)がバカにならない気がする。
  • 特にmemcacheデータが増えてきた場合。
  • なので、memcacheを定期的に捨てないといけないのだが、Datastoreへのクエリがいつになったら整合性が取れるのか分からないので、捨て時がわからない。
  • あるクエリの実行結果がmemcacheと一致するなら「そのクエリに関しては」整合性が取れたと言えるのだろうが、別のクエリでも一致するとは限らない(と思う)ので完全に整合性が取れたと言い切れるタイミングが図れない。
  • Datastore操作ログもmemcacheと同様に捨て時がわからない。
ふ~む、困ったね・・・。

第四章 HRDで整合が取れるまでの時間とは


で、実際HRDで整合性取れるのってどのくらいかかんのよ、ってことで実験してみたよ。
  • Datastoreにput()する
  • 直後に「put()したデータだけ」がひっかかるはずのクエリを投げる
  • 引っ掛かった件数が1なら整合、それ以外なら不整合と判定する
結果:必ず一件取れた。おいおい・・・、優秀じゃねーか。

ん~、データが軽すぎるのかも知れない。プロパティ数が数個かつエンティティ数も100件程度じゃ、あっという間にインデックスが構築されてしまう模様。

じゃ、プロパティを26個まで増やし、エンティティ数も1000件程度の状態まで持って行ったらどうか。

結果:数百回試して1回だけ0件となった。

なるほどね・・・。
スクリプト言語であるPythonごときの処理速度では、インデックス構築完了より先にクエリは投げられんよ、と、そういうことか?

てーことは、何?上に書いたような対策は杞憂なの?やりすぎなの?
でもさー、保証されてないんだから考えちゃうよね、そりゃ。

第五章 終幕


とかって実験しているうちに、Over Quotaとなった。。。

新料金体系では、無料枠は
Datastore Write Operations : 0.05 Million Ops
となっているので、Write Opsは5万回まで。

Write Opsってput()とdelete()の回数ですか?って甘かった。
put()/delete()の際のインデックスの更新もWrite Opsに含まれるんだってよ、奥さん!

インデックスは、Keyと、プロパティ1個1個に対して昇順と降順のインデックスが作られるので、
・データそのもの(1)
・Key用インデックス(1)
・プロパティの昇順、降順(プロパティ数 x 2)
だけのWrite Opsが発生するんだって!一回のput()で!

上記の実験では26個のプロパティを用意したので、一回当たり 1 + 1 + 26 * 2=54 Ops

5万 ÷ 54 = 約1000

そう。たった1000回(未満)のput()でOver Quotaなのであった。
なーんーだーよー。

ろくろく実験もできねーじゃねーか。

なんかもー、こんな対策考えてる時点でベンダーロックインされまくってる弊害がありありだし、別に安くもなくなっちゃったし、もうGAEやめちゃおっかな、とか思うんだが・・・。

2011年11月1日火曜日

【備忘録】 GAEでPython2.5からPython2.7へマイグレーションするには

まだ途中なんだけど、GAEがPython2.7に対応した(但、experimental)。

Python2.7になると何が嬉しいかって、新料金体系で制限が厳しくなったインスタンス起動時間の制限を、スレッドの利用によって回避できる(かも?)なこと。

ちゅうことで、今はPython2.5で作られているPush It!サーバをPython2.7化しようと目論見中なのだが、若干はまったので、備忘録としておく。

とりあえずこの辺とかこの辺とかは読んどくこと。必要なことは多分ここから全部たどれる。

①アプリケーションの新規作成

Python25で作ってあるアプリケーションをPython27に変更できるかどうかは調べていないので不明だが、少なくとも現状DatastoreをMaster/Slaveにしている場合は、Python27だとHigh Replication一択なので、新規に作成しておく必要がある。

②app.yamlの変更(その1)

とりあえず、以下は必須だ。
-runtime: python
+runtime: python27

①でアプリケーションを新規作成した場合はapplicationも変更しておこう。

それから、後でも良いが、スレッドを使いたい場合は、
+threadsafe: true
も付けておこう。
ただし、threadsafeでないライブラリを使ってない場合は実行時に怒られるぞ。 アプリがthreadsafeかどうかは実装次第なのでそこは自前で頑張ること。

Djangoを使っている場合は、
+libraries:
+- name: django
+  version: "1.2"
こんなのもいるのだが、しかし、google.appengine.ext.webapp.templateとしてDjangoのTemplate機能のみを使っている場合は不要っぽい。

③webapp2を使用するように変更する
webappフレームワークがwebapp2となりましたのでimportするモジュールを変更する。
が、しかし、Python27 on GAE環境ではwebappという選択肢がないので、webappモジュールはwebapp2へのエイリアスとなっている。
従って、結論としては変更の必要なし。

④とりあえずサーバにアップしてみよう

尚、Python27版のSDKは「ない」。ローカルでテストできないので必然的にサーバにアップしてテストすることになる。
アップロードの仕方は以前と変わりなし。

で、エラーになった。

app.yamlで怒られる。CGI Handlerはthreadsafeではありません、とな?
ここをよくよく見てみよう。

script: main.app

と書いてあるでしょう。
紆余曲折ありつつも、これは、main.pyのappという変数にマッピングすることを意味しているらしいことが分かる。なので、以下に続く。

⑤app.yamlの変更(その2)

④に書いた通り、

-script: main.py
+script: main.app
てな感じに修正する(mainはあくまで例。任意のハンドラスクリプト名に置き換える)。
appが予約語なのか、main.pyのWSGIApplicationのインスタンスを指す変数名ならなんでも良いのか、は不明。

⑥ハンドラスクリプトの修正

③に書いた通り、webappほげほげのimportはエイリアスが張られているので変更不要。
変更が必要なところは、ここに書いてある。
要するにmain関数が不要になり、WSGIApplicationのインスタンスをappという名前の変数に格納する、ということ。

⑦サーバにアップロードする(2回目)

さあ、これで準備は整いました。
アップロードして、アクセスしてみよう。
よほど特殊なことをしていない限りは多分、それなりに動くはず。

⑧Django1.2対応

⑦でそれなりに動くことは動くんだが、Djangoのバージョンが0.96から1.2に変わっている影響で、templateモジュールに若干の変更あり。

テンプレートに埋め込む変数値が、0.96ではデフォルトではエスケープされないところ、1.2ではデフォルトでエスケープされるようになった。
なんらかの理由によりスクリプト側から既にエスケープした状態の値をテンプレートに渡している場合、二重にエスケープされることになるので、

somevariable|safe

みたいな感じで書いてあげること。
あと、HTMLタグを含む値(などのHTML的に意味のある値)を、HTMLタグとして埋め込みたい場合とかね。

⑨High Replication Datastoreへの対応

普通のアプリケーションなら⑧までやれば、それなりに動くようにはなるので、Python27化はそれほど高い敷居ではありません。

さて、問題はこれです。
従来、GAEのDatastoreはMaster/Slave(MSD)という構成でした。
MSDは1年ちょっと前にトラブルが続発していたことがあり、その反省から生まれたのが、High Replication Datastore(HRD)です。
以降、GAEでアプリケーションを新規作成する際には、MSDかHRDのどちらかを選ぶことができるようになりました。ですが、そのアプリケーションでは新規作成時以外ではDatastoreを変更することはできません。

どちらを選ぶべきか、は、この辺に書かれているのですが、気になるのは、
  • Python2.7ではHRDしか選択肢がない(GoogleさんはMSDを捨てたい、と思っているのでしょう)
  • Consistency(日本語にすると一貫性とか整合性、かな?)の「Most Queries」がStrongからEventualになっている。
Eventual...つまりどういうことか?というのが分かりやすく書かれているのがこの辺になります。

意訳すると、データの一貫性は保たれるが、インデックスの一貫性は保証できねー、ということらしい。インデックスを使わない(つまりクエリーを使わない)で、キーなどを使ってデータに辿りつける場合は無問題。クエリー使うと、最新でない結果セットが取れちゃうかもよ、てなことだそうだ。

情報系(ブログとかニュースとか)などのサイトであれば、多少結果が古かろうと「まあいいよね?」で済むと思うんだが、Push It!はしかし、そうではない。一貫性が保証されないと、クライアントとの同期が維持できない。
困ったことだよ・・・。

回避策としてはmemcacheの利用などが挙げられているものの、memcacheって揮発性だから、なくなっちゃった状態から、どうやって「最新と保証できる」memcacheを構築できるのか、全く思いつかない。
全ての情報がキーから辿れるなら(そしてそのキーが可知であれば)問題ないですけど、そんな風には作ってないわけで・・・。

しかし、今回のブログはリンクいっぱい埋め込んだな・・・。情報分散しすぎなんだよ・・・。