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 を発見。

<!ELEMENT datastore-indexes (datastore-index)*> <!ATTLIST datastore-indexes autoGenerate (true|false) #REQUIRED> <!ELEMENT datastore-index (property)*> <!ATTLIST datastore-index ancestor CDATA #REQUIRED kind CDATA #REQUIRED> <!ELEMENT property (#PCDATA)> <!ATTLIST property direction (asc|desc) #REQUIRED name CDATA #REQUIRED>

これを見る限り Composite index の設定しか無さげ。

対策検討中にたまたま見付けたのが以下のページ

  • Single Property Indexを作らないとComposite Indexも作成されない?
    http://d.hatena.ne.jp/koherent/20100801/1280643622

うーん、逃げ道無し。

ちなみに single index 全部無くすとどのくらい効果があるかと言うと 項目数11個で Write ops が 22 -> 2 になる。

実際には全部消せないけどケースバイケースで効果絶大な場合もありそう。

2011/11/15

GAEの新料金体系、追記

前の記事で 1entity=1ops と書いたがどうもそうじゃ無いらしい。
ちゃんと計算する方法は分からないのだがローカルの環境で Datastore Viewer を見ると Write Ops と言う項目が勝手に追加されている事に気づいた。

この Entity を書き込んだ時に使った Datastore Write Operations の値と思われる。

Kind により異なるが手元のデータでは 1entity=20〜90ops になっている。
おそらく 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の新料金体系、追記


2011/10/12

GAE で MySQL が使えるようになったみたい

まだ、限定リリースだけど GAE で MySQL が動くらしい。
  • http://code.google.com/intl/ja/apis/sql/

Java だと普通に JDBC からアクセスするみたいだ。

おいらは申請ページにも行かせてもらえなかったので試せて無い。(;_;)

無償領域があるのかどうかは今の所良く分からない。 でもこれが無料で使えるなら誰も Bigtable 使わないので有料だと思う。


2011/08/24

GAEでJSESSIONIDが永続化されない

GAE/J でセッションを使っていて困った現象が1つ。

JSESSIONIDが永続化されないのだ。

Chromeで見てみると御覧の通りで実機に上げても同じ。

web.xml の session-timeout を設定しても変わらない。

jetty のソースを眺めてみたがそんな実装になっているように見えない。
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 が何らかの意図を持ってやっている場合、 将来的に動かなくなるかもしれないのでこういう回避に仕方は嫌なんだよね。

でも、ブラウザ立ち上げっぱなしすれば同じだから意味がない。 どういう仕様なんだろ。


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

以下はライブラリの詳細。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

storedbean v1.0

  • author 2011.06.18 kotemaru.org

概要

storedbean は GAE 上で簡易に Bean インスタンスを DataStore? に永続化する事を目的とするライブラリです。

環境設定を保存するような Bean は通常インスタンスは一つしか存在せず複数の Bean が構造化されることも有ります。

このような Bean の為に JDO や Slim3 の Model をちゃんと定義する事は以外な負担となります。

Serialize を使う方法も有りますが後々、互換性の問題を引き起こしたりデータの可読性が悪くなる問題があります。

これらの問題を解決し気軽に作った Bean をそのまま永続化できるようにしたものが本ライブラリです。

使い方

  1. storedbean-1.0.x.jar をビルトパスに追加します。
  2. 保存したい Bean に StoredBean? インターフェースを実装します。
    • StoredBean? にはメソッドはありません。
  3. StoredBeanService?#put で保存します。
  4. 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 の規約に従っていなければなりません。
  • 項目の型の制限
    • int,long,boolean,String,byte[],List,Set,StoredBean? のみが利用可能です。
    • List,Setの項目は Integer,Long,Boolean,String,StoredBean? のみが利用可能です。
  • 復元時の制限
    • List,Set が復元されると実体として元の型とはならず ArrayList?,HashSet? となります。

コンパイル

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 <command> [-limit=<n>] [-file=<file>] <url> <kind> <command>: download or upload. download : Download JSON format entities from <url>. upload : Upload JSON format entities to <url>. <url>: DsToolServlet URL. Ex. https://dstool.app-id.appspot.com/dstool <kind>: DataStore kind. Ex. Employee -limit=<n>: The number of the limits of the entity to handle at one request. default=100. -file=<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。

198 entity 取れてる。

落ちて来たデータ。 {__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順とかを考えないとうまく動かないかも。

基本的な事は出来たけど課題も出て来た。

  • ユーザ認証。
  • GUIクライアント。
  • 削除機能。
  • upload の上書きオプション。
  • download の絞り混み条件。
くらい有ると使い物になりそうな気がする。 認証は OAuth が本命だけどオーバースペックかなぁ。

ここまでのコードは Google code に上がっているので興味のある人は Eclipse の SVN で落してください。


2011/05/29

GAE/J用のBigtableのバックアップツールが無い

なんでBigtableのバックアップ/リストアツールって用意されて 無いんだろと思ってたら Python版 のみ用意されてたのね orz.

仕方が無いので Java版を自力で作る方向を考えてみる。

まずデータ形式はJSONとする。 Python版は XML or CSV のようだがJSONの方が軽いので。

CSVは簡易形式としては便利だけどBigtableを完全に表現できないので 直接は扱わない。 CSV->JSONのフィルタを別ツールで用意すれば良い。

データの仕様を決めてみる。

基本仕様

  • 1つの Entity は 1つのJSONオブジェクト({〜})として表す。
  • Entity のプロパティ名がそのままJSONのプロパティ名となる。
  • プロパティ名 "__key__" は Entity の Key として元々予約である。
  • プロパティ名の文字列化は省略可能。JavaScript互換仕様。
  • プリミティブ以外のデータはプロパティ type を必須とするJSONオブジェクトで表す。

イメージ:

{__key__:{type:"Key", id:1234}, data1:123, data2:{type:"Date",value:1234567890},...}

各データ種別毎の仕様
クラスJSON表現説明
nullnull
Long123
Double123.0'.' or 'e' を含める事でLongと区別する。
Booleantruetrue 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 するしかないだろうし。

プロフィール
20年勤めた会社がリーマンショックで消滅、紆余曲折を経て現在はフリーランスのSE。 失業をきっかけにこのブログを始める。

サイト内検索

登録
RSS/2.0

カテゴリ

最近の投稿【GAE/J】

リンク

アーカイブ