ラベル Google Cloud Datastore の投稿を表示しています。 すべての投稿を表示
ラベル Google Cloud Datastore の投稿を表示しています。 すべての投稿を表示

2013年6月20日木曜日

38.Google Cloud Datastoreを試してみた その他環境編 (3/3)

このエントリーをはてなブックマークに追加



・33.Google Cloud Datastoreを試してみた 概要編 (1/3)
・37.Google Cloud Datastoreを試してみた GCE編 (2/3)
・38.Google Cloud Datastoreを試してみた その他環境編 (3/3)

Ryo Yamasaki(@vierjp)です。

前回に引き続き「Google Cloud Datastore」についてです。
第1回は概要や仕組み、注意点について、
第2回ではGoogle Compute Engineから実行してみました。

この第3回ではGoogleのクラウド環境以外から実行してみます。
さらに、私のローカル環境・GCE・GAE それぞれからDatastoreを操作した場合の実行時間の比較もしました。


「33.Google Cloud Datastoreを試してみた 概要編 (1/3)」に書いた
以下のユースケースではGoogleのクラウド環境以外からCloud Datastoreに接続します。

・ユースケース3 Googleのクラウド環境以外からDatastoreを利用する

第1回に書いたとおり「Google Cloud Datastore」はAPI経由で操作できるので、
Googleのクラウド環境以外からも利用が可能です。
Googleのクラウド外から接続する場合にも第2回の「サンプルプログラム」の項までは
同様に必要なので、そちらを参照してください。



◯Googleのクラウド外からAPIを利用するための準備

1.Cloud Console で対象プロジェクトを開く。
2.プロダクト一覧の「APIs」を選択する。
3.画面左の「REGISTER APP」ボタンを押す。
4.アプリケーションの名前を入力する。
 (この値はプログラム等から使用しないので、わかりやすい一意な名前をつけましょう)
5.「Web Application」「Generic」を選択して「Register」ボタンを押す。
6.「Certificate」をクリックする。
7.「Generate Certificate」ボタンをクリックする。
8.「Download private key」ボタンをクリックして「Private Key」をダウンロードする。
9.「View public key」ボタンをクリックしてダイアログを閉じる。

後の作業で以下を使用します。
・「Service Account」→「Certificate」セクションに書いてある「EMAIL ADDRESS」
・「Private Key」→ダウンロードした「*.p12ファイル」
・「Dataset ID」→「CloudプロジェクトのProject ID」

これはCloud Datastore API に限らない、 Service Account Flow でGoogleのAPIを使うための準備です。
GoogleのAPIを実行するための認証方法はいくつかあるのですが、
Webサーバーや定期的に実行するバッチ等から利用する場合にはこの方式が便利です。



◯前回のサンプルコードを使って試してみる

まずは「36.Google Cloud Datastoreを試してみた GCE編 (2/3)」で使用したサンプルコードを使います。

前回書いたように、DatastoreHelper#getOptionsfromEnv() メソッドが
「Compute Engineから認証する場合」と「それ以外の環境から認証する場合」とを
環境に応じて自動で切り替えてくれるので、
環境変数の設定をするだけでコードの修正なしにそのまま動きます。

− 環境変数を設定する
まず必須なのは以下の2つの環境変数です。
export DATASTORE_SERVICE_ACCOUNT=<service-account>
export DATASTORE_PRIVATE_KEY_FILE=<path-to-private-key-file>

ローカルの開発サーバーに接続したい時は追加で下記も設定するそうです。
export DATASTORE_HOST=http://localhost:8080
export DATASTORE_DATASET=<dataset_id>

− プログラムを実行する
mavenから実行するので前回と実行方法は同じです。

mvn clean compile
mvn exec:java -Dexec.mainClass="ClientTest" -Dexec.args="[Project ID]"
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building vier-test-client 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) @ vier-test-client >>>
[INFO] 
[INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) @ vier-test-client <<<
[INFO] 
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ vier-test-client ---
6 12, 2013 7:04:56 午後 ClientTest main
情報: DATASTORE_DATASET:null
6 12, 2013 7:04:56 午後 ClientTest main
情報: DATASTORE_HOST:null
6 12, 2013 7:04:56 午後 ClientTest main
情報: DATASTORE_SERVICE_ACCOUNT:*************-********************************@developer.gserviceaccount.com
6 12, 2013 7:04:56 午後 ClientTest main
情報: DATASTORE_PRIVATE_KEY_FILE:/Users/User/Documents/workspace/vier-test-client/privatekey.p12
6 12, 2013 7:04:56 午後 com.google.api.services.datastore.client.DatastoreHelper getOptionsfromEnv
情報: Using JWT Service Account credential.
6 12, 2013 7:04:56 午後 ClientTest main
情報: options.getHost():https://www.googleapis.com
6 12, 2013 7:04:56 午後 ClientTest main
情報: options.getDataset():***********
6 12, 2013 7:04:58 午後 ClientTest main
情報: Meaning of Life?
> 42
6 12, 2013 7:05:02 午後 ClientTest main
情報: fascinating, extraordinary and,when you think hard about it, completely obvious.

前回Compute Engine上で実行した場合には
「Using Compute Engine credential.」とログが出力されましたが、
Compute Engine上以外から環境変数を指定して実行した場合には
「Using JWT Service Account credential.」と出力されます。
このように環境に応じて使用するCredentialを内部で自動的に切り替えているのがわかります。



◯クエリを実行するサンプル

「Getting Started with Google Cloud Datastore and Java/Protobuf」のサンプルを少し修正しただけです。

処理内容は下記の通り。
1.Entity を1000件 put する
2.更新時刻の降順、limit5件でクエリする

* 「Putの平均時間」を計測するため、あえて batch put してません。


・ClientTest3.java

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang3.time.StopWatch;

import com.google.api.services.datastore.DatastoreV1.BlindWriteRequest;
import com.google.api.services.datastore.DatastoreV1.Entity;
import com.google.api.services.datastore.DatastoreV1.EntityResult;
import com.google.api.services.datastore.DatastoreV1.Key;
import com.google.api.services.datastore.DatastoreV1.Property;
import com.google.api.services.datastore.DatastoreV1.PropertyOrder;
import com.google.api.services.datastore.DatastoreV1.Query;
import com.google.api.services.datastore.DatastoreV1.RunQueryRequest;
import com.google.api.services.datastore.DatastoreV1.RunQueryResponse;
import com.google.api.services.datastore.DatastoreV1.Value;
import com.google.api.services.datastore.client.Datastore;
import com.google.api.services.datastore.client.DatastoreException;
import com.google.api.services.datastore.client.DatastoreFactory;
import com.google.api.services.datastore.client.DatastoreHelper;
import com.google.api.services.datastore.client.DatastoreOptions;

public class ClientTest3 {
 private static final Logger logger = Logger.getLogger(ClientTest3.class.getName());

 public static void main(String[] args) {
  if (args.length < 1) {
   System.err.println("Usage: ClientTest dataset_id");
   System.exit(1);
  }

  logger.info("DATASTORE_DATASET:" + System.getenv("DATASTORE_DATASET"));
  logger.info("DATASTORE_HOST:" + System.getenv("DATASTORE_HOST"));
  logger.info("DATASTORE_SERVICE_ACCOUNT:" + System.getenv("DATASTORE_SERVICE_ACCOUNT"));
  logger.info("DATASTORE_PRIVATE_KEY_FILE:" + System.getenv("DATASTORE_PRIVATE_KEY_FILE"));

  String datasetId = args[0];
  Datastore datastore = null;
  try {
   DatastoreOptions.Builder builder = DatastoreHelper.getOptionsfromEnv();
   DatastoreOptions options = builder.dataset(datasetId).build();
   logger.info("options.getHost():" + options.getHost());
   logger.info("options.getDataset():" + options.getDataset());

   datastore = DatastoreFactory.get().create(options);

  } catch (GeneralSecurityException exception) {
   System.err.println("Security error connecting to the datastore: " + exception.getMessage());
   System.exit(1);
  } catch (IOException exception) {
   System.err.println("I/O error connecting to the datastore: " + exception.getMessage());
   System.exit(1);
  }

  StopWatch sw = new StopWatch();
  sw.start();
  for (int i = 1; i <= 1000; i++) {
   try {
    // トランザクション外で更新するためのRPC requestを作成する
    BlindWriteRequest.Builder req = BlindWriteRequest.newBuilder();
    // 新規Entityを作成する
    Entity.Builder entity = Entity.newBuilder();
    // 一つのPathElementでKeyを生成する (親Keyなし)
    Key.Builder key = Key.newBuilder().addPathElement(
      Key.PathElement.newBuilder().setKind("ClientTest3").setName("keyName" + i));
    entity.setKey(key);
    // 文字列
    entity.addProperty(Property.newBuilder().setName("str")
      .addValue(Value.newBuilder().setStringValue("string" + i)));
    // 数値
    entity.addProperty(Property.newBuilder().setName("number")
      .addValue(Value.newBuilder().setIntegerValue(i)));
    // 作成時刻
    entity.addProperty(Property.newBuilder().setName("createDate")
      .addValue(Value.newBuilder().setTimestampMicrosecondsValue(new Date().getTime() * 1000)));
    req.getMutationBuilder().addUpsert(entity);
    // putする
    datastore.blindWrite(req.build());

    logger.info("put done count:" + i);

   } catch (DatastoreException exception) {
    logger.log(Level.SEVERE, "error", exception);
   }
  }
  sw.stop();
  logger.info("put entities " + sw.getTime() + " milliseconds.");

  sw.reset();
  sw.start();

  try {
   // クエリする
   RunQueryRequest.Builder req = RunQueryRequest.newBuilder();
   Query.Builder queryBuilder = req.getQueryBuilder();
   queryBuilder.addKindBuilder().setName("ClientTest3");
   // 作成時刻の新しい順
   queryBuilder.addOrder(DatastoreHelper.makeOrder("createDate", PropertyOrder.Direction.DESCENDING));
   // limit 5件
   queryBuilder.setLimit(5);
   // クエリ実行
   RunQueryResponse res = datastore.runQuery(req.build());

   List<EntityResult> results = res.getBatch().getEntityResultList();
   for (EntityResult result : results) {
    Entity entity = result.getEntity();

    Map<String, Object> propertyMap = DatastoreHelper.getPropertyMap(entity);
    logger.info("Entity: keyName:" + entity.getKey().getPathElement(0).getName() + " str:"
      + propertyMap.get("str") + " number:" + propertyMap.get("number") + " createDate:"
      + propertyMap.get("createDate"));
   }

  } catch (DatastoreException exception) {
   logger.log(Level.SEVERE, "error", exception);
   System.exit(1);
  }
  sw.stop();
  logger.info("query entities " + sw.getTime() + " milliseconds.");

 }
}



◯ローカル環境とCompute Engineからの処理速度比較

- ローカルで実行
iMac mid2012 Core i5 メモリ 32GB

mvn clean compile
mvn exec:java -Dexec.mainClass="ClientTest3" -Dexec.args="cloud-datastore"
(中略)
6 11, 2013 4:53:02 午後 ClientTest3 main
情報: put done count:999
6 11, 2013 4:53:02 午後 ClientTest3 main
情報: put done count:1000
6 11, 2013 4:53:02 午後 ClientTest3 main
情報: put entities 504134 milliseconds.
6 11, 2013 4:53:03 午後 ClientTest3 main
情報: Entity: keyName:keyName1000 str:string1000 number:1000 createDate:Tue Jun 11 16:53:02 JST 2013
6 11, 2013 4:53:03 午後 ClientTest3 main
情報: Entity: keyName:keyName999 str:string999 number:999 createDate:Tue Jun 11 16:53:01 JST 2013
6 11, 2013 4:53:03 午後 ClientTest3 main
情報: Entity: keyName:keyName998 str:string998 number:998 createDate:Tue Jun 11 16:53:01 JST 2013
6 11, 2013 4:53:03 午後 ClientTest3 main
情報: Entity: keyName:keyName997 str:string997 number:997 createDate:Tue Jun 11 16:53:01 JST 2013
6 11, 2013 4:53:03 午後 ClientTest3 main
情報: Entity: keyName:keyName996 str:string996 number:996 createDate:Tue Jun 11 16:53:00 JST 2013
6 11, 2013 4:53:03 午後 ClientTest3 main
情報: query entities 424 milliseconds.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8:26.646s
[INFO] Finished at: Tue Jun 11 16:53:03 JST 2013
[INFO] Final Memory: 19M/193M
[INFO] ------------------------------------------------------------------------
iMac:vier-test-client User$ 
→1000回のPutに504,134ms. 約504秒→平均500ms. ぐらい


- Compute Engine上で実行
f1-micro メモリ:0.60GB ゾーン:us-central1-a

mvn clean compile
mvn exec:java -Dexec.mainClass="ClientTest3" -Dexec.args="cloud-datastore"
(中略)
6 12, 2013 3:30:36 午前 ClientTest3 main
情報: put done count:999
6 12, 2013 3:30:36 午前 ClientTest3 main
情報: put done count:1000
6 12, 2013 3:30:36 午前 ClientTest3 main
情報: put entities 251113 milliseconds.
6 12, 2013 3:30:37 午前 ClientTest3 main
情報: Entity: keyName:keyName1000 str:string1000 number:1000 createDate:Wed Jun 12 03:30:36 EDT 2013
6 12, 2013 3:30:37 午前 ClientTest3 main
情報: Entity: keyName:keyName999 str:string999 number:999 createDate:Wed Jun 12 03:30:36 EDT 2013
6 12, 2013 3:30:37 午前 ClientTest3 main
情報: Entity: keyName:keyName998 str:string998 number:998 createDate:Wed Jun 12 03:30:36 EDT 2013
6 12, 2013 3:30:37 午前 ClientTest3 main
情報: Entity: keyName:keyName997 str:string997 number:997 createDate:Wed Jun 12 03:30:35 EDT 2013
6 12, 2013 3:30:37 午前 ClientTest3 main
情報: Entity: keyName:keyName996 str:string996 number:996 createDate:Wed Jun 12 03:30:35 EDT 2013
6 12, 2013 3:30:37 午前 ClientTest3 main
情報: query entities 462 milliseconds.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4:23.316s
[INFO] Finished at: Wed Jun 12 03:30:37 EDT 2013
[INFO] Final Memory: 10M/24M
[INFO] ------------------------------------------------------------------------
[User@cdstest vier-test-client]$ 
→1000回のPutに208,679ms. 約208秒→平均200ms. ぐらい

一番非力な仮想マシンで試しましたが、予想通り国内からインターネット経由でAPIを実行するのと比べて
処理時間に2倍の差があります。
インスタンスを「n1-standard-1 (コア1 メモリ 3.75GB)」にしても実行時間は誤差レベルでしか変わらず。
ほとんどの時間をAPIの通信で使っているからでしょう。


- ついでにApp Engine上からのPut速度も試してみる
F1インスタンス (一番低価格のFrontendインスタンス)

Compute Engine からの put にかかった時間を見て、
「App Engine から put したらどのくらいだっけ?」と思ったので試してみました。

コードは以下の内容。
Model (Entity)の定義は前述のClientTest3とほぼ同じです。
App Engine 上から Slim3経由で Low Level APIを使って操作した場合の実行時間を調べます。

リクエストの60秒制限に引っかからないよう、こちらはPut回数を「100回」にしています。
(cronやTaskQueue使ってもいいんですけど簡単に)

package jp.vier.test.controller;

import java.util.Date;
import java.util.logging.Logger;

import jp.vier.test.model.Slim3Test;

import org.apache.commons.lang3.time.StopWatch;
import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import org.slim3.datastore.Datastore;

import com.google.appengine.api.datastore.Key;

public class PutTestController extends Controller {
    private static final Logger logger = Logger
        .getLogger(PutTestController.class.getName());

    @Override
    public Navigation run() throws Exception {

        StopWatch sw = new StopWatch();
        sw.start();
        for (int i = 1; i <= 100; i++) {
            Key key = Slim3Test.createKey("keyName" + i);
            Slim3Test model = new Slim3Test(key);
            model.setStr("string" + i);
            model.setNumber(i);
            model.setCreateDate(new Date());
            Datastore.put(model);
        }
        sw.stop();
        logger.info("put entities " + sw.getTime() + " milliseconds.");

        return forward("putTest.jsp");
    }
}


→100回のPutに4,388ms. 約4.4秒→平均44ms. ぐらい。

GCE上から Cloud Datastore API 経由で実行するより4〜5倍速いです。


- 所感
予想通りの結果として、
Cloud Datastore API は Googleクラウド外からよりもCompute Engineから実行した方が速い
という結果になりました。 (アメリカ国内だったらここまで変わらないのでしょうけど)

「既存の別システムの処理に伴ってDatastoreを操作したいようなケース」では
既存システムから直接APIを実行するのがシンプルだと思いますが、
「大量データに対して更新処理を行うバッチ処理」のような単独の処理は
Compute Engineから実行するのが良さそうです。
ちなみに f1-micro のお値段は$0.019/時。 (起動しっぱなしでも $13.68/月ぐらい)

次に、
Compute Engineからの実行速度とApp Engineからの実行速度を比べると、
AppEngine 上から直接 Datastore を操作するのに比べてCloud Datastore API はかなり速度が落ちる
という結果も出ました。

※ 2013/6/20 追記 Google+で +Takashi Matsuo さんと +Johan Euphrosine さんからコメントをいただきました
Google でも Compute Engine から実行した際の速度については認識していて、
「現在 Compute Engine と Cloud Datastore 間のネットワークレイテンシーを減らすために積極的に取り組んでいる」とのことです。
参考: High latency · Issue #5 · GoogleCloudPlatform/google-cloud-datastore
(ちなみに Johan さんは「18.Google App Engineパターン (appengine ja night #23)」の「proppy氏」)

※ 2013/7/11 追記 Githubで Johan さんから「もう一回ベンチマーク試せる?」と書き込みがあったので試してみました。
試したところ f1-micoro で put速度は3割強向上しました。
また、別途 Brian さんから「高性能なインスタンスほどネットワークのスループットも高いから、n1-standard-4 か n1-highcpu-4 インスタンス変えたらもっと速くなるかも」という書き込みがあったので、こちらも試してみました。

putの速度は n1-highcpu-4 も f1-micro とほとんどと変わりませんでしたが、クエリの速度は3割ほど速くなりました。
(ちなみに Brian さんは「appengine ja night #23」でCompute Engineについて発表してくれた「Brian氏」)

Cloud Datastoreの話から少し逸れますが、Compute EngineでWebサーバーを公開するような場合はネットワークのパフォーマンスのために高性能なインスタンスを使うのが良いかもしれません。
(軽く調べたところEC2もインスタンスタイプによってネッワークのパフォーマンスが変わる模様)


前回書いた仕組みを考えれば、
「クライアント→GAEアプリ上のAPI」間の通信時間とGAEアプリの処理時間が
そのままオーバーヘッドとなって実行時間に加算されているので、当然と言えば当然かもしれません。
(さらに APIのURLである www.googleapis.com のサーバーと App Engineアプリ の間でも通信してるのかな?)

この結果を見ると、「33.Google Cloud Datastoreを試してみた 概要編 (1/3)」に書いた
・ユースケース2 Compute Engineから使うNoSQLデータベースとして利用する のように、
GCE上のWebアプリからメンテナンスフリーなKVSとしてCloud Datastoreを利用するのは速度的に苦しい気がしてきました。

現時点ではApp Engineをメインに使っているWebシステムとの連携目的で
バッチや別システムから Cloud Datastore API を使うのが現実的かもしれません。


「Cloud Datastoreの請求がPreview期間後はAppEngineとは別の請求になる」という話から考えると
その頃には今と異なる仕組みになっていそうな気もしますので、
Compute Engine から接続する場合だけでもDatastoreに高速にアクセスする手段が提供されて欲しいと思います。
App Engine を使わず Compute Engine だけで利用する場合にも、
メンテナンスフリーな Datastore の存在は運用コストに結構影響があると思うので。

個人的には、予定されているCompute Engine のLoad Balancerと
アジアのデータセンターが提供される頃に間に合うとタイミングがいいなぁと思います。



◯おまけ (トラブルシューティング)

実は今回書いた「Service Account Flowを使ってAPIを実行」はとても難航しました。
本来は前回のCompute Engineからの実行ができていれば環境変数を設定するだけで簡単に済むはずですが、
認証エラーが発生してAPIを実行できず、その調査に2,3日悩まされました。
その時のトラブルの原因は環境的なものであったと考えています。

最後にApp Engine Ver.1.8.1リリース後に作成した新規プロジェクトではスムーズに動作したので
もう問題は再現しないかもしれませんが、
Service Account Flow でうまく接続できない場合の参考に、原因の切り分けのために試した事を書いておきます。


- blindWrite実行時に 「DatastoreException(Unauthorized.)」 が発生する
当初、ドキュメント通りに環境変数の設定をしていてもこのエラーが発生してエラーになっていました。

・Compute Engine上からは問題なく動作した。
 (前回書いたようにCompute Engine上では専用のCredentialが使われるので、Credentialが異なる)

・APIs Explorer からは問題なく動作した。(JSからGoogleアカウントのIDとパスワードを使った認証)

・blindWrite実行時に使用するTokenの値をデバッガで「APIs Explorerで生成したToken」に書き換えたら動作した。

・Java で Authorization Code Flow を使って Credential を取得するコードを書いたら問題なく動作した。
 (これもGoogleアカウントのIDとパスワードを使った認証)
 参考:https://developers.google.com/bigquery/docs/authorization#installed-applications

* ここまでに試した内容では取得した Service Account や Private Key を使用しません。
これらを使用するのがこの後のService Account Flow です。

ここまでで、どうやら Service Account Flow で認証した場合にだけ動作していないことがわかりました。
次に Service Account Flow での認証ができていないのかを確認しました。

・Service Account Flowで OAuth2 Token の取得はできている。

・BigQueryのScopeを追加してService Account Flowで認証し、BigQueryのAPIを実行したところ動作した。
→Service Account Flow を使った認証自体には問題なさそう。
→"Service Account Flow を使って Datastore を操作しようとした場合"に「Unauthorized」となる。

・この時対象アプリの「ah-builtin-datastoreservice」にリクエストログが記録されている。(アクセスは来ている)

これらの内容から、
「Service Account Flow を使った認証自体はできているが App Engine に対して権限が無いのではないか」
と推測しました。

そして、最後に作成して正常に動作した App Engine のアプリでは、
Admin Console の Permissions に「Cloud Consoleの Certificate セクションの EMAIL ADDRESS」、
つまり「環境変数の DATASTORE_SERVICE_ACCOUNT に指定するメールアドレス」が登録されていました。
一方、以前に作成して実行に失敗したGAEアプリには「EMAIL ADDRESS」が登録されていませんでした。
このアドレスは自分で登録することができないので(メールを受信して承認できないので)
どこかのタイミングで自動で登録されるのでしょう。
何らかの理由でこれが登録されなかった場合にエラーになるのかもしれません。

正常に動作したアプリでは、Cloud Project関連のユーザーとして
・123456789000@project.gserviceaccount.com
・123456789000@developer.gserviceaccount.com
の2つが、
追加したService Account Flow用のアプリのEMAIL ADDRESSである、
・123456789000-01g2taglb3ur4567resjqda89pod0t2g@developer.gserviceaccount.com
が登録されていました。
* アドレスの「@」以前は適当に書き換えています。

特にこの3つ目の「EMAIL ADDRESS」が無い場合が怪しいのかな、と思っています。
(確定では無いのですが、接続に失敗したアプリには今もこれが登録されていませんし、今も接続できません)


− APIのリクエストがAppEngineアプリの「ah-builtin-datastoreservice」で処理されない
これは2つのパターンがありました。
・アプリのデフォルトバージョンにリクエストが来る
・「ah-empty」という謎のバージョンが作成されてそこにアクセスが来る


「サイズ 0Bytes」、「デプロイしたユーザー名が空」ってのはレアですね。
さすがにバグっぽい気がしますが、こちらの原因と解決策はわかりません。


- 他のエラーのパターン
こちらは設定やコードにミスがある場合のパターンです。参考に。

- Cloud DatastoreAPIをONにしていない場合
DatastoreException(Access Not Configured): blindWrite 403

- Scopeが足りない場合 (DatastoreHelperを使っている限りは起こり得ませんが)
403 Forbidden Insufficient Permission


かなり苦労しましたが、おかげでGoogle APIの各種認証方法についての理解は以前より深まった気がします(;´∀`)



◯参考リンク

・Google Cloud Datastore 公式ドキュメント
・Activate Google Cloud Datastore - Google Cloud Datastore
・Using the Local Development Server - Google Cloud Datastore
・Getting Started with Google Cloud Datastore and Java/Protobuf
・GoogleCloudPlatform/google-cloud-datastore · GitHub



このエントリーをはてなブックマークに追加

2013年6月18日火曜日

37.Google Cloud Datastoreを試してみた GCE編 (2/3)

このエントリーをはてなブックマークに追加


・33.Google Cloud Datastoreを試してみた 概要編 (1/3)
・37.Google Cloud Datastoreを試してみた GCE編 (2/3)
・38.Google Cloud Datastoreを試してみた その他環境編 (3/3)

Ryo Yamasaki(@vierjp)です。

途中で「App Engine SDK 1.8.1のリリース」「BigQueryの新機能」「GAE for PHP用WordPressプラグイン」を挟んで
引き続き「Google Cloud Datastore」について試したことを書いていきます。

第1回は概要や仕組み、注意点について書きました。
この第2回ではGoogle Compute EngineからCloud DatastoreのAPIを実行してみます。



◯Google Cloud Console と Cloudプロジェクト

Cloud DatastoreのAPI実行時に「プロジェクト」という概念が出てくるので、
先に「Google Cloud Console」とその「プロジェクト」について簡単に書こうと思います。

先日「Blog @vierjp : 27.Google I/Oで発表されたGoogle Cloud Platformの新機能」に書いた通り、
Google I/Oで「Google Cloud Console」が発表されました。

これまでプロダクト毎に分かれていたGoogle Cloud Platformの各プロダクトを
まとめて扱うための管理画面が「Google Cloud Console」で、
その各プロダクトをまとめる単位が「Cloudプロジェクト」になります。



上の画像のように、
一つの「Cloudプロジェクト」にはAppEngine・ComputeEngine・Cloud Storageなどの各プロダクトが含まれます。

「Cloudプロジェクト」と既存プロダクトとの関連は以下のようになります。

・AppEngineの"一つのアプリケーション"とプロジェクトが1:1で紐付いている
・AppEngineの「アプリケーションID」と Cloud Consoleの「Project ID」は同じ値である
・プロジェクトに紐づくCloud Datastore は"一つのGAEアプリケーションのDatastore"を示す
・既存のAPI Projectは「Cloudプロジェクト」と1:1で紐付いている(ほぼ同等)
という点がポイントです。



◯Cloudプロジェクトの作成

- 新規に作成したGAEアプリケーションのDatastoreを使用する場合

・Google Cloud Consoleを開く
・画面左上の「CREATE PROJECT」ボタンを押す
・「Project name」と「Project ID」を入力する。

* デフォルトではかなり適当なID値が表示されますが、
Project IDの値はAPI利用時に使ったり、
GAEアプリケーションのアプリケーションID(=URLの一部)にもなるので、
自分で覚えやすい名前を明示的に指定するのがオススメ。


- 既存GAEアプリケーションのDatastoreを使用する場合
第一回に書いたように Cloud Datastore API の処理は App Engine のアプリで実行されますが、
Cloud Datastore APIの実行時には「Cloudプロジェクト」を指定します。
そのため、過去に作成済みのAppEngineアプリとCloudプロジェクトを紐付けておく必要があります。

1.AppEngineのAdmin Consoleを開く
2.Application SettingsのCloud Integration セクション内「Add Project」を押す

しばらく「進行中」になるので待ちます。
完了すると成功した旨表示され、「Application Settings」のBasics セクションに
「Google APIs Console Project Number:〜〜」の記述が追加されます。


これでそのGAEアプリケーションがCloudプロジェクトと紐付きます。
(エラーになって失敗するケースもあるようですが。。)

参考:Activate Google Cloud Datastore - Google Cloud Datastore



◯サンプルプログラム

ほとんど「Getting Started with Google Cloud Datastore and Java/Protobuf」のサンプルのままです。
コメントは全て日本語に直しました。

1.TriviaというKind名でEntityを一件putする
2.Keyを指定してgetする
3.入力した値とEntityから取得した数字(42)が一致するかを判定してメッセージを出力する
というコードです。


・pom.xml
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelversion>4.0.0</modelversion>
  <groupid>vier-test-client</groupid>
  <artifactid>vier-test-client</artifactid>
  <version>0.0.1-SNAPSHOT</version>
  <name>vier-test-client</name>

<repositories>
  <repository>
    <id>sonatype-snapshots</id>
    <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
  </repository>
</repositories>

<dependencies>
  <dependency>
    <groupid>com.google.apis</groupid>
    <artifactid>google-api-services-datastore-protobuf</artifactid>
    <version>v1beta1-rev1-1.0.0-SNAPSHOT</version>
  </dependency>
</dependencies>

  <build>
    <plugins>
      <plugin>
        <artifactid>maven-eclipse-plugin</artifactid>
        <configuration>
          <downloadsources>true</downloadsources>
          <downloadjavadocs>true</downloadjavadocs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

・ClentTest.java
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.api.services.datastore.DatastoreV1.BlindWriteRequest;
import com.google.api.services.datastore.DatastoreV1.Entity;
import com.google.api.services.datastore.DatastoreV1.Key;
import com.google.api.services.datastore.DatastoreV1.LookupRequest;
import com.google.api.services.datastore.DatastoreV1.LookupResponse;
import com.google.api.services.datastore.DatastoreV1.Property;
import com.google.api.services.datastore.DatastoreV1.Value;
import com.google.api.services.datastore.client.Datastore;
import com.google.api.services.datastore.client.DatastoreException;
import com.google.api.services.datastore.client.DatastoreFactory;
import com.google.api.services.datastore.client.DatastoreHelper;
import com.google.api.services.datastore.client.DatastoreOptions;

public class ClientTest {
 private static final Logger logger = Logger.getLogger(ClientTest.class.getName());

 public static void main(String[] args) {
  if (args.length < 1) {
   System.err.println("Usage: ClientTest DATASET_ID");
   System.exit(1);
  }

  // 環境変数をログ出力
  logger.info("DATASTORE_DATASET:" + System.getenv("DATASTORE_DATASET"));
  logger.info("DATASTORE_HOST:" + System.getenv("DATASTORE_HOST"));
  logger.info("DATASTORE_SERVICE_ACCOUNT:" + System.getenv("DATASTORE_SERVICE_ACCOUNT"));
  logger.info("DATASTORE_PRIVATE_KEY_FILE:" + System.getenv("DATASTORE_PRIVATE_KEY_FILE"));

  // DatasetID(ProjectID) を実行時引数から取得する
  String datasetId = args[0];
  Datastore datastore = null;
  try {
   // 生成したCredentialをセットした「DatastoreOptions」を取得する。
   // Compute Engine上て動作している場合はComputeEngine用のCredentialを、
   // それ以外の環境で、かつ環境変数が適切に設定されている場合は
   // Serivce Account FlowのCredentialを生成する。
   DatastoreOptions.Builder builder = DatastoreHelper.getOptionsfromEnv();

   DatastoreOptions options = builder.dataset(datasetId).build();
   logger.info("options.getHost():" + options.getHost());
   logger.info("options.getDataset():" + options.getDataset());

   datastore = DatastoreFactory.get().create(options);

  } catch (GeneralSecurityException exception) {
   logger.severe("Security error connecting to the datastore: " + exception.getMessage());
   System.exit(1);
  } catch (IOException exception) {
   logger.severe("I/O error connecting to the datastore: " + exception.getMessage());
   System.exit(1);
  }

  try {
   // トランザクション外で更新するためのRPC requestを作成する
   BlindWriteRequest.Builder req = BlindWriteRequest.newBuilder();
   // 新規Entityを作成する
   Entity.Builder entity = Entity.newBuilder();
   // 一つのPathElementでKeyを生成する (親Keyなし)
   Key.Builder key = Key.newBuilder().addPathElement(
     Key.PathElement.newBuilder().setKind("Trivia").setName("hgtg"));
   entity.setKey(key);
   // Entityに2つのPropertyを追加する
   // utf-8文字列の「question」
   entity.addProperty(Property.newBuilder().setName("question")
     .addValue(Value.newBuilder().setStringValue("Meaning of Life?")));
   // 64bit integerの「answer」を追加する
   entity.addProperty(Property.newBuilder().setName("answer").addValue(Value.newBuilder().setIntegerValue(42)));
   // update or insertする「mutation」として「upsert」を指定する
   // 他にも「update」「insert」「insertAutoId」「delete」「force」がある
   // (「force」はユーザーが設定したread-onlyを無視するらしい)
   req.getMutationBuilder().addUpsert(entity);
   // 同期的にRPCを実行して結果を無視する
   // (「返り値を無視する」の意味であって、システムエラー時には例外が発生する)
   datastore.blindWrite(req.build());

   // KeyでEntityを「get」するための RPC リクエストを作成する
   LookupRequest.Builder lreq = LookupRequest.newBuilder();
   // 登録したEntityを「get」するためにKeyを一つ指定する
   // (おそらく複数指定するとbatch get)
   lreq.addKey(key);
   // RPCを実行して結果を取得する
   LookupResponse lresp = datastore.lookup(lreq.build());
   // 結果として一つのEntityを取得する.
   Entity entityFound = lresp.getFound(0).getEntity();
   // 「question」 propertyの値を取得する。
   String question = entityFound.getProperty(0).getValue(0).getStringValue();
   // 「answer」 propertyの値を取得する。
   Long answer = entityFound.getProperty(1).getValue(0).getIntegerValue();
   logger.info(question);
   // コンソールからの値入力待ち
   String result = System.console().readLine("> ");
   // 入力値とEntityから取得した「answer」(=42)が一致した場合
   if (result.equals(answer.toString())) {
    logger.info("fascinating, extraordinary and,when you think hard about it, completely obvious.");

    // 一致しなかった場合
   } else {
    logger.info("Don't Panic!");
   }
  } catch (DatastoreException exception) {
   // DatastoreAPI実行時の例外をcatchする.
   logger.severe("Error while doing datastore operation");
   logger.log(Level.SEVERE, "error", exception);
   System.exit(1);
  }
 }
}



◯Compute Engine から Cloud Datastore の API を使う

前回「Blog @vierjp : "33.Google Cloud Datastoreを試してみた 概要編 (1/3)"」に書いた以下のユースケースでは
Compute EngineからCloud Datastoreに接続します。

・ユースケース1 App EngineのバックエンドとしてCompute Engineを利用する
・ユースケース2 Compute Engineから使うNoSQLデータベースとして利用する

前回書いたとおりCompute EngineからCloud DatastoreのAPIを利用する場合は認証が簡単です。


- Google Compute Engineを有効にする
Google Cloud Consoleで操作対象の Datastore が属する Cloudプロジェクト を開いて Compute Engine を選択します。
Compute Engine を使用するためにはここで Billing設定を有効にする必要があります。


- gcutilの導入
Google Compute Engine では CUI のツールとして gcutil が提供されています。
今回の手順では gcutil が必要なので最初に準備します。(理由は後述)

gcutil はMac、Linux環境では他のプロダクトのCUIツール同様簡単に導入できますが、
Windows環境では「Cygwin」の導入も必要です。
(おそらく gcutil のコマンドの一部に内部的に ssh や scp コマンドを使うものがあるため)

・ダウンロードして展開する
wget https://google-compute-engine-tools.googlecode.com/files/gcutil-1.8.1.tar.gz
tar xzvpf gcutil-1.8.1.tar.gz -C $HOME
rm gcutil-1.8.1.tar.gz

・Pathに追加する
echo -e "\nexport PATH=\$PATH:\$HOME/gcutil-1.8.1" > $HOME/.bashrc
source $HOME/.bashrc

参考:gcutil Tool - Google Compute Engine


- GCEインスタンスの作成
gcutil --project [Project ID] addinstance [インスタンス名]\
 --image=projects/centos-cloud/global/images/centos-6-v20130522\
 --machine_type=f1-micro\
 --persistent_boot_disk=true\
 --service_account_scopes=\
https://www.googleapis.com/auth/userinfo.email,\
https://www.googleapis.com/auth/datastore

・Project ID
作成済みのCloudプロジェクトのIDを指定します。

・インスタンス名
ここでは好きな名前を指定すればOK。
覚えやすい名前にしましょう。

・image
OSイメージを指定する。
ここでは用意されているCentOS6のイメージを使いました。

・machine_type
インスタンスタイプを指定する。
提供されているインスタンスタイプの内容と料金は以下を参照
Google Compute Engine Pricing - Google Cloud Platform — Cloud Platform

・persistent_boot_disk
起動ディスクをPersistent Diskにするかどうか。
共有インスタンスの f1-micro はPersistent Diskの利用が必須なのでtrueを指定しています。
(Compute Engineについての詳しい話はまたそのうち)

・service_account_scopes
このGCEインスタンスから利用可能なScope
Googleの各API毎に要求するScopeが異なります。
「https://www.googleapis.com/auth/userinfo.email」はユーザーを特定するために定番で指定、
「https://www.googleapis.com/auth/datastore」はCloud DatastoreのAPIを利用するために指定します。


* 現状この手順は gcutil から行う必要があります
本来 GCEインスタンス の作成は Cloud Console の GUI からもできます。
その際にAPIの利用設定(Scopeの設定)をすることが可能ですが、
Cloud Datastore API については現状そこで利用設定をすることができません。(UI的に)
そのため gcutil のコマンドで明示的に Scope を指定してインスタンスを作成する必要があります。


- GCEインスタンスにsshで接続する
gcutil --project [プロジェクト名] ssh [インスタンス名]
インスタンス作成時に指定したプロジェクト名とインスタンス名を指定してsshで接続します。
任意のsshクライアントからIPアドレスを指定して接続することもできますが、
gcutil からなら覚えやすいインスタンス名で接続できるので楽です。


- JDKのインストール
Javaプログラムを実行するのでJDKをyumでインストールします。

yum list \*java-1\* | grep open
sudo yum install java-1.7.0-openjdk.x86_64
sudo yum install java-1.7.0-openjdk-devel.x86_64

- mavenのインストール
mavenをダウンロードして設定します。

wget http://ftp.riken.jp/net/apache/maven/maven-3/3.0.5/binaries/apache-maven-3.0.5-bin.tar.gz
tar xvzf apache-maven-3.0.5-bin.tar.gz
sudo mv apache-maven-3.0.5 /usr/share
sudo ln -s /usr/share/apache-maven-3.0.5 /usr/share/maven

export M2_HOME=/usr/share/maven
export M2=$M2_HOME/bin
export PATH=$M2:$PATH

最後に以下のコマンドで動作を確認。
mvn --version

- ローカルのファイルをGCEインスタンスに転送
gcutil --project [プロジェクト名] push [インスタンス名] \
/Users/User/Documents/workspace/vier-test-client/ /home/User/vier-test-client/
こちらも ssh 同様、インスタンス名を指定して gcutil からファイルの転送ができます。
内部的には scp コマンドが実行されているようです。


- プログラムの実行
mavenから実行します。

mvn clean compile
mvn exec:java -Dexec.mainClass="ClientTest" -Dexec.args="[Project ID]"
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building vier-test-client 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) @ vier-test-client >>>
[INFO] 
[INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) @ vier-test-client <<<
[INFO] 
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ vier-test-client ---
6 12, 2013 11:23:56 午前 ClientTest main
情報: DATASTORE_DATASET:null
6 12, 2013 11:23:56 午前 ClientTest main
情報: DATASTORE_HOST:null
6 12, 2013 11:23:56 午前 ClientTest main
情報: DATASTORE_SERVICE_ACCOUNT:null
6 12, 2013 11:23:56 午前 ClientTest main
情報: DATASTORE_PRIVATE_KEY_FILE:null
6 12, 2013 11:23:56 午前 com.google.api.services.datastore.client.DatastoreHelper getOptionsfromEnv
情報: Using Compute Engine credential.
6 12, 2013 11:23:56 午前 ClientTest main
情報: options.getHost():https://www.googleapis.com
6 12, 2013 11:23:56 午前 ClientTest main
情報: options.getDataset():***********
6 12, 2013 11:24:01 午前 ClientTest main
情報: Meaning of Life?
> 42
6 12, 2013 11:24:07 午前 ClientTest main
情報: fascinating, extraordinary and,when you think hard about it, completely obvious.

実行時引数は「Project ID」を指定します。(コード中では datasetId としているけど同じ値)
冒頭に書いたとおり「Cloudプロジェクト」 と 「Datastore」 は 1:1 で紐付いているので、
「Cloudプロジェクト」のIDを指定することでアクセス対象のDatastoreが確定します。


DatastoreHelper#getOptionsfromEnv()メソッドの中では、
1. Compute Engine用のCredentialの取得を試みて、取得できればそれを返す
 →Compute Engine上で実行した場合はCompute Engine用のCredentialを取得できる
  (実行ログ23行目の「Using Compute Engine credential.」はこの分岐のログ)

2. (1でCredentialを取得できなければ)Service Account Flow用のCredentialを生成して返す
 →Compute Engine用のCredentialを取得できなければ、環境変数にセットした情報を用いてCredentialを生成して返す
という処理が行われています。

よってローカル環境でテストする際やCompute Engine以外の環境でも、
環境変数の設定をするだけでプログラムの修正は無しに動作する作りになっています。

(Compute Engine から Scope設定 だけでアクセスできるのは同一プロジェクト内の Datastore だけなので、
ComputeEngine 優先のフローは 別Project の Datastore にアクセスしようとした場合に困りそうな気もしますが)



◯おまけ

GCEインスタンス上ではGoogle APIを実行するためのCredentialを以下のコードで取得できるようです。

  public static Credential getComputeEngineCredential()
      throws GeneralSecurityException, IOException {
    NetHttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
    ComputeCredential credential = new ComputeCredential(transport, new JacksonFactory());
    credential.refreshToken();
    return credential;
  }

参考:
com.google.api.services.datastore.client.DatastoreHelper#getComputeEngineCredential()

「ComputeCredential」は「google-api-client-1.15.0-rc.jar」に含まれているので、
ComputeEngineのインスタンスを生成する際に必要なScopeを指定することで、
おそらくCloud Datastore以外のAPIを使うときにも使えるでしょう。



◯参考リンク

・Google Cloud Datastore 公式ドキュメント

・Activate Google Cloud Datastore - Google Cloud Datastore

・Getting Started with Google Cloud Datastore and Java/Protobuf

・gcutil Tool - Google Compute Engine



◯次回

次回は Compute Engine 以外の環境から Cloud Datastore の API を試します。
今回は put と get だけでしたが、次回はクエリも実行してみようと思います。



このエントリーをはてなブックマークに追加

2013年6月11日火曜日

33.Google Cloud Datastoreを試してみた 概要編 (1/3)

このエントリーをはてなブックマークに追加



・33.Google Cloud Datastoreを試してみた 概要編 (1/3)
・37.Google Cloud Datastoreを試してみた GCE編 (2/3)
・38.Google Cloud Datastoreを試してみた その他環境編 (3/3)


Ryo Yamasaki(@vierjp)です。

Google I/O 2013で発表された「Google Cloud Datastore」について調べたので、
今回から三回に分けて書いていこうと思います。


第1回は概要や仕組み、注意点についてです。

今回は以前に書いた「Blog @vierjp : 27.Google I/Oで発表されたGoogle Cloud Platformの新機能」と重なっている部分がいくらかあります。


◯概要

・App EngineのDatastoreが単独で利用可能になる
これまでAppEngine上のアプリからしか利用できまなかったDatastoreがそれ以外からも利用できるようになりました。
Compute Engineや他のクラウド環境、オンプレミス環境からも利用が可能です。

・特徴はApp EngineのDatastoreと同じ
 ・クエリの速度がデータ量に影響しない
 ・データは複数データセンターにレプリケートされる
 ・計画されたダウンタイムはなし
 ・メンテナンスフリー
 ・etc…

・環境や言語を問わず利用可能
Protocol Buffer形式かJSON形式でAPIを利用してDatastoreを操作する事が可能です。
PythonとJava、node.js向けには公式のクライアントライブラリも提供されています。

・現在「Preview Release」という扱い
まだPreviewなのでAPIが突然変わる可能性あります。
と言っても通常過去のAPIバージョンもしばらく並行して動くようにしているようなので、
おそらく安全に移行するための期間はあるのではないでしょうか。
「Google APIs Explorer」を見ると、
例えばCompute EngineのAPIは現在v1beta13〜v1beta15を利用可能であることがわかります。


◯仕組み

サービス的な位置づけとしては「AppEngineのDatastoreを別のサービスとして切り出した」で良いと思いますが、
実際の仕組みは「AppEngine のDatastoreを切り出した」というよりも
「既存のAppEngineのDatastoreそのものにアクセスするためのAPIを提供した」
というのが正しいようです。

APIによる操作は「API実行時に指定したAppEngineアプリケーションのDatastore」に対して行われます。
(厳密には指定したCloud Projectに紐づくAppEngineアプリケーション)

そしてAPIはAppEngine上で動作するアプリとして作られているようです。
Admin Consoleの「Logs」には、以下のように「ah-builtin-datastoreservice」という暗黙的なバージョンが「Built-ins」というカテゴリで追加されています。
(一度でもCloud Datastore APIを実行すると表示されるっぽい)
そしてAPIを実行するとこのバージョンのリクエストログに記録されます。


そう考えると使用量がAppEngineの使用量に加算されそこで課金される(後述)のも納得ですし、
システム的な特性もAppEngineのDatastoreと同じということになります。

このような仕組みなので、
自分でAppEngine上にデプロイしたアプリからはこれまで通りにそのアプリケーションの Datastore を操作できます。
その上で「そのアプリケーションのDatastoreをAPIを使って外部からも操作できる」という事になります。
そのためAppEngine上の既存アプリケーションは全く修正することなく、
アプリケーション外の環境とデータを共有することができます。



◯Indexの管理

AppEngineから使っている場合はこれまでどおりに「datastore-indexes.xml」を配置してデプロイすればOKです。

加えてAppEngine"以外"から使う場合のために、
AppEngineのアプリをデプロイせずにインデックスを設定するための手段が追加されています。
と言っても「datastore-indexes.xml」を作成するのは同じですし、
コマンドからこのファイルを指定して「インデックスの更新」と「使ってないインデックスの削除ができる」というものなので、
機能的にはappcfgのインデックス関連の機能をそのまま持ってきて単体で使えるようにしたという感じです。

Index Configuration - Google Cloud Datastore — Google Developers



◯Preview期間中の制限

・Read、Write、Small Operationの回数に制限あり
1,000万/日 500/秒まで

ただしこれらは連絡すれば増やしてもらえるとのことです。



◯Memcacheの利用に関する注意

特にフレームワークでEntityの自動キャッシュ制御をしている場合には注意が必要です。
これまでは「DatastoreはAppEngine上のアプリからしか操作しない」という前提でした。
そのためアプリで使うフレームワーク(NDB等)で自動的にMemcacheにEntityデータの登録や削除を行い、
必ずその処理を通してDatastoreを操作することでDatastoreとキャッシュの内容が一致していました。

しかし、
例えば「AppEngine上のアプリから追加されたEntityをアプリ外からCloud Datastore API経由で更新する」というケースを考えた場合、
APIけ更新処理はアプリで使っているフレームワークの処理を通らないのでキャッシュが更新されません。
よってこのタイミングで Datastore のデータとキャッシュの内容がずれてしまうことになります。

同様に自前でクエリ結果をキャッシュしているような場合にも
Entityが更新されたタイミングでそのキャッシュを削除することができません。

AppEngine 上のアプリで Entity の自動キャッシュ制御をしている場合には、
外部から操作するKindだけキャッシュしないようにしたり、
それが困るならその処理だけ専用のAPIを自前で作ったり(Cloud DatastoreのAPIを使わない)とする必要があります。

APIのアプリのソースコードが公開されてAPIのアプリを置き換え可能になると良いのですが。



◯課金に関する注意

・少なくとも現時点ではAPIの使用量はAppEngineの使用量として加算されます
冒頭に書いたとおりAPIがAppEngine上で動作しているという仕組みを考えると納得ですが、
APIの使用量はAppEngineの使用量として加算され「Usage History」(「Billing History」)で確認できます。
そしてAppEngineの無料枠を超えた分が課金の対象になります。

これはPreview期間中だけの暫定仕様で、今後はAppEngineとは別の請求になる予定だそうです。
この時に Cloud Datastore の利用に無料枠があるかどうかわからないので、
もしかしたら試すには今が都合が良い時期かもしれません。

・API実行によって課金されるリソース
 ・Datastore Storage
 ・Datastore Writes
 ・Datastore Reads
 ・Small Datastore Operations
 ・Frontend Instance Hours
 ・Bandwidth Out

最初の4つはDatastoreの操作に伴うものなので当然ですが、
Frontendインスタンス利用時間と帯域使用量にも加算される点は注意です。

どちらもAPIがAppEngine上のアプリとして動作していると考えれば理解できます。
(挙動として理解はできるけどFrontendインスタンス使用量は課金対象から外してくれても良い気がする)

Bandwidth OutはAPIのレスポンスデータの通信分に適用されていると思います。
(Compute Engineから実行すればBandwidth Outはかからないかも)



◯ユースケース1 App EngineのバックエンドとしてCompute Engineを利用する



Cloud Datastore API によってApp EngineとCompute Engineの両方の環境からデータの共有が簡単になりました。

よって、
・AppEngineのアプリが保存する大量データの更新処理をCompute Engine上から行う
・DatastoreのデータをBigQueryに転送する前に加工する
・Compute Engine上で特殊なミドルウェアが必要な処理を行った上で処理結果をDatastoreに記録する

といった、App Engineで実装しづらい(できない)処理をCompute Engineで行う事なりました。
ただし前述のようにAppEngine上のアプリでキャッシュ制御をしている場合には留意してください。

Cloud Datastoreの登場によって、
AppEngineのバックエンドとしてのCompute Engineの価値が高まったと言えるでしょう。



◯ユースケース2 Compute Engineから使うNoSQLデータベースとして利用する


・App Engineの対応言語に縛られずにCompute Engine上のWebアプリからDatastoreを利用できる
・Compute Engineが使う「メンテナンスフリー・ダウンタイム無し」のデータ保存領域として利用できる

例えばCompute Engine上にnode.jsのサーバーを立ててデータ保存領域としてCloud Datastoreを使うことができます。

また、これまでCompute Engineでデータベースを利用したい場合には
Compute Engine上に自前でRDBやNoSQLデータベースを構築するかCloud SQLを使うという選択肢がありましたが、
今回新しくCloud Datastoreという選択肢が増えたと言えます。


また、Compute EngineからはCloud DatastoreのAPIを利用するための認証が簡単です。(詳しくは第2回に)



◯ユースケース 3 Googleのクラウド環境以外からDatastoreを利用する


JSON APIがあるので基本的に環境・言語を問わずに利用できます。
別のクラウド環境からも、自社にあるオンプレミスのサーバーからアクセスすることも可能です。

アプリのアップグレードに伴うデータの一括更新処理をローカル環境から実行するようなこともできます。
ただこのケースの場合にはCompute Engineから操作するのと比べて通信速度が遅いでしょうから、
Webアプリでユーザーに対してリアルタイムにレスポンスを返す目的で使うなら、
App EngineやCompute Engineから利用した方が良いかと思います。
どちらかというと、このケースはバックグラウンドでの処理や既存システムとの連携において有用でしょう。

(App Engine、Compute Engine以外からの利用は第3回に)



◯参考リンク

Ushering in the Next Generation of Cloud Computing (I/O Session)

What's New and Cool with Google Compute Engine (I/O Session)

Google Cloud Datastore 公式ドキュメント

Report of Google I/O 2013 Google Cloud Platform



◯次回以降の予定

第2回はCompute Engineから、第3回はその他の環境から、
サンプルコードを使ってCloud Datastore APIを実行してみようと思います。





このエントリーをはてなブックマークに追加

2013年5月17日金曜日

27.Google I/Oで発表されたGoogle Cloud Platformの新機能

このエントリーをはてなブックマークに追加





こんばんは。Ryo Yamasaki(@vierjp)です。

現在Google I/Oに参加するためサンフランシスコに来ています。
今は2日目の夜ですが、
Google Cloud Platformの変更点についてセッションを聞いて理解した限りで書こうと思います。
まだ検証もドキュメントの確認もちゃんとできていないので間違っている点もあるかもしれませんが予めご了承ください。
3日目のセッションを聞いた後や実際に検証した上で間違いに気がついたら訂正なり追加の記事なり書くと思います。

間違いに気がついた方はツッコミやご指摘いただけたら幸いです。


◯Google App Engine


◯PHP対応


https://developers.google.com/appengine/docs/php/

・概要
AppEngine上で使えるプログラミング言語として、
Python、Java、Goに続く第四の言語としてPHPが追加されました。
言語に関する要望の中では昔からPHPが一番多かったそうで、今回ついに対応となりました。


ドキュメント内ではデータアクセスとして書かれているのが
「Cloud SQL」と「Cloud Storage」のみで「Datastore」についての言及がありませんが、
セッション中のサンプルコードにはDatastoreへのアクセスっぽいコードが存在していました。
* 2013/5/23追記 セッションの動画を見直したら「Cloud Data Store」(後述)と言っていました。



* 2013/5/24追記 早速Limited Previewをもらえたので試してみました。以下の記事も参考にどうぞ。
・28.Google App Engine for PHP (GAE/PHP) を早速試してみた
・29.Google App Engine for PHPでWordPressを動かしてみた
・30.Google App Engine for PHPにおけるポータビリティを考える
・36.Google App Engine for PHPでWordPressを運用するためのプラグインが登場
・39.Google App Engine for PHPでCakePHPを動かしてみた


◯Modulalized Applications (Servers?)



スライドによって名称が「Modulalized Applications」だったり「Servers」だったりしましたが、同じ物を指していると思います。

おそらくTrusted Tester中に「Servers」と呼ばれていた機能で、
一つの「アプリ」の中に複数の「コンポーネント」を定義できるものではないかと。
これは現在の「アプリケーション」と「バージョン」の概念の間に存在するような仕組み。
たぶんこんな感じ?(想像図です)

概念は既存の「backends」の位置づけに似ていますが、
・スケーリング
・デプロイ
・バージョニング
・パフォーマンス設定
を「コンポーネント」毎にできるようになります。
例えば、
インスタンスクラス(F1等)やインスタンス数等の設定は「コンポーネント」毎に設定できます。
と言っても既存のBackendsでも上記については一応できていたので、
もう少し細かく設定できたり制限がなくなるといった要素もあるのかもしれません。
セッションでは「3つのコンポーネントに分けているアプリ」の例もありましたが、
「Frontendとbackends」という既存の分け方を、より柔軟にするような趣旨かと思います。

これが正式リリースされた際に既存の「Frontend」「Backend」がどうなるかは少し気になります。
(既存のアプリのために何らかの形で残りそうには思いますが)

以下についてはアプリ毎に存在し、各コンポーネントから共有する形になります。
・Datastore
・Memcache
・TaskQueue

この機能について、あるセッションでは「Launched Today」と書いてあったり、
また別のセッションでは「Limited Preview」と書いてあったりで、
リリース時期がいまいち不明です。

ただ、これがリリースされたらさすがに管理画面の構成が変わるはずですが、
現状変わっていないのでおそらくまだ「Limited Preview」なのでしょう。


◯Mobile Backend Starter




「サーバーサイドのコードを一切書かずにサーバーと連携するAndroidアプリを作る」ためのAppEngineアプリ。
「Mobile Backend Starter」は「Platformの拡張」ではなく、「AppEngineのアプリケーション」として提供されます。

以前からこのブログで扱っている「Google Cloud Endpoints」も近い要素がありますが、これは本当にサーバーの知識が不要です。

・サーバー側アプリをデプロイをするための環境を構築する必要が無い
→eclipseをダウンロードして、アプリのソースコードを取得してデプロイする・・・という作業が必要ありません。
Webの画面上から「Mobile Backend Starter」のアプリを直接デプロイすることができます。

・OAuth2の認証をサポート
 (内部的にはCloud Endpointsを使用しています)

・サーバー側のコードを書く必要が無い
→DAOをAPIとして公開するような作りになっていて、クライアント側からクエリの内容を指定してクエリを実行します。
 (詳しく見ていないけどアクセスコントロールの仕組みはちゃんとあるらしい)

・ContinuousQuery
 ・サーバー側でクエリの結果に影響するデータが登録されると自動でクライアント側の画面に反映される
  ・クライアント側からクエリを実行した際にProspective Searchに端末IDとクエリの内容を登録する
  ・データの更新時にProspectiveSearchが呼び出される(登録内容にマッチする更新が行われたか、が確認される)
  ・ProspectiveSearchに登録した条件にマッチした場合、GCMを使ってクライアント側に通知する
  ・クライアント側はGCMのMessageを受け取ると、API経由でクエリを再実行して「最新のクエリ結果」を取得して描画する

・クライアント側を楽に実装するための仕組みも用意されているらしい。

大々的に好評されていませんでしたが実は4/20ぐらいにはリリースされていて、少しソースを読んでいました。
「Mobile Solutions - Google Cloud Platform — Cloud Platform」で
「Try it now」ボタンを押して利用を開始する事ができます。

ソースコードも公開されているので、
ProspectiveSearchとGCMの利用方法のサンプルアプリとして見ても面白いと思います。



◯Cloud Datastore


https://developers.google.com/datastore/

「Blog @vierjp : 33.Google Cloud Datastoreを試してみた 概要編 (1/3)」に詳細をまとめ直しています。

◯概要
位置づけとしてはAppEngineのDatastoreを単独のサービスとして切り出したもの。
現在「Preview Release」という扱い。
概要レベルでは機能も同じとのこと。
ただしプログラムからの呼び出し方は大きく異なる模様。

これまでDatastoreはAppEngineの各アプリと1:1で完全に紐付いていて、
そのアプリ以外からアクセスすることができなかったが、
Cloud Datastoreとして単独のサービスになった事で、
異なるAppEngineアプリや環境から呼び出すことができるようになった。

複数のAppEngineのアプリから同一のDatastoreにアクセスしたり、
逆に一つのAppEngineのアプリから複数のDatastoreにアクセスしたり、
Compute Engineからアクセスしたりもできる。
さらには別のクラウドやオンプレミスのサーバーからもアクセスできる。


◯Cloud DatastoreのAPI
基本的に既存のDatastoreと機能は同じだそうですが、
ドキュメントを見る限りアクセス方法は異なります。
Protocol Buffer形式でリクエストを投げる「Protocol Buffers API」か、
JSON形式でリクエストを投げる「JSON API」を使用します。
Protocol Buffers APIは現状JavaとPythonから利用できます。
(現時点でGO用・PHP用は用意されていません)

注意が必要なのは既存のDatastoreアクセスに使っている「Low Level API」とは異なるという事。
そのため現在利用している「Low Levle APIベースのフレームワーク」は
「Cloud Datastore」に対してそのままでは使えないのでしょう。
(API Proxyレベルで処理を差し替えて対応できたりしないだろうか)
* 2013/06/11 追記 AppEngine上のアプリからはこれまで通りに使えます。
詳しくは「Blog @vierjp : 33.Google Cloud Datastoreを試してみた 概要編 (1/3)」を参照。

JSON API経由なら言語を問わず利用する事ができるので、
「node.jsのサーバーから利用する」ような事もできます。

JSON APIは既にAPIs Explorerに表示されています。
Discovery APIベースなので、多くの言語に対応するClient Libraryが公開されることでしょう。


◯ユースケース
- ケース1 AppEngineアプリのバックエンドとしてCompute Engineを使う場合

AppEngineのアプリが保存する大量データの処理にCompute Engineを使う。
データの保存領域に「Cloud Datastore」を使うことで、
時間のかかる大量データの処理をCompute Engine上のバッチから行う事ができる。
(Cloud DatastoreならAppEngineのアプリとCompute Engineの両方からアクセスできるので)

ComputeEngineを使っていわゆる「夜間バッチ」的な一括処理に使ったり、
DatastoreのデータをBigQueryに転送する前に加工したり、
という用途が思いつきます。

これまではAppEngineアプリの持つ大量データの処理は
「Shutdown対応」をした上でbackendsを使ったり、細かいたくさんのTaskに分散させて処理する等、
実装段階で手間をかけていましたが、それが軽減されるでしょう。


- ケース2 Compute Engine上にWebアプリを配置する場合
AppEngineを使わない場合でも、
例えばCompute Engine上にnode.jsのサーバーを立てて、
そのデータ保存領域としてCloud Datastoreを使うということもできます。

実際にI/O 3日目の「Code Labs」でこの説明が行われるようです。
「Getting Start」では「node.jsからGoogle Cloud Datastoreを使う方法」について書かれています。
Overview - Google Cloud Datastore — Google Developers


- ケース3
 AppEngine・ComputeEngine以外のクラウド環境からデータを操作する
 JSON APIがあるので基本的にどこからでも操作できるはずです。
 自社にあるオンプレミスのサーバーからアクセスすることもできますし、
 アプリのアップグレードに伴うデータの一括更新処理をローカル環境から実行するようなこともできるでしょう。
 (この場合ComputeEngineから動かすのと比べて速度が遅そうですが)


◯Preview期間中の制限
Preview期間中は、課金を有効にしている場合にも、
Read、Write、Small Operationそれぞれに対して
「10 million per day, no more than 500 per second」のlimitが設定されています。
(連絡すれば増やしてもらえるそうです)



◯Google Compute Engine



現地時間で15日についにGoogle Compute Engineが一般公開されたそうです。
以前と比べて変更されたのは以下のとおりです。

- Sub-hour Billing
 課金の時間単位が変わったそうです。
 最低課金額は10分単位だが、その後は一分単位で課金される、とのこと。

- Shared Core Instance
 →f1-micro,g1-smallの追加
  さらに安いインスタンスが追加されました。
  これまでの一番安いインスタンスが「n1-standard-1」の「$0.115/h」ですが、
  「f1-micro」は「$0.019/h」で格段にお安くなっています。
  実際にどのくらいのパフォーマンスがあるのかわかりませんが、
  メモリをそれほど必要としない長時間の処理に良いのでしょう。

- Advanced Routing
3日目のセッションで話されるそうですが未確認です。
* 2013/5/25追記 オンプレミス環境や他のクラウドとセキュアに繋ぐためのVPN的なもの・・・らしいです。

- Persistent Diskのサイズが最大10TBになった。
これまでの最大サイズ「1TB」から10倍の「10TB」になります。

参考:
・Google Compute Engine Pricing (料金体系)



◯Google Cloud Storage (2013/5/23追記)

すっかり忘れてましたがセッション動画見直していたら新機能があったので追記。

◯Offline Disk Import (Limited Preview) 
https://developers.google.com/storage/docs/early-access?hl=ja

アップロードしたいファイルをハードディスクに入れてGoogleに送ると、
Google内のネットワークからUploadしてくれる
という究極手段。
(最初ネタかと思ったのは内緒^-^;)

Google/IO 初日の5/15にサービス開始。

・手順
1.Offline Disk ImportのInterest Formから申し込み
2.SATAのハードディスクをencfs(暗号化ファイルシステム)でフォーマットする
3.データをハードディスクにコピーする
4.Googleに郵送する
5.Googleが顧客が所有する新しいbucketにimportする(Googleの高速なネットワークを使って)
6.GoogleがHDDを郵送で返す

* 現時点ではアメリカ国内の顧客限定です
ただし、間違いなく今後数ヶ月の間に国際的に利用できるように拡大するし、
優先順位が上がるかもしれないので海外の顧客も「Interest Form」から連絡してください、とのこと。

料金は通常のGoogle Cloud Storageの料金がリクエスト回数, 帯域使用料,ストレージ使用料」に加えて
HDD一つ毎に「$80」加算されます。

* 2013/5/24追記 Google Cloud Storeのセッションに関して別途詳細記事を書きました。
「31.Google Cloud Storageに大量データをアップロードする際のテクニック(Google I/O 2013)」



◯Google BigQuery (2013/5/28追記)

IO報告会(#Devfest)で知ったので追記。

◯Google Analytics and AdSense Data Analysis in BigQuery
AdSense BigQuery Integration Guide - AdSense — Google Developers

・BigQueryでGoogle AdSenseのデータを分析可能になりました
・Google Analyticsのデータは2013年9月の公開を目指しているそうです

AdsenseにしてもAnalyticsにしても、
BigQueryを使ってこれらと自社で持っている別の情報とをJoinして分析できる事が魅力です。

Google BigQueryのセッションに関して別途詳細記事を書きました。
「32.Google BigQueryでAnalyticsとAdSenseのデータを分析する(Google I/O 2013)」


概要レベルですが、今日はここまでとしてまた近日中に続報を書こうと思います。

以上、 San Franciscoからでした!



◯TopGate社 Google I/O フィードバックセミナー (2013/6/11追記)

2013/5/31に行われた「TopGate社 Google I/O フィードバックセミナー」で
私がGoogle Cloud Platform担当として発表したスライドです。
Report of Google I/O 2013 Google Cloud Platform

こちらも併せてどうぞ!




このエントリーをはてなブックマークに追加