2012/05/07
datastore-indexes.xmlでSingle Indexは制御できないのかね?
GAEの課金が大きくなったその対策の一つとして余計な index
を作らないと言うのが有るのだが
Single index はデフォルトで作られちゃうので各項目に設定しなければならない。
Slim3ならアノテーションでやって貰えるけど低レベルAPI
だとソースの setProperty() を setUnindexedProperty() に変更しないといけない。
で、これって datastore-indexes.xml で出来ないのかと思って調べたがどうやら
出来ないらしい。
つーか、良く考えたら datastore-indexes.xml のちゃんとした仕様って
読んだこと無いわ orz
しかも、ググってもなかなか出て来ない。 結局 SDK の中に DTD を発見。
これを見る限り Composite index の設定しか無さげ。
対策検討中にたまたま見付けたのが以下のページ
しかも、ググってもなかなか出て来ない。 結局 SDK の中に DTD を発見。
- Single Property Indexを作らないとComposite Indexも作成されない?
http://d.hatena.ne.jp/koherent/20100801/1280643622
2011/11/15
GAEの新料金体系、追記
前の記事で 1entity=1ops と書いたがどうもそうじゃ無いらしい。
ちゃんと計算する方法は分からないのだがローカルの環境で Datastore Viewer を見ると Write Ops と言う項目が勝手に追加されている事に気づいた。
おそらく index の書き込みがカウントされているのだと思うがそれにしても大きい値だ。 無料分では1000件書込みできない。 もう一回、資料を読み直そうと思ったら
ちゃんと計算する方法は分からないのだがローカルの環境で Datastore Viewer を見ると Write Ops と言う項目が勝手に追加されている事に気づいた。
おそらく index の書き込みがカウントされているのだと思うがそれにしても大きい値だ。 無料分では1000件書込みできない。 もう一回、資料を読み直そうと思ったら
http://code.google.com/intl/ja/appengine/docs/quotas.html
から Datastore Operations に付いての記述が無くなってる...
おそらく内容が実態と一致していないため削除されたのだろう。
現状は Write Ops から Read Ops を類推して使うしか無さそうだ。
2011/11/09
GAEの新料金体系
2011/11/07 からGAEのプレビューが終って新料金体系に移行した。
で Datastore Read Operations の制限がきつくてあちこちで悲鳴が上がってる。
ちょっとググっただけでも
http://hondaalfa.blogspot.com/2011/11/datastore-read-operations.html
http://arle30.dtiblog.com/blog-entry-63.html
http://d.hatena.ne.jp/a-know/20111108/1320749150
うちも軽くテストしてたら 40% 行ってた。 Operationの単位が良く分からなかったんで調べてみた。 http://code.google.com/intl/ja/appengine/docs/quotas.html#Datastore 大雑把に言うと 1/Entity 読み込んだら 1/ReadOperation になる。 つまり 5万Entity/日 しか読み込めない。 元々、Datasotre は Write が遅いってんでみんな Read に寄せる作りになってんだよね GAE のアプリって。 一応、memcache で逃げられるって海外のサイトに書いて有ったけど そう都合の良いデータばっかりじゃ無いしね。 基本、まともなアプリは金払えって事になったみたい。
課金は Read $0.07 per 100k operations なんでぼったくりじゃ無いけどねー。 ちなみに Blobstore が 5G まで無料になったので ちょっとした動画くらい置ける。 もっとも Outgoing Bandwidth が 1G/日 だけど (^^; GAEの新料金体系、追記
http://arle30.dtiblog.com/blog-entry-63.html
http://d.hatena.ne.jp/a-know/20111108/1320749150
うちも軽くテストしてたら 40% 行ってた。 Operationの単位が良く分からなかったんで調べてみた。 http://code.google.com/intl/ja/appengine/docs/quotas.html#Datastore 大雑把に言うと 1/Entity 読み込んだら 1/ReadOperation になる。 つまり 5万Entity/日 しか読み込めない。 元々、Datasotre は Write が遅いってんでみんな Read に寄せる作りになってんだよね GAE のアプリって。 一応、memcache で逃げられるって海外のサイトに書いて有ったけど そう都合の良いデータばっかりじゃ無いしね。 基本、まともなアプリは金払えって事になったみたい。
課金は Read $0.07 per 100k operations なんでぼったくりじゃ無いけどねー。 ちなみに Blobstore が 5G まで無料になったので ちょっとした動画くらい置ける。 もっとも Outgoing Bandwidth が 1G/日 だけど (^^; GAEの新料金体系、追記
2011/10/12
GAE で MySQL が使えるようになったみたい
まだ、限定リリースだけど GAE で MySQL が動くらしい。
- http://code.google.com/intl/ja/apis/sql/
2011/08/24
GAEでJSESSIONIDが永続化されない
GAE/J でセッションを使っていて困った現象が1つ。
JSESSIONIDが永続化されないのだ。
Chromeで見てみると御覧の通りで実機に上げても同じ。
GAEの独自の仕様なのだろうか? 自力でセッションを実装しても良いのだができればやりたく無いので 逃げ手を探してみた。 Set-Cookie は同じキーが複数ある場合、 後の方が有効になるはずなので Filter をかましてみた。
public class SessionFilter implements Filter {
public void init(FilterConfig conf) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws ServletException, IOException {
chain.doFilter(req, res);
HttpServletRequest _req = (HttpServletRequest) req;
HttpServletResponse _res = (HttpServletResponse) res;
HttpSession session = _req.getSession();
Cookie jsid = new Cookie("JSESSIONID",session.getId());
jsid.setMaxAge(session.getMaxInactiveInterval());
jsid.setPath("/");
_res.addCookie(jsid);
}
public void destroy() {
}
}
なんか行けてるみたい。
とりあえず、session-timeout の設定が効くようになった。
GAEの独自の仕様なのだろうか? 自力でセッションを実装しても良いのだができればやりたく無いので 逃げ手を探してみた。 Set-Cookie は同じキーが複数ある場合、 後の方が有効になるはずなので Filter をかましてみた。
とりあえず、session-timeout の設定が効くようになった。
2011/06/18
GAE/JでBeanをそのまま保存したい。
GAE/J のコードを書いていると1個しか作らない Bean
をちょいと保存したい事が良く有る。
JDO や Model を作るのはたるいので
Serialiserで逃げていたのだがやはりいろいろ
問題が出て来る。
Memcache 程度のAPIで DataStore に Bean を保存できないかと
思ってライブラリを書いてみたらあっさり動いた。
もうちょっと機能追加したい部分もあるがここで一旦、公開しとく。
ソース:http://kotemaru.googlecode.com/svn/trunk/storedbean/
バイナリ:http://code.google.com/p/kotemaru/downloads/list
Javadoc:http://kotemaru.googlecode.com/svn/trunk/storedbean/docs/javadoc/index.html 以下はライブラリの詳細。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
バイナリ:http://code.google.com/p/kotemaru/downloads/list
Javadoc:http://kotemaru.googlecode.com/svn/trunk/storedbean/docs/javadoc/index.html 以下はライブラリの詳細。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
storedbean v1.0
- author 2011.06.18 kotemaru.org
概要
storedbean は GAE 上で簡易に Bean インスタンスを DataStore? に永続化する事を目的とするライブラリです。
環境設定を保存するような Bean は通常インスタンスは一つしか存在せず複数の Bean が構造化されることも有ります。
このような Bean の為に JDO や Slim3 の Model をちゃんと定義する事は以外な負担となります。
Serialize を使う方法も有りますが後々、互換性の問題を引き起こしたりデータの可読性が悪くなる問題があります。
これらの問題を解決し気軽に作った Bean をそのまま永続化できるようにしたものが本ライブラリです。
使い方
- storedbean-1.0.x.jar をビルトパスに追加します。
- 保存したい Bean に StoredBean? インターフェースを実装します。
- StoredBean? にはメソッドはありません。
- StoredBeanService?#put で保存します。
- StoredBeanService?#get で取得します。
サンプルコード
import org.kotemaru.gae.storedbean.StoredBean; import org.kotemaru.gae.storedbean.StoredBeanService; // Bean の定義 public class TestBean implements StoredBean { private int item01; private String item02; public int getItem01() {return item01;} public void setItem01(int item01) {this.item01 = item01;} public String getItem02() {return item02;} public void setItem02(String item02) {this.item02 = item02;} } // Bean に値を設定。 TestBean bean = new TestBean(); bean.setItem01(123); bean.setItem02("abc"); StoredBeanService sbs = new StoredBeanService("StoredBean"); String key = "key-name"; // 保存 sbs.put(key, bean); // 復元 TestBean restoreBean = (TestBean) sbs.get(key);
- 全体としてはほぼ Map と同じように使えます。
- StoredBeanService?コンストラクタの引数は保存先DataStore?のkindです。
- key-name は保存先の名前で任意の文字列です。
- 復元時は Memcache を利用しますので性能を気にする必要はありません。
- 保存したデータは GAE の管理画面から編集する事が可能です。
- ※管理画面から編集後は Memcache のクリアをしてください。
DataStore? の状態
サンプルを実行するとDataStore?は以下のような状態になります。
![]() |
そのまま編集も可能です。
![]() |
制限
StoredBean? として保存可能な Bean には幾つかの制限があります。
- Bean の制限
- 一般的な Bean の規約に従っていなければなりません。
- 項目の型の制限
コンパイル
Eclipseでビルド後 build.xml の Ant実行 からターゲット jar を実行してください。
storedbean-1.0.x.jar が生成されます。
2011/06/05
GAE/J用のバックアップツールを作ってみてる。その1
前回の
「GAE/J用のBigtableのバックアップツールが無い」
で決めた仕様にしたがってバックアップツールを作ってみた。
JSONパーザはWSJSから使い回しなので
DataStoreの低レベルAPIを使って読込と書込をするServletを作ってやれば良い。
Servletは dstool とかのバージョンで upload して
dstool.{app-id}.appspot.com にアクセスさせる。 面倒なのはクライアントの方だ。 Download中に30秒ルール等で落ちた場合、 最後の中途半端なEntityの部分を捨てて そこからやり直すリクエストをサーバに送ったりとかしなければいけない。 Uploadも同様に正常に書き込めたEntity数をサーバから返してもらって その続きをできるようにしなけらばならない。 困ったのは現存する Kind の一覧を取る方法が無いこと。 Local環境だと裏技があるみたいだが通常のAPIには無いのか? もろもろ含めて決めた CUI の仕様。$ ./bin/dstool.sh
Usage: java DsTool [-limit=] [-file=]
: download or upload.
download : Download JSON format entities from .
upload : Upload JSON format entities to .
: DsToolServlet URL. Ex. https://dstool.app-id.appspot.com/dstool
: DataStore kind. Ex. Employee
-limit=: The number of the limits of the entity to handle at one request. default=100.
-file=: Output or input JSON file. default=stdin/stdout.
Eclipse から使う事を考えると GUI も必要だなぁ。
実際にServletを上げて動かしてみた。
$ ./bin/dstool.sh download -file=/tmp/Page.json http://dstool.wsjs-gae.appspot.com/dstool Page
Request http://dstool.wsjs-gae.appspot.com/dstool?kind=Page&limit=100
Status: 200
Download 100 entities.
Request http://dstool.wsjs-gae.appspot.com/dstool?kind=Page&limit=100&offset=%7B%22name%22%3A%22%2Flib%2Fjscp%2Fimgbtn.pack%22%2C%22type%22%3A%22Key%22%2C%22kind%22%3A%22Page%22%7D
Status: 200
Download 98 entities.
Request http://dstool.wsjs-gae.appspot.com/dstool?kind=Page&limit=100&offset=%7B%22name%22%3A%22%2Ftmp%2Fmaildata.txt%22%2C%22type%22%3A%22Key%22%2C%22kind%22%3A%22Page%22%7D
Status: 204
#Windowsの場合は dstool.bat。
{__key__:{type:"Key",kind:"Page",name:"/"}, length:0, body:null, parentPageName:null, lastModified:1278830141000, directory:true},
{__key__:{type:"Key",kind:"Page",name:"/_wsjs_"}, length:0, parentPageName:"/", lastModified:1278856948000, directory:true},
{__key__:{type:"Key",kind:"Page",name:"/_wsjs_/develop"}, length:0, parentPageName:"/_wsjs_", lastModified:1286692293086, directory:true},
{__key__:{type:"Key",kind:"Page",name:"/_wsjs_/develop/template"}, length:0, parentPageName:"/_wsjs_/develop", lastModified:1286692306721, directory:true},
{__key__:{type:"Key",kind:"Page",name:"/_wsjs_/develop/template/0.css"}, length:37, body:{type:"Blob",value:"0A2E436C737373207B0A096D617267696E3A20303B0A0970616464696E673A20303B0A7D0A"}, parentPageName:"/_wsjs_/develop/template", lastModified:1286692308802, directory:false},
{__key__:{type:"Key",kind:"Page",name:"/_wsjs_/develop/template/0.exjs"}, length:979, body:{type:"Blob",value:"2F2A2A0A45584A53E381AF4A617661536372697074E38292E4B880E983A8E68BA1E5BCB5E38197E381A6207969656C6420E6A99FE883BDE38292E4BDBFE38188E3828BE38288E38186E381ABE38197E...
:
:
1Entity=1行で落してくるのでBlobとか有ると凄い事になる。
エディタによっては開けないと思う。
当然、このデータは加工してから upload とかできる。
Downloadしたデータを別のサーバにUploadしてみる。
$ ./bin/dstool.sh upload -file=/tmp/Page.json http://dstool.new-server.appspot.com/dstool Page
Status: 200
upload 100 entities.
Status: 200
upload 98 entities.
管理画面から確認。ちゃんとuploadできてるみたい。
あ、Keyに親が有った場合、upload順とかを考えないとうまく動かないかも。
基本的な事は出来たけど課題も出て来た。
dstool.{app-id}.appspot.com にアクセスさせる。 面倒なのはクライアントの方だ。 Download中に30秒ルール等で落ちた場合、 最後の中途半端なEntityの部分を捨てて そこからやり直すリクエストをサーバに送ったりとかしなければいけない。 Uploadも同様に正常に書き込めたEntity数をサーバから返してもらって その続きをできるようにしなけらばならない。 困ったのは現存する Kind の一覧を取る方法が無いこと。 Local環境だと裏技があるみたいだが通常のAPIには無いのか? もろもろ含めて決めた CUI の仕様。
198 entity 取れてる。
落ちて来たデータ。あ、Keyに親が有った場合、upload順とかを考えないとうまく動かないかも。

- ユーザ認証。
- GUIクライアント。
- 削除機能。
- upload の上書きオプション。
- download の絞り混み条件。
2011/05/29
GAE/J用のBigtableのバックアップツールが無い
なんでBigtableのバックアップ/リストアツールって用意されて
無いんだろと思ってたら Python版 のみ用意されてたのね orz.
仕方が無いので Java版を自力で作る方向を考えてみる。
まずデータ形式はJSONとする。
Python版は XML or CSV のようだがJSONの方が軽いので。
CSVは簡易形式としては便利だけどBigtableを完全に表現できないので
直接は扱わない。
CSV->JSONのフィルタを別ツールで用意すれば良い。
データの仕様を決めてみる。
基本仕様{__key__:{type:"Key", id:1234}, data1:123, data2:{type:"Date",value:1234567890},...}
各データ種別毎の仕様
てな感じかな。
クライアントは CUI でやるしか無いね。 30秒ルールが有るから分割して Upload/Download するしかないだろうし。
- 1つの Entity は 1つのJSONオブジェクト({〜})として表す。
- Entity のプロパティ名がそのままJSONのプロパティ名となる。
- プロパティ名 "__key__" は Entity の Key として元々予約である。
- プロパティ名の文字列化は省略可能。JavaScript互換仕様。
- プリミティブ以外のデータはプロパティ type を必須とするJSONオブジェクトで表す。
クラス | JSON表現 | 説明 |
---|---|---|
null | null | |
Long | 123 | |
Double | 123.0 | '.' or 'e' を含める事でLongと区別する。 |
Boolean | true | true or false |
String | "文字列" | 日本語はUTF8 or \uXXXX。改行は \n にエスケープ。 |
Key | {type:"Key", name:"key名"} | 文字列Keyの場合 |
{type:"Key", id:keyID} | LongKeyの場合 | |
{type:"Key", name:"key名" or id:keyID, parent:{再帰的にKey}} | 親を持つ場合 | |
Date | {type:"Date", value:経過ミリ秒} | valueはgetTime()の値 |
{type:"Date", fmt:"書式", value:"日時"} | SimpleDateFormat形式で表現 | |
ShortBlob | {type:"SBlob", value:"16進文字列"} | |
Blob | {type:"Blob", value:"16進文字列"} | |
Text | {type:"Text", value:"文字列"} | Stringに同じ |
User | {type:"User", email:"メールアドレス", authDomain:"ドメイン名"} | |
Link | {type:"Link", value:"URL文字列"} | |
Collection | [data1,data2,...] | 配列で表現 |
クライアントは CUI でやるしか無いね。 30秒ルールが有るから分割して Upload/Download するしかないだろうし。