• Google App Engine ~ TaskQueue

    このエントリーを含むはてなブックマークはてなブックマーク - Google App Engine ~ TaskQueue この記事をクリップ!Livedoorクリップ - Google App Engine ~ TaskQueue Yahoo!ブックマークに登録 @niftyクリップに追加 Share on Tumblr FC2ブックマークへ追加 newsing it! Googleブックマークに追加 Bookmark this on Delicious FriendFeedで共有 このエントリをつぶやくこのWebページのtweets

    こんばんは。システム部の佐々木です。

    前回から少し日が空いてしまいましたが、本日はTaskQueueについて書きます。

    読んで字の如く、Task(タスク)Queue(キュー)なのでできることはタスクのキューイングです。
    簡単に説明すると非同期処理なのですが、前回説明したCronとは別の非同期処理になります。

    Cron…実行タイミングがスケジューリングされている。
    TaskQueue…キューイングはプログラムで行い、実行タイミングはGoogle App Engineが判断します。

    Google App EngineではHttp(s)は30秒でタイムアウトする仕様になっています。
    リクエスト時に大量データ登録などが発生してサーバの処理時間が30秒を超えた場合、
    Google App Engineはエラーを返します。
    極力重いアクセスを避けるのがよいのですが、そういう訳にはいかない場合もあるでしょう。

    という訳なので重い処理を行う場合はTaskQueueを使用します。

    使い方は至って簡単。

    1.サーブレットを作る

    前回のcronの時のようにサーブレットを一つ作ります。
    ここではAsyncTaskControllerということにしましょう。(slim-genで自動生成)
    Slim3の規約に従って<Slim3RootPackage>.queue.AsyncTaskControllerには”/queue/asyncTask”というURLでアクセスすることができます。
    通例ですがcronと同様に”/queue/*”には管理者以外アクセスできないようにしましょう。

    2.TaskQueueを呼びだす

    最もシンプルなTaskQueueの呼び出しは次のようになります。

    Queue queue = QueueFactory.getDefaultQueue();
    queue.add(TaskOptions.Builder.withUrl("/queue/asyncTask"));

    こうすることによってGoogle App Engineが/queue/asyncTaskに非同期でリクエストを送信してくれます。

    上記は最も簡単な例ですが、GETまたはPOSTでTaskQueueにパラメータを渡すこともできます。

    // GET
    queue.add(TaskOptions.Builder.withUrl("/queue/asynkTask?p1=aaa&amp;p2=bbb").method(Method.GET));
    // POST
    queue.add(TaskOptions.Builder.withUrl("/queue/asynkTask").param("p1", "aaa").param("p2", "bbb").method(Method.POST));
     
    // オブジェクトも渡せる(未検証)
    // Serializeインターフェースを実装してるオブジェクトかつPOSTである必要がある
    List strList = Arrays.asList("1","2","3");
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(strList);
    // サーブレット側ではバイト配列からオブジェクトをデシリアライズする
    queue.add(TaskOptions.Builder.withUrl("/queue/asynkTask").param("p1", baos.toByteArray()).method(Method.POST));

    ・デフォルトキュー
    デフォルトキューはWEB-INF/queue.xmlの存在を必要としません。
    queue.xmlが存在しない場合のデフォルトキューは毎秒5件でキューに積まれた処理を実行します。
    もしデフォルトキューの設定を変更する場合はqueue.xmlを作成してname要素を”default”にします。

    ・設定値の詳細
    queue.xmlで設定できる内容です。
    name…キューの名称。QueueFactoryからキューを取得するときのキー名を設定します。
    rate…10/sの場合だと1秒間に10回タスクが実行されます。
    bucket-size…タスクを溜めておける量です。bucket-sizeを超過したタスクは、bucketに空きが出るまで待ち状態になります。
    retry-parameter…タスクが失敗した際のリトライ回数を設定できます。

    下記のサイトにてとても親切に和訳されていました。
    http://elekmole.blogspot.com/2011/02/java-task-queue-configuration.html

    ・Google App Engine SDK1.5から追加された機能

    プルキュー…今まではGoogleAppEngineがキューの制御(タスクのレート制御)を行っていましたが、
    プログラムで任意にキューの実行を制御できるようになりました。
    http://code.google.com/intl/en/appengine/docs/java/taskqueue/overview-pull.html
    REST-API…TaskQueueにRESTでアクセスできるようになりました。
    http://code.google.com/intl/en/appengine/docs/java/taskqueue/rest.html#method_taskqueue_tasks_list
    その他…Cronも含めたバックグランド処理は上限時間が24hになったそうです。

    佐々木 康幸

    ・所属 システム部 ・役職 マネージャ ・関心事 Scala,Groovy,Java, Slim3,Wicket,Google App Engine,Gaelyk ・主な仕事 市場系金融システム開発

     
  • Google App Engine ~ Cronでメール配信

    このエントリーを含むはてなブックマークはてなブックマーク - Google App Engine ~ Cronでメール配信 この記事をクリップ!Livedoorクリップ - Google App Engine ~ Cronでメール配信 Yahoo!ブックマークに登録 @niftyクリップに追加 Share on Tumblr FC2ブックマークへ追加 newsing it! Googleブックマークに追加 Bookmark this on Delicious FriendFeedで共有 このエントリをつぶやくこのWebページのtweets

    ご無沙汰してましたm(_ _)m

    先月1児のパパになったシステム部の佐々木です。

    本日はGoogle App EngineのCronサービスとメール配信についてつらつらと書いていきたいと思います。

    Unix/Linuxをご存じの方には説明不要かと思われますが、そうです。あのCronです。

    データベースの定期監視を行う場合など、非クラウド環境のJavaアプリケーションでは非同期スレッドを生成してsleepしながらデータベースを監視するというのが定石だったかと思いますが、GAEではそもそもスレッドの生成ができません。

    これの代わりにGoogle App EngineではCronサービスを使用します。

    それではCronサービスを使用して、定期的にメールを送信する方法を説明していきます。

    1.ジョブの作成

    まずは定時実行されるジョブを作ります。とはいっても作るのは単なるサーブレットです。
    Google App EngineのCronサービスはURLに対してHttpGETリクエストを放ることでジョブの実行を行っているので「ジョブ=サーブレット」ということになります。

    Pureサーブレットでもよいのですが、筆者がSlim3を使用しているので、build.xmlの”gen-controller-without-view”からコントローラを生成します。
    ここではURL”/cron/cronTest”としてCronTestController.javaを生成しました。

    package com.sample.controller.cron;

    import java.util.Date;
    import java.util.Enumeration;
    import java.util.logging.Logger;
     
    import org.slim3.controller.Controller;
    import org.slim3.controller.Navigation;
     
    import com.google.appengine.api.mail.MailService;
    import com.google.appengine.api.mail.MailServiceFactory;
    import com.google.appengine.api.mail.MailService.Message;
     
    public class CronTestController extends Controller {
     
        @Override
        public Navigation run() throws Exception {
     
            // MailService
            MailService service = MailServiceFactory.getMailService();
            // Message
            Message message = new Message();
            // 送信者は必ずアプリケーション管理者
            message.setSender("rl.ysasaki@gmail.com");
            // to
            message.setTo("y-sasaki@r-learning.co.jp");
            // タイトル
            message.setSubject("Cronテスト");
            // 本文
            message.setTextBody("メール送信テスト:" + new Date().toString());
            // 送信
            service.send(message);
            return null;
        }
    }

    処理中では低レベルのメールAPIを使用してメール送信を行っています。
    ネット上で公開されているサンプルはjavax.mailを使用したサンプルが多いですが、低レベルAPIの方が簡単なのでこっちで書いてます。
    http://code.google.com/intl/ja/appengine/docs/java/javadoc/com/google/appengine/api/mail/package-summary.html

    2.ジョブのテスト

    まずは作ったジョブが動くかどうかですね。メール送信はローカルでは確認できないのでGoogle App Engineにアプリケーションをデプロイします。
    デプロイ後、ブラウザからhttp://アプリケーションホスト/cron/cronTestにアクセスし、メールが送信されればテストは完了・・・・・
    とはいかないのです。
    このままでは一般ユーザがURLにアクセスした場合に、不正なジョブの実行になってしまいます。
    これを回避するために、adminユーザ以外のアクセスを禁止する必要があります。
    一般ユーザのアクセスを禁止するにはwar/WEB-INF/web.xmlに以下の記述を追加します。

        <security-constraint>
            <web-resource-collection>
                <url-pattern>/cron/*</url-pattern>
            </web-resource-collection>
            <auth-constraint>
                <role-name>admin</role-name>
            </auth-constraint>
        </security-constraint>

    これで管理者以外は”/cron/*”のURLにアクセスできなくなりました。

    3.ジョブの登録

    ジョブの登録はwar/WEB-INF/cron.xmlに以下の内容を記述し、デプロイしたら完了です。

    <?xml version="1.0" encoding="UTF-8"?>
    <cronentries>
    	<cron>
    		<url>/cron/cronTest</url>
    		<description>CronTest And MailService</description>
    		<schedule>every 1 minutes</schedule>
    		<timezone>Asia/Tokyo</timezone>
    	</cron>
    </cronentries>
    • url・・・実行するジョブのURL
    • schedule・・・ジョブの起動タイミング(例は毎分)
    • timezone・・・デフォルトはUTCなので日本時間でジョブ実行する場合は”Asia/Tokyo”を指定

    Scheduleの書き方
    http://code.google.com/intl/ja/appengine/docs/java/config/cron.html#The_Schedule_Format

    Timezone
    http://en.wikipedia.org/wiki/List_of_zoneinfo_time_zones

    4.ジョブの解除

    cron.xmlの<cron>要素を消してデプロイし直せばジョブは解除されます。
    また、Google App Engineの管理コンソールから、登録されているCronの状態(Cron Jobs)を確認することもできます。

    次回はTaskQueueについて書きます。

    佐々木 康幸

    ・所属 システム部 ・役職 マネージャ ・関心事 Scala,Groovy,Java, Slim3,Wicket,Google App Engine,Gaelyk ・主な仕事 市場系金融システム開発

     
  • GAE用フルスタック・フレームワーク ~ Slim3のご紹介

    このエントリーを含むはてなブックマークはてなブックマーク - GAE用フルスタック・フレームワーク ~ Slim3のご紹介 この記事をクリップ!Livedoorクリップ - GAE用フルスタック・フレームワーク ~ Slim3のご紹介 Yahoo!ブックマークに登録 @niftyクリップに追加 Share on Tumblr FC2ブックマークへ追加 newsing it! Googleブックマークに追加 Bookmark this on Delicious FriendFeedで共有 このエントリをつぶやくこのWebページのtweets

    こんばんは。システムの佐々木です。

    本日はGoogle App Engine用フルスタックフレームワークである「Slim3」についてご紹介します。

    Slim3とは…

    Slim3はGAE用に作成された非常に軽量なWEBフレームワークです。
    元々はSeasar2の後継として立ち上がったのですが、途中からGAE用に特化し、今の形になります。
    名前の由来はSimple And “Less is more”でとにかくシンプルなのがコンセプト。

    特筆すべき機能は下記の4点。

    • HotReloading
    • Global Transactions
    • Fast Spin-up
    • Faster than JDO/JPA
    • Type Safe Query

    HotReloading

    わかる人にいうとSAStrutsのホットデプロイ。
    わからない人にいうと、APサーバーの再起動なしにモジュールの修正を即座に反映してくれる機能です。
    コード修正してすぐさまブラウザで確認できるのでスクリプトによるWEB開発のようにサクサク開発を行うことができます。

    Global Transactions

    過去に掲載した記事にて、データストアのトランザクション範囲は「単一のエンティティグループ」に限定されると書きましたが、Slim3のデータストア機能を使用すると複数エンティティグループ間もトランザクション制御をすることが可能になります。

    Fast Spin-up

    通常のWEBアプリケーションはサーバを起動したり、デプロイしたらアプリケーションは立ちあがったままですね。
    GAEでは初めてアクセスがあった時に、アプリケーションのインスタンスが起動します(Spin-up)。
    そしてアプリケーションに対して一定期間(2,3分?)アクセスがなくなるとインスタンスが停止します(Spin-down)。

    また、GAEには30秒制限(リクエストタイムアウト)があります。これにはSpin-upしている時間も含まれています。
    クライアントからアクセスした際にSpin-upで30秒かかってしまったらもうそのアプリケーションは動きません(笑)
    仮にSpin-upを10秒にしたところで立ち上がるのに10秒かかるWEBアプリケーションなんて誰が好き好んで使うでしょう。
    Spin-upは3秒以内に!

    Slim3はそのあたりが考慮をされていて、フルスタックなのに稀にみる軽量さです。
    http://shin1o.blogspot.com/2010/02/slim3java-appengine-spinup.html

    また、Spin-up問題を解決するには軽量なフレームワークを使用する他、ServletContextInitializerなどの処理は行わないなどできるだけ早くインスタンスが立ち上がるような考慮が必要です。

    Faster than JDO/JPA

    GAEでデータストアにアクセスする際の標準的な方法としてLowLevelAPIの他、ORMであるJDO,JPAがあります。
    しかし、JDO/JPAは遅いんです。(下記のオンラインデモから確認できます)
    http://slim3demo.appspot.com/performance/

    LowLevelAPIは段違いで速いのですが、以前の記事で紹介したとおり、コードがやや冗長になります。
    生産性をあげるにはORMという選択肢があるのですが、JDO/JPAは遅いんです。
    理由としてJDO/JPAは実行時リフレクションでデータとオブジェクトのマッピングを行いますが、Slim3ではこのマッピング手続きをオブジェクトのコンパイル時にコード生成という形で行います。
    Javaのリフレクションは遅いということが明白なのでリフレクションしないSlim3の方が早いってわけです。

    Type Safe Query

    SQLとか手書きするとよくタイプミスしますよね?LowLevelAPIであっても、プロパティ名やカインド名はタイプセーフではありません。
    Slim3ではEclipseのサジェスト(CTRL+Space)を駆使してクエリを作成するのでクエリのタイプミスなんてことは起こらなくなります。

    その他

    その他に、URLからビューとコントローラ、モデルのひな形を作成してくれるAntスクリプトがあったり、GAE上でテストできるktrwjr(Kotori Web Junit Runner)、 Railsのrouter.rbのようなURLルーティングも可能です。

    Eclipse plugin

    本家から正式にpluginも出てます。GWTやScenicにも対応していますよー

    書籍

    Slim3の書籍は2冊ほどあります。
    オープンソース徹底活用 Slim3 on Google App Engine for Java
    オープンソース徹底活用 Slim3によるWebアプリケーション開発

    が、本家のサイトもかなりの充実っぷりなので個人的にはいらないかも 。
    (機能も多くないので覚えることも少ないし)
    http://sites.google.com/site/slim3documentja/

    佐々木 康幸

    ・所属 システム部 ・役職 マネージャ ・関心事 Scala,Groovy,Java, Slim3,Wicket,Google App Engine,Gaelyk ・主な仕事 市場系金融システム開発

     
  • Google App Engine ~ データストアまとめ

    このエントリーを含むはてなブックマークはてなブックマーク - Google App Engine ~ データストアまとめ この記事をクリップ!Livedoorクリップ - Google App Engine ~ データストアまとめ Yahoo!ブックマークに登録 @niftyクリップに追加 Share on Tumblr FC2ブックマークへ追加 newsing it! Googleブックマークに追加 Bookmark this on Delicious FriendFeedで共有 このエントリをつぶやくこのWebページのtweets

    こんばんは。システム部の佐々木です。

    前回分でGAE/J データストアについて投稿が終了したので今回はまとめです。
    前回までの投稿はこちら。

    Google App Engine ~ データストアAPIで簡単CRUD

    Google App Engine ~ エンティティグループとトランザクション

    Google App Engine ~ クエリとその制限

    Google App Engine ~ インデックスの仕組み

    また、ブログ中には記載はなかったのですが、データストアには他にも制限があります。
    (制限に関する内容ばかりで申し訳ないのですが、、、^^;)

    1.1回のクエリで取得できるエンティティ数は1000件まで。
    http://code.google.com/intl/en/appengine/docs/java/datastore/overview.html

    2.ストレージサイズ、APIのCall回数などに制限があります。
    http://code.google.com/intl/en/appengine/docs/quotas.html#Datastore

    また、Google App Engineの日本語サイトは更新が進んでいないため、誤った情報が数多くあります。(特にリソースまわり、制限に関する記述) そのため、しっかり調べたい場合は英語版のサイトを参照するようにするのが吉です。

    フレームワークについて

    今回はデータストアについてなるべく分かり易く解説するため、Low Level APIを用いましたが、スムーズに開発を行うためには、やはりフレームワークが必要になるでしょう。
    前回の投稿までに作成したサンプルはデータストア周りはLow Level APIを用いましたが、サーブレット周りはSlim3を使用して検証を行っていました。
    http://sites.google.com/site/slim3documentja/

    Slim3はもの凄くシンプルで軽く、Google App Engine for Javaに最も適したフルスタックフレームワークであると私は思っています。むしろGoogle App Engineのためにあるフレームワークでしょう。
    ということで次回はSlim3を使いたくなる記事を投稿したいと思います。

    佐々木 康幸

    ・所属 システム部 ・役職 マネージャ ・関心事 Scala,Groovy,Java, Slim3,Wicket,Google App Engine,Gaelyk ・主な仕事 市場系金融システム開発

     
  • Google App Engine ~ インデックスの仕組み

    このエントリーを含むはてなブックマークはてなブックマーク - Google App Engine ~ インデックスの仕組み この記事をクリップ!Livedoorクリップ - Google App Engine ~ インデックスの仕組み Yahoo!ブックマークに登録 @niftyクリップに追加 Share on Tumblr FC2ブックマークへ追加 newsing it! Googleブックマークに追加 Bookmark this on Delicious FriendFeedで共有 このエントリをつぶやくこのWebページのtweets

    こんばんは。システム部の佐々木です。

    本日はデータストアのインデックスの仕組みについて解説します。

    永続化の仕組み

    まず、プログラムにて実行したデータ操作がどのようにしてデータストアに永続化されるかについて簡単に解説したいと思います。


    上図よりプログラム上で作成されたデータは永続化の前にプロトコル・バッファフォーマットにシリアライズされます。
    シリアライズされたデータはEntityTableに格納されます。EntityTableは単なるデータの格納庫です。
    検索を行ったり、ソートを行うための情報は全てインデックステーブルに格納されます。
    GAEのデータストアは下記の7つのテーブルによって構成されます。

    • Entities Table
    • EntitiesByKind Table
    • EntitiesByProperty ASC Table
    • EntitiesByProperty DESC Table
    • EntitiesByCompositeProperty Table
    • Custom indexes Table
    • Id sequences Table

    上記テーブルのうち、Entities TableとId sequences Table以外はすべてインデックステーブルです。
    データストアでは、永続化の際に全クエリの結果をインデックステーブルに格納します。つまり、検索結果が予め用意されているということです。

    Entities Table

    永続化されたデータそのものを格納するテーブルです。このテーブルは下記のような情報を持ちます。

    • キー
      AppID(アプリの識別子)+パス(Kind+ID or キー名)
    • プロパティ
      シリアライズされたプロパティ名+プロパティ値
    • メタデータ
      ルートエンティティキー、Kind
    • カスタムインデックスデータ
      Custom indexes Tableから参照されるインデックスID、祖先エンティティ

    EntitiesByKind Table

    Kindを指定して永続化された場合に生成されるインデックステーブルです。
    このテーブルはKindとキーによって構成されます。
    Kindのみを指定したクエリはこのインデックステーブルを用いてEntities Tableのエンティティを特定します。

    EntitiesByProperty ASC/DESC Table

    データストアでの並び替えは、クエリ実行時ではなく、永続化の際にインデックステーブル上でデータがソートされます。
    また、単一プロパティに対するスキャンもこのインデックステーブルを使用します。例を見てみましょう。

    例1) 複数の等式フィルタを使用する場合
    例えば、name=”Jiro”、birthDay=”1986/02/01″を指定したクエリを実行する場合、Emp/nameの先頭インデックスからシークを始めます。それと同時にEmp/birthDayの先頭インデックスからシークを始めます。(MapReduce技術による複数プロパティの並行スキャン)
    両方のシークが完了すると、スキャン結果を突合(マージジョイン)させてキー”0002″を特定します。
    仮にいずれか一方の等式フィルタが該当しない場合は、マージジョインにマッチするキーが存在しないため、キー”0002″は特定できないことになります。
    例2)不等式フィルタを使用する場合
    Emp/birthDayに対して”1986/01/01~1987/04/01″の範囲を指定したクエリを実行してみます。
    例1と同様に、「birthDay>”1986/01/01″」と「birthDay>”1987/04/01″」が並行スキャンされます。
    スキャン終了後、マージジョインを行うとキー”0002″,”0003″が取得できます。
    また、例2のようなクエリを実行すると取得結果はbirthDayによって昇順ソートされてます。
    これは、SortDirectionを指定しない場合はEntitiesByProperty ASC Tableが使用されていることを指しています。
    クエリに降順を指定している場合は、 EntitiesByProperty DESC Tableを指定してインデックススキャンを行うことになります。
    Id sequences Table
    エンティティなどに付与されるID採番用テーブルです。開発者自身が考慮する必要はないようです。
    EntitiesByCompositeProperty Table/Custom indexes Table
    こちらは特別なインデックステーブルです。
    データストアでは単純なクエリに対するインデックスは全てデータストアが自動生成してくれます。
    一方、複雑なクエリに対するインデックスは開発者が自身でGoogle App Engineに登録する必要があります。
    App Engine は次のような形式のクエリに対するインデックスを自動生成します。
    • フィルタとソート順を使用しないクエリ
    • 等式フィルタと祖先フィルタのみを使用するクエリ
    • 単一のプロパティに対する不等式フィルタのみを使用するクエリ
    • フィルタなしで、プロパティに昇順か降順のどちらかのソート順が設定されているクエリ
    • 等式フィルタをプロパティに使用して、不等式または範囲フィルタをキーに使用しているクエリ

    次のような形式のクエリは datastore-indexes.xmlまたはdatastore-indexes-auto.xml にインデックスの指定が必要です。

    • 複数のソート順を持つクエリ
    • キーに対する降順のソート順を持つクエリ
    • あるプロパティに対して不等式フィルタが1つ以上設定されていて、別のプロパティに対して等式フィルタが1つ以上設定されているクエリ
    • 不等式フィルタと祖先フィルタを持つクエリ

    datastore-indexes.xmlはwar/WEB-INF/appengine-generated以下に格納されています。例として、
    ・第1ソートプロパティ ”price”の降順
    ・第2ソートプロパティ ”cd”の降順
    のクエリを実行する場合、以下のようなインデックス定義が必要になります。

    <datastore-indexes>
        <datastore-index kind="Goods" ancestor="false" source="auto">
            <property name="price" direction="desc"/>
            <property name="cd" direction="desc"/>
        </datastore-index>
    </datastore-indexes>

    このインデックス定義が存在しない場合、AppEngine上で以下のような例外が発生します。

    com.google.appengine.api.datastore.DatastoreNeedIndexException: no matching index found..
    <datastore-indexes>
        <datastore-index kind="Goods" ancestor="false" source="manual">
            <property name="price" direction="desc"/>
            <property name="cd" direction="desc"/>
        </datastore-index>
    </datastore-indexes>

    ただ、これらの定義をクエリ毎に作成するのは面倒ですね。ですのでGoogle App Engine SDKでは、これらのコンポジット・カスタムインデックスを自動生成してくれる仕組みを提供しています。
    上で示したXMLは、ローカルサーバにてクエリを実行した際に自動的に生成されたインデックス定義です。
    自動生成されたインデックスはwar/WEB-INF/appengine-generatedディレクトリのdatastore-indexes-auto.xmlに自動的に書きこまれます。
    あとは通常どおり、App Engineにデプロイすればdatastore-indexes-auto.xmlからコンポジット・カスタムインデックスが自動生成され、問題なくクエリを実行することができます。

    インデックス爆発

    データストアにはインデックスのエントリー制限があります。その数5000エントリ。
    インデックス数が5000エントリを超えるとそのインデックスに対するクエリが実行できなくなってしまいます。
    とは言ってもそうそう起きるものでもありません。これまで例で示したようなデータでは複合インデックスを織り交ぜたとしても、たかだか数は知れています。

    インデックス爆発は下記のようなケースでおきます。

    • リストプロパティに対して複合インデックスを指定

    データストアは全ての組み合わせをインデックスにエントリする必要があるため、上記の場合は3×2の6パターンの複合インデックスが必要になります。
    仮に3つのリストプロパティにそれぞれ18個の値がある場合、18^3=5832(エントリ)となりインデックス爆発が発生します。

    また、インデックスが多いエンティティは更新の度にインデックスの更新を多く行う必要があるため、1エンティティに対するインデックスはなるべく少ない方がクエリのパフォーマンスが上がります。

    補足 インデックスが作成される値型

    GAE/Jでサポートされる値型のうちText,Blob以外はすべてインデックスの作成対象です。
    http://code.google.com/intl/ja/appengine/docs/java/datastore/dataclasses.html#Core_Value_Types

    不要なインデックス作成を避ける場合はEntity#setUnindexedProperty(String,Object)を使用します。
    インデックス生成を最小限に留めることでインデックス生成にかかるコストを削減し、パフォーマンスアップにつながります。


    佐々木 康幸

    ・所属 システム部 ・役職 マネージャ ・関心事 Scala,Groovy,Java, Slim3,Wicket,Google App Engine,Gaelyk ・主な仕事 市場系金融システム開発

     
  • Google App Engine ~ クエリとその制限

    このエントリーを含むはてなブックマークはてなブックマーク - Google App Engine ~ クエリとその制限 この記事をクリップ!Livedoorクリップ - Google App Engine ~ クエリとその制限 Yahoo!ブックマークに登録 @niftyクリップに追加 Share on Tumblr FC2ブックマークへ追加 newsing it! Googleブックマークに追加 Bookmark this on Delicious FriendFeedで共有 このエントリをつぶやくこのWebページのtweets

    こんにちは。システム部の佐々木です。
    本日は、データストアのクエリ(検索)について解説します。

    フィルタ

    クエリに対してフィルタを指定することができます。
    フィルタを指定するには以下のようにします。

    Query query = new Query("Goods");
    query.addFilter("price", FilterOperator.GREATER_THAN, "1000");
    query.addFilter("price", FilterOperator.LESS_THAN, "10000");

    このクエリを実行した場合、Kind”Goods”の中からプロパティ”price”が1000より大きく、10000より小さいエンティティを取得することができます。
    また、フィルタの種類はFilterOperatorによって指定可能です。詳しくはJavadocを参照。

    ソート

    クエリに対して降順、昇順を指定することができます。
    ソート順を指定するには以下のようにします。

    Query query = new Query("Goods");
    query.addSort("price", SortDirection.DESCENDING); // priceの降順に並べ替え

    また、SortDirection.ASCENDINGを指定することで昇順に並び換えることもできます。

    フェッチオプション

    ・オフセット
    クエリの結果を先頭行からオフセット数分スキップします。
    ・リミット
    クエリの結果取得数を制限します。オフセットにリミットのスキップ分は含まれません。
    ページング処理を行う場合はフェッチオプションを以下のように指定することで実現可能です。

    PreparedQuery prepare = DatastoreServiceFactory.getDatastoreService().prepare(new Query("Goods");
    // Page数*10をスキップして最大10件取得する。
    FetchOptions fetch = FetchOptions.Builder.withOffset({Page数}*10).limit(10); // {Page数}はリクエストページ数
    List list = prepare.asList(fetch);

    ここまでの説明を踏まえてQueryクラスを使い易いように軽くラップしました。
    毎度毎度addFilter,addSortしてたら面倒ですよねってことで作成したのが下記のソース。
    フィルタ、ソート、フェッチ時は自身のインスタンスを返すようにしているので
    new CustomQuery(“Goods”).gt(“price”, 1000).lt(“price”, 10000).asc(“price”).asc(“cd”).offset(10).limit(10);
    のようにチェインすることもできます。

    package com.sample.util;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import com.google.appengine.api.datastore.DatastoreServiceFactory;
    import com.google.appengine.api.datastore.Entity;
    import com.google.appengine.api.datastore.FetchOptions;
    import com.google.appengine.api.datastore.Key;
    import com.google.appengine.api.datastore.PreparedQuery;
    import com.google.appengine.api.datastore.Query;
    import com.google.appengine.api.datastore.Query.FilterOperator;
    import com.google.appengine.api.datastore.Query.SortDirection;
     
    public class CustomQuery {
     
        private Query query;
        private FetchOptions fetchOptions = null;
     
        public CustomQuery(String kind) {
            query = new Query(kind);
        }
        public CustomQuery(String kind, Key key) {
            query = new Query(kind, key);
        }
     
        public CustomQuery eq(String key, Object value) {
            query.addFilter(key, FilterOperator.EQUAL, value);
            return this;
        }
     
        public CustomQuery ne(String key, Object value) {
            query.addFilter(key, FilterOperator.NOT_EQUAL, value);
            return this;
        }
     
        public CustomQuery lt(String key, Object value) {
            query.addFilter(key, FilterOperator.LESS_THAN, value);
            return this;
        }
     
        public CustomQuery le(String key, Object value) {
            query.addFilter(key, FilterOperator.LESS_THAN_OR_EQUAL, value);
            return this;
        }
     
        public CustomQuery gt(String key, Object value) {
            query.addFilter(key, FilterOperator.GREATER_THAN, value);
            return this;
        }
     
        public CustomQuery ge(String key, Object value) {
            query.addFilter(key, FilterOperator.GREATER_THAN_OR_EQUAL, value);
            return this;
        }
     
        public CustomQuery In(String key, Object value) {
            query.addFilter(key, FilterOperator.IN, value);
            return this;
        }
     
        public CustomQuery limit(int limit) {
            if (fetchOptions == null) {
                fetchOptions = FetchOptions.Builder.withLimit(limit);
            } else {
                fetchOptions.limit(limit);
            }
            return this;
        }
     
        public CustomQuery offset(int offset) {
            if (fetchOptions == null) {
                fetchOptions = FetchOptions.Builder.withOffset(offset);
            } else {
                fetchOptions.offset(offset);
            }
            return this;
        }
     
        public CustomQuery asc(String key) {
            query = query.addSort(key, SortDirection.ASCENDING);
            return this;
        }
        public CustomQuery desc(String key) {
            query = query.addSort(key, SortDirection.DESCENDING);
            return this;
        }
     
        public Entity asSingle() {
            return DatastoreServiceFactory.getDatastoreService().prepare(query).asSingleEntity();
        }
     
        public List asList() {
     
            PreparedQuery q = DatastoreServiceFactory.getDatastoreService().prepare(query);
     
            Iterator it = null;
            if (fetchOptions == null) {
                it = q.asIterator();
                List results = new ArrayList();
                while (it.hasNext()) {
                    results.add(it.next());
                }
                return results;
            } else {
                return q.asList(fetchOptions);
            }
        }
    }

    クエリに対する制限

    参考サイト:
    http://code.google.com/intl/ja/appengine/docs/python/datastore/queriesandindexes.html
    ・プロパティをフィルタまたは並び替えするには、プロパティが存在することが必要
    当たり前といえば当たり前ですね。
    おそらくKindを指定しないクエリに対してフィルタする場合に気をつけるべきなんだろうけど、そもそもKind指定しないクエリがどういうケースで必要になるか想像できない。頭の片隅に。。。
    ・プロパティを持たないエンティティに一致するフィルタはない
    つまりこーゆーこと↓。こんなエンティティ永続化しないので頭の片隅に。。。

    Entity en = new Entity("hoge");
    store.put(en);

    ・不等式フィルタが使用できるのは 1 つのプロパティに限られる
    クエリが不等式フィルタ(<、<=、>=、>、!=)を使用できるのは、すべてのフィルタにわたって 1 つのプロパティに限られます。
    下記のソースを見てみましょう。

    Query query = new Query("Goods");
    query.addFilter("date", FilterOperator.GREATER_THAN, "2011/05/11");
    query.addFilter("price", FilterOperator.LESS_THAN, "10000");

    このように複数のプロパティに対して不等式フィルタを設定し、クエリを実行すると下記の例外が発生します。
    (等式フィルタ”FilterOperator.EQUOL”はいくつ指定しても問題ありません)

    java.lang.IllegalArgumentException: Only one inequality filter per query is supported.
    Encountered both date and price

    ・他の並び替え順序より先に、不等式フィルタのプロパティを並び替える必要がある
    下記のソースを見てみましょう。このクエリは有効ではありません。

    Query query = new Query("Goods");
    query.addFilter("date", FilterOperator.GREATER_THAN, "2011/05/11"); // 不等式フィルタ
    query.addSort("price", SortDirection.DESCENDING);
    query.addSort("date", SortDirection.DESCENDING);

    このクエリを実行した場合、下記の例外が発生します。

    java.lang.IllegalArgumentException: The first sort property must be the same as the property to which the inequality filter is applied.
    In your query the first sort property is price but the inequality filter is on date

    これを回避するには”price”と”date”の並び換え順序を逆にします。

    Query query = new Query("Goods");
    query.addFilter("date", FilterOperator.GREATER_THAN, "2011/05/11");
    query.addSort("date", SortDirection.DESCENDING);
    query.addSort("price", SortDirection.DESCENDING);

    クエリに不等式比較のフィルタと 1 つ以上の並び替え順序がある場合、クエリは不等式に使用されるプロパティの並び替え順序を含んでいる必要があり、この並び替え順序は他のプロパティに対する並び替え順序より先に出現する必要があります。
    ・複数の値での並び替え順序とプロパティ
    複数の値のプロパティのインデックス方法は通常とは異なるため、これらのプロパティの並び替え順序は通常とは異なります。

    • エンティティが複数の値のプロパティを基準にして昇順に並び替えられる場合、並び替えに使用される値は最小値になります。
    • エンティティが複数の値のプロパティを基準にして降順に並び替えられる場合、並び替えに使用される値は最も大きな値になります。
    • その他の値は並び替え順序や値の数に影響しません。
    • 要素の大きさが同じ場合、エンティティのキーはタイブレーカとして使用されます。(エンティティーキーで並び換えされる)

    この並び替え順序は通常とは異なった結果となり、昇順および降順ともに [1, 9] が [4, 5, 6, 7] より先になります。
    上記の説明をソースにするとこんな感じになります。

    Entity en1 = new Entity("hoge");
    en1.setProperty("list", Arrays.asList(1,9)); // リストプロパティ
     
    Entity en2 = new Entity("hoge");
    en2.setProperty("list", Arrays.asList(4,5,6,7)); // リストプロパティ
     
    ・・・永続化
     
    Query query1 = new Query("hoge");
    query1.addSort("list", SortDirection.ASCENDING);
    ・・・クエリ実行→最小値(en1は1,en2は4)でソートされるためen1が先頭
     
    Query query2 = new Query("hoge");
    query2.addSort("list", SortDirection.DESCENDING);
    	・・・クエリ実行→最大値(en1は9,en2は5)でソートされるためen1が先頭

    また、リストプロパティに等式フィルタと並び換え順序の両方をしたクエリを実行する場合、並び換え順序は無視されます。

    次回はデータストアのインデックスメカニズムについて解説します。

    佐々木 康幸

    ・所属 システム部 ・役職 マネージャ ・関心事 Scala,Groovy,Java, Slim3,Wicket,Google App Engine,Gaelyk ・主な仕事 市場系金融システム開発

     
  • Google App Engine ~ エンティティグループとトランザクション

    このエントリーを含むはてなブックマークはてなブックマーク - Google App Engine ~ エンティティグループとトランザクション この記事をクリップ!Livedoorクリップ - Google App Engine ~ エンティティグループとトランザクション Yahoo!ブックマークに登録 @niftyクリップに追加 Share on Tumblr FC2ブックマークへ追加 newsing it! Googleブックマークに追加 Bookmark this on Delicious FriendFeedで共有 このエントリをつぶやくこのWebページのtweets

    こんばんは。システム部の佐々木です。

    本日は、データストアAPI エンティティグループとトランザクションについて解説します。(連続更新です!)

    エンティティグループ

    永続化するエンティティは親子関係を持つことができます。また、親が存在しないエンティティはルートエンティティと呼ばれ、親キー(Ancester Key)から生成されるエンティティは子エンティティと呼ばれます。

    トランザクション

    データストアAPIのトランザクションは同一エンティティグループにしか適用できません(これをローカルトランザクションと呼びます)。
    エンティティグループを跨ぐようなグローバルトランザクションはアプリケーションレベルでの実装が必要です。
    (Slim3 DatastoreAPIはグローバルトランザクションに対応)

    下記の画像のようなデータを登録してみましょう。よくある1対多の関連です。

    図のようなデータを登録するコードは下記のとおりです。

    Transaction tx = store.beginTransaction(); // トランザクション開始
     
    // ルートエンティティを登録
    Entity parent = new Entity("Contract");
    parent.setProperty("userId", userId);
    parent.setProperty("userName", userName);
    Key key = store.put(tx, parent);
     
    // 子エンティティを登録
    ArrayList child = new ArrayList();
    Entity childEntity = new Entity("ContractChild", key); // 祖先キーを指定してエンティティ生成
    for (int i = 0; i < 3; i++) {
        Entity entity = childEntity.clone();
        entity.setProperty("plan", "plan" + (i+1));
        child.add(entity);
    }
    store.put(tx, child);
    tx.commit(); // トランザクション終了

    まずはルートエンティティとなる契約情報を登録します。ルートエンティティが登録されるとそのキーが取得できますのでそのキーをもって子エンティティを登録します。
    上記のコードはルート、子エンティティ共に登録の際にキーが自動付与されます。
    このキーを指定したい場合(一意なユーザIDやメールアドレスなど)は、コンストラクタ引数に任意キーを指定するものがあるのでそちらをご使用ください。
    また、ローカルトランザクションを実装しているので、処理の途中で例外が発生する場合は、データの更新が全てロールバックされます。

    また、異なるエンティティグループでトランザクションを行った場合は下記のような例外が発生します。

    java.lang.IllegalArgumentException: can't operate on multiple entity groups in a single transaction.
    単一トランザクションでは複数のエンティティグループに対する操作はできませんって言ってますね。

    実行後のダッシュボードを見てみましょう。子エンティティキーの先頭部分が”祖先キー”であることがわかります。

    キーを指定したスキャン

    エンティティグループ内で永続化されたエンティティは、祖先キーの指定によって対象を絞りこむことができます。

    下記のコードをご覧ください。

    Query query = new Query("Contract"); // 親エンティティに対してのクエリ。ルートなので祖先キーなし
    query.addFilter("userId", FilterOperator.EQUAL, userId); // 一意にするため、userIdでフィルタ指定
    PreparedQuery prepare = store.prepare(query);
    Entity parent = prepare.asSingleEntity(); // 親エンティティを取得
    log.info("親エンティティ:" + parent.getProperty("userId"));
     
    Query childQuery = new Query("ContractChild", parent.getKey()); // 祖先キーを指定
    prepare = store.prepare(childQuery);
    Iterator<Entity> childs = prepare.asIterator(); // 親エンティティに紐づいたエンティティを取得
    while (childs.hasNext()) {
        log.info("プラン:" + childs.next().getProperty("plan"));
    }

    子エンティティを取得する際のQueryクラスのコンストラクタに祖先キーを指定しています。祖先キーを指定しない場合は指定した種類(ここではContractChild)内の全てのエンティティを返すことになります。

    補足1 親エンティティの削除

    エンティティグループ内ではエンティティに対して必ず親子関係が存在しますが、親エンティティをデータストアから削除したからといって子エンティティが自動で削除されるわけではありません。
    そういった実装を行う場合はアプリケーションレベルでの実装、もしくはJDOなどのフレームワークを使用する必要があります。

    補足2 エンティティグループとデータストアノード

    エンティティグループ内のエンティティ(ルート含む)は同一データストアノード内で永続化されます。これはエンティティグループ内のアトミック性を保ちやすくするためです。
    GFS(GoogleFileSystem)は分散並列処理が効率的に働くように、複数のエンティティグループ(もしくはルートエンティティ)を異なるデータストアノードに分散配置します。
    仮に2つの異なるエンティティグループに対してトランザクションを行う場合、2つのデータストアノードに跨るアトミック性を保証しなければなりません。また、データストアノードは別ディスクであることも考えられます。
    異なるディスクに対してのアトミック性保証は単一ディスクでの保障に比べると遥かにコストが高くなることは容易に想像がつくでしょう。
    GAEは高いスケーラビリティを維持するためにこのような制限を設けています。

    補足3 トランザクション制御を行う際の注意

    データストアのトランザクションはルートエンティティのTimestampを元に楽観ロックを行います。
    仮にあるエンティティグループに対して複数のユーザからのトランザクションが発生した場合、BigtableはRead Commited(コミットされるまでは更新前の値がRead可能)であるため、同一エンティティグループに対して複数のトランザクションが発生した場合、ロールバックが頻発する可能性があります。
    これを回避するためには、
    「1エンティティグループ:1ユーザ」のような関連を持ち、複数ユーザが同一エンティティグループに対して干渉しないようにします。
    また、エンティティの関連をできるだけ少なくすることにより、ルートエンティティが分散され、分散並列処理がより効果的に働きます。

    パフォーマンス関連の話はまた追々したいと思います。
    

    次回はクエリについての解説をしますね。
    (本当はデータストアのメインとなるインデックスの解説も織り交ぜたいのですが、筆者が未熟であるため、前半はクエリの説明、後半にインデックスの仕組みについて説明したいと思います)

    佐々木 康幸

    ・所属 システム部 ・役職 マネージャ ・関心事 Scala,Groovy,Java, Slim3,Wicket,Google App Engine,Gaelyk ・主な仕事 市場系金融システム開発

     
  • Google App Engine ~ データストアAPIで簡単CRUD

    このエントリーを含むはてなブックマークはてなブックマーク - Google App Engine ~ データストアAPIで簡単CRUD この記事をクリップ!Livedoorクリップ - Google App Engine ~ データストアAPIで簡単CRUD Yahoo!ブックマークに登録 @niftyクリップに追加 Share on Tumblr FC2ブックマークへ追加 newsing it! Googleブックマークに追加 Bookmark this on Delicious FriendFeedで共有 このエントリをつぶやくこのWebページのtweets

    本日はGAE/Jによるデータストア(Bigtable)の簡単なCRUDについて解説します。

    RDBとBigtable(キーバリューストア)

    本題に入る前にRDB(Relational Data Base)とBigtable(Key Value Store)の違いについて簡単に説明します。
    1.テーブル結合、集約関数(sum,max,min etc)、Group byなどが使えない

    多くのRDBがサポートするテーブル結合や集約関数、Group byが使えません。
    KVSは原則として「キーを指定して値を読み書きする」という単純な操作のみします。

    2.クエリの制限

    以下のようなことがBigtableではできません。

    • Likeによる部分一致(前方一致は可)
    • OR、NotEqualが指定できない
    • 不等式条件(<,<=,=>,>)を同時に複数のプロパティに指定できない(コンポジットインデックスの使用することで回避できるが。。)

    3.ハイスケーラビリティ

    ・レコード数は性能に影響しない
    RDBはテーブルのレコード数が増加するに連れ、処理時間も長くなっていきます。
    Bigtableはいくらレコードがあってもスキャンにかかる処理時間はかかりません。
    これはBigtableがGFS(GoogleFileSystem)上で動作し、MapReduceによって分散並列処理されているためです。
    Gmailの検索が速いのもこういった背景があるからなんですね。

    ・データ紛失が起きない
    BigTableへの書き込みはMapReduceの分散並列処理によって、複数のデータノードに高速レプリケートされます。
    そのため、いずれかのデータノードに障害が発生しても、他のデータノードからデータを瞬時に取得します。

    ・スケールアウトが容易
    従来のRDBではストレージ増設やクラスタソフトの導入が必要になるところが、Bigtableは課金すれば済みます。
    これはKVSという単純なデータ構造の上に成り立っています。

    4.トランザクションに制限

    Bigtableは高可用性であるがゆえにトランザクションに制限を設けています。具体的には

    • 悲観ロックできない
    • トランザクションはエンティティグループ内に閉じなければならない

    ※エンティティグループとトランザクションについては次回説明します。

    RDBより使いにくいという印象を受けるかもしれませんが、KVSの魅力はスケーラビリティにこそあります。
    日次で膨大な量のトランザクションが発生する場合、RDBではインスタンスを分けたり、日、時単位でパーティショニングしてパラレルクエリをごにょごにょ。。。
    私はそのあたりに関しては素人ですが、Bigtableはそれらを意識させることはありません。容量が足りなくなったらちょっとだけ、Googleさんにお小遣いをあげれば済むんです(笑)
    さて、これから実際にGAEのデータストアにデータを永続化してみます。
    データストアを使うには、何種類か方法があります。

    JDOとJPAは通常のWEBアプリケーションプロジェクトでも使用されるORMフレームワークです。
    一方、Slim3はS2Struts, SAStrutsのCoCを汲んだ、GAE向けのフルスタックフレームワークです。
    いずれもBigtableに対してORMでのアクセスを行いますが、BigtableはあくまでもKVSであることに注意です。
    手っとり早く仕組みを理解するにはLow Level APIが最も適しています。また、Low Level APIはBigtableにアクセスするための最もシンプルな仕組みを提供します。
    (JDOもJPAもSlim3もあくまでLow Level APIをラップしたAPI)

    Low Level APIを制するものはBigtableを制す!

    すみません。前おきが長くなりましたm(_ _)m
    これから簡単なCRUD(Create,Read,Update,Delete)の説明を行うにあたり、エンティティやプロパティ、キーなどという言葉を頻繁に使いますが
    RDBでは下記のような位置づけなので、読み替えてもらえばイメージがつきやすいと思います。

    Bigtable RDB
    カインド(Kind) テーブル(table)
    キー プライマリキー(主キー)
    エンティティ レコード(行)
    プロパティ カラム(列)

    A.CREATE

    DatastoreService store = DatastoreServiceFactory.getDatastoreService();
    Entity entity = new Entity("user");
    entity.setProperty("userId", "user1");
    entity.setProperty("name", "Taro");
    entity.setProperty("age", "16");
    entity.setProperty("date", new Date());
    store.put(entity);

    画像は上記のコードをGAE上で実行した後のダッシュボード(管理画面)です。Kind”user”に指定したプロパティと値が永続化されたのがわかります。
    ID/Nameは永続化の際に自動付与される一意キーです。Bigtable上のエンティティを特定する際はこのキーが使用されます。
    B.READ

    	PreparedQuery prepare = store.prepare(new Query("user"));
    	Iterator prepare.asIterator();

    Kind”user”へ永続化したすべてのエンティティをIteratorで返します。Queryはその名の通り、RDBでいうところのSQLのようなものです。
    また、PreparedQuery#asSingleEntity()は1件のエンティティを返します。仮に2件以上のエンティティが該当した場合はTooManyResultExceptionがthrowされるので注意。
    C.UPDATE

            Entity entity = singleRead();
            if (entity != null) {
                int age = Integer.parseInt((String) entity.getProperty("age")) + 1;
                entity.setProperty("age", String.valueOf(age));
                store.put(entity);
            }

    上記はエンティティを取得し、プロパティ”age”の値をインクリメントして永続化しています。
    Bigtableでは同一キーのエンティティにputした場合は上書きとみなされます。
    D.DELETE

            Entity entity = singleRead();
     
            if (entity != null) {
                store.delete(entity.getKey());
            }

    削除はいたって簡単です。削除対象のエンティティを取得し、そのキーをもってDatastoreService#deleteを呼びだすことで削除できます。
    上記コードを使用したサンプルをGAE上にデプロイしています。
    http://rl-gae-sample.appspot.com/datastore/ex1/read
    以外と簡単でしたね。
    キーバリューストアはRDBと違って非常に単純な仕組みで成り立っています。
    だからこそ、ハイスケーラビリティも発揮できるし、大規模なデータも容易に扱えるんですね。

    佐々木 康幸

    ・所属 システム部 ・役職 マネージャ ・関心事 Scala,Groovy,Java, Slim3,Wicket,Google App Engine,Gaelyk ・主な仕事 市場系金融システム開発

     
  • Google App Engine for Java を始める

    このエントリーを含むはてなブックマークはてなブックマーク - Google App Engine for Java を始める この記事をクリップ!Livedoorクリップ - Google App Engine for Java を始める Yahoo!ブックマークに登録 @niftyクリップに追加 Share on Tumblr FC2ブックマークへ追加 newsing it! Googleブックマークに追加 Bookmark this on Delicious FriendFeedで共有 このエントリをつぶやくこのWebページのtweets

    こんばんは。システム部マネージャの佐々木です。
    本日よりGAE/J(Google App Engine for Java)についてTips程度に色々書いていきます。


    GAEとは・・・

    Googleが提供するWEBアプリケーションをGoogleインフラストラクチャー上で稼動させるためのサービスの一つです。
    MicrosoftのWindows AzureやSalesforce.comのForce.comも同様ですが、このようなサービスをPaaS(Platform as a Service)と呼びます。
    PaaSはソフトウェアを稼動させるための土台となるプラットフォームを提供します。
    具体的にはWebサーバはアプリケーションサーバ、DBサーバなど、WEB開発に必要なものを提供してくれるのですが、自分で作ったアプリケーションをサーバとかDBの設定どうこう抜きに、インターネット上にサービスを提供できるものだと思っていただければ間違いないです(笑)


    Q1.どんなことができるの?

    A1.パソコン1台でWEBアプリケーション開発ができます。


    Q2.どんな言語を使うの?

    ・PythonもしくはJVM上で動作する言語であれば何でも
    (JVM上で動作する言語:Java,JRuby,Scala,Groovy etc)


    Q3.お金はかかるの?

    ・制限の範囲内であれば「無料」です。
    簡単なアプリケーションであればまず無料で構築できます。


    Q4.DBは何を使っているの?

    ・RDBは使っていません。GAEではBigtableというKVS(Key Value Store)を使用します。KVSについては追々サンプルを作成しながら説明していきます。



    興味のある方は下記のURLを参考に開発環境を構築してみましょう。
    http://www.atmarkit.co.jp/fjava/index/index_gaej.html
    次回からはGAE/Jの環境やAPIに関するTips等を更新していきます。
    たまに別分野に脱線するかもしれませんが、末永く宜しくお願いしますm(_ _)m

    佐々木 康幸

    ・所属 システム部 ・役職 マネージャ ・関心事 Scala,Groovy,Java, Slim3,Wicket,Google App Engine,Gaelyk ・主な仕事 市場系金融システム開発