110
GAE/J っっっっっっっっっっっっっ っっ

Sthseminar Gae 20090715

Embed Size (px)

Citation preview

Page 1: Sthseminar Gae 20090715

GAE/J ってどう使う?

スティルハウス 佐藤一憲

Page 2: Sthseminar Gae 20090715

GAE/J ってどう使う? • この資料のダウンロード

– http://tinyurl.com/kaz279

Page 3: Sthseminar Gae 20090715

自己紹介• スティルハウス 佐藤一憲

– id:kazunori_279– http://www.sth.co.jp/

• 主な業務– 開発

• Adobe Flex/AIR 、 Rails 、 GAE/J

– テクニカルライティングや翻訳• ペンネーム吉川和巳• @IT や ITpro など

– セミナー講師など

Page 4: Sthseminar Gae 20090715

アジェンダ• GAE と「ご都合 .com 」事例• GAE のサーバー構成とスケーラビリティ• Bigtable と Datastore

• Datastore ってどう使う?

Page 5: Sthseminar Gae 20090715

GAE と「ご都合 .com 」事例

Page 6: Sthseminar Gae 20090715

GAE とは• Google App Engine

– 自分のアプリを Google インフラで運用• Python 版と Java 版

– クラウドコンピューティングサービス• GAE のすごいところ

– 無償で使える– Bigtable が簡単に使える– サーバ構築不要、デプロイ簡単

Page 7: Sthseminar Gae 20090715

ご都合 .com (gotsugo.com)

• ご都合 .com– スケジュール共有ツー

ル– 会議・飲み会に– 無償・登録不要– 5/6 公開– 6 日間で 2000 UU

Page 8: Sthseminar Gae 20090715

ご都合 .com の中身• Flex クライアント• Google App Engine for Java サーバ

– BlazeDS– ビジネスロジックのクラス– Entity クラス

• 開発期間: 5 日間– 設計・実装・テスト・ドキュメント作成など

Page 9: Sthseminar Gae 20090715

BlazeDS を動かすには…• Martin 氏のブログを見よ!

– http://martinzoldano.blogspot.com/2009/04/appengine-adobe-blazeds-fix.html

– Martin さんありがとう!!• BlazeDS ソースのビルドが必要

– まずは BlazeDS をビルドする環境を用意• 要点

– JMX API の管理機能はオフに– AbstractAmfInput.java を修正– BaseHTTPEndpoint.java を修正

• 以下ページでも詳しく解説– http://tinyurl.com/gaetips

Page 10: Sthseminar Gae 20090715

GAE のサーバー構成とスケーラビリティ

Page 11: Sthseminar Gae 20090715

GAE の現状

• 現在の利用状況 – 8 万以上のアプリを収容 – 140M PV/day – 20 万人以上の開発者が利用

• 統合環境を提供 – サーバーの構築や管理が不要。デプロイが容易 – ログ管理、管理コンソールや各種ツールを提供

Page 12: Sthseminar Gae 20090715

GAE の特徴

• スケーラブルな Web アプリ構築のためのベストプラクティス利用を促す環境 – 個々のリクエストの処理時間やリソース消費を

制限 – ステートレス設計と内蔵 API の利用 – Datastore によるパーティション化されたデータ

モデルの利用

Page 13: Sthseminar Gae 20090715

GAE の特徴

• 非常に高いスケーラビリティと可用性を備える Google のクラスタ環境を簡単に利用できる – 自動クラスタリングによる負荷分散と高可用性を

提供 – アプリの負荷状況に応じて App Server を動的に

増減 – アプリ間の隔離性を維持、個々のアプリの安全性

とパフォーマンスを確保 • 既存の Google テクノロジーがベース

Page 14: Sthseminar Gae 20090715

Google App Engine Stack の構成

• Google App Engine Stack の構成

Page 15: Sthseminar Gae 20090715

GAE Stack の構成要素

• App Master – アプリのデプロイ管理やバージョン管理 – Front End への App Server 位置の通知 – 全体の統括

Page 16: Sthseminar Gae 20090715

GAE Stack の構成要素

• Front End – HTTP のリクエスト受信とレスポンス送信を担当 – リクエストはクライアントから最も近い Google デ

ータセンターに到着 – Google 内部ネットワークを経由して Front End に

到着 – リクエストは最大 10MB まで

• 独自ドメイン利用時は 1MB まで – スタティックコンテンツはスタティックファイルサ

ーバーに、ダイナミックコンテンツは App Serverに転送

Page 17: Sthseminar Gae 20090715

GAE Stack の構成要素

• スタティックファイルサーバー – スタティックコンテンツを提供するサーバー – スタティックコンテンツとは

• appengine-web.xml で static 要素に指定した、静的コンテンツ

• HTML ファイルや画像ファイル、動画ファイルなど

– アプリコードとは別の専用サーバーに配置される

– Front End が直接処理し、 App Server は関与しない

Page 18: Sthseminar Gae 20090715

GAE Stack の構成要素

• App Server – ダイナミックコンテンツのリクエストを処理。

アプリコードが動作するアプリサーバー – 多数のアプリを同時に収容し、アプリ間の隔離性

を確保する • 各 API のサーバー

– Datastore 、 Memcache など、各種 API に基づくサービスを提供

Page 19: Sthseminar Gae 20090715

GAE Stack の構成要素

• GAE が提供する API

Page 20: Sthseminar Gae 20090715

App Server について

• App Server の構成 – Java 版は JDK 1.6 ベースの JVM

• サンの 1.6 版 JVM をそのまま使っている • アプリのロードを速くするため( Server VM ではな

く) HotSpot Client VM を使っている • Jetty ベースのアプリケーションサーバー

Page 21: Sthseminar Gae 20090715

App Server について

• App Server によるアプリの扱い – アプリはステートレスに設計する

• アプリにステートは持たせない – ステート=サーブレットのフィールドなど、 JVM 上の状態や変

数– アプリが異なる App Server に移動する場合でも、ステートは引

き継がれない – 多数の App Server による負荷分散とフェイルオーバーを簡素化 – ステートは Datastore や memcache に保存する

– Datastore と memcache の内容は共有• どの App Server からでも同じ内容にアクセスできる• HttpSession は Datastore に保存される

– セッションの掃除が必要

Page 22: Sthseminar Gae 20090715

App Server について

• ApiProxy による API コールの扱い – API コールは ApiProxy を経由して実行される – 個々のリクエストの情報( ThreadLocal )を保存

する – API コールを AOP のようにインターセプトして、

その前後に追加処理を挿入できる • ログ記録など

– クラスパス変更( JAR 入れ替え?)だけで、 API のサービスプロバイダーを変更できる

• 単体テストの記述に便利

Page 23: Sthseminar Gae 20090715

App Server について

• App Server のサンドボックスによる制限– 30 秒以上を要するリクエスト処理

• 時間のかかる処理は Task Queue で– レスポンス送出時のデータストリーミング

• comet できない– ファイルシステムへの書き込み– 外部サーバへのソケット接続– スレッド生成– ガベージコレクション実行やシステム停止– カスタムクラスローダの利用

Page 24: Sthseminar Gae 20090715

App Server について

• GAE/J で利用可能な既存資産– Dependency Injection Frameworks

• Guice, Spring, etc.

– Aspect Oriented Programming• AspectJ, Spring AOP, etc.

– Web frameworks• Google Web Toolkit, Tapestry, BlazeDS (Flex), etc.• Grails (Just Announced!)

– Alternate JVM languages• Scala, Rhino, JRuby, Jython, Clojure, Groovy, PHP, etc.

Page 25: Sthseminar Gae 20090715

GAE のスケールアウト

• 高負荷時の自動デプロイ – 高負荷状態が 50 分程度続くと新しい App Server

が追加されデプロイされて負荷分散し、負荷が低くなるとアンデプロイされるようです

• http://groups.google.com/group/google-app-engine-japan/browse_thread/thread/24cedb6808d634bf

• アプリが受けられる負荷に上限はあるか? – 現状、 safety limit が設けられている。それを超え

る負荷を扱いたい場合は、 app engine チームに連絡してほしい。案件ごとに safety limit を解除できる

Page 26: Sthseminar Gae 20090715

GAE のスケールアウト事例

• ホワイトハウスの "Open For Questions" – 2 日間だけ提供された投票サイト

• その結果を受けてオバマ大統領が記者会見を行った – 10 万件の質問と 360 万件の投票を受け付けた

• ピーク時には毎秒 700回のクエリを実行した – GAE を out of box で使用

• Google Moderator のソースをベースに、ホワイトハウス側の開発者がチューンしてデプロイ。 Google側による特別な作り込み等は行っていない

– GAE 上の他のアプリケーションには一切影響なし

Page 27: Sthseminar Gae 20090715

GAE のスケールアウト事例

• "Open For Questions" のトラフィック推移

Page 28: Sthseminar Gae 20090715

そのほか

• Java 版と Python 版の性能の違いは? – きちんとした比較はしていない。使い方によっ

て異なるが、あまり大きな違いはない。 GAE 以外の環境で両者を比べた場合と同じ程度。

• Comet サポートは? – 現状ではレスポンスのストリーム送信に対応し

ないため、 Comet は使えない。現状、 Comet サポートはロードマップにはないが、 XMPP はサポート予定なので、プッシュ通信は実装できるようになる

Page 29: Sthseminar Gae 20090715

そのほか

• Virgin America 事例– http://www.dayinthecloud.com/lounge/

Page 30: Sthseminar Gae 20090715

Bigtable とは

Page 31: Sthseminar Gae 20090715

Bigtable の概要

• Bigtable とは –構造化データを管理するための分

散化ストレージ –膨大な数の汎用サーバーをつなげ

てペタバイト規模のデータを扱えるよう設計されている • 現在およそ数 PB

Page 32: Sthseminar Gae 20090715

Bigtable の概要

• Bigtable の歴史

–およそ 7人年の開発作業を経て、 2005年 4月からプロダクション利用を開始

–2006年時点では、 Google の 60 以上のプロジェクトが Bigtable を利用 •検索 , Analytics, Finance, Orkut, Earth, YouT

ube, Map など

Page 33: Sthseminar Gae 20090715

Bigtable の構成要素

• Bigtable のテーブル – Bigtable のテーブルは、「分散化された多次元ソートマップ」

• 簡単に言うと、ソート済みの Excel表のようなもの

• 個々のセルは履歴を残せる(multidimensional)

Page 34: Sthseminar Gae 20090715

Bigtable の構成要素

• Bigtable のテーブル – テーブルの実体は、巨大な key-value store

• キー:行キー+カラムキー+タイムスタンプ• 行キーは最大 64KB まで(大半は 10~ 100 バイト)

– 行キーの辞書順でソートされている – 行単位の CRUD はアトミックに実行

• 複数行にまたがる更新処理は原子性が保証されない –古い履歴データや削除された行は、自動的に GC

Page 35: Sthseminar Gae 20090715

Bigtable にできること• Bigtable にできること

–キーを指定した行単位の CRUD • 行単位の ACID 特性が保証される

– 2 種類のスキャン: • prefix scan: キーの前方一致検索で複数行を一括取得

• range scan: キーの範囲指定検索で複数行を一括取得

• ソート済みなので高速に実行できる

– Bigtable ではカラムの値に基づく検索は一切実行できない!

Page 36: Sthseminar Gae 20090715

Bigtable にできること• prefix scan と range scan

Page 37: Sthseminar Gae 20090715

Bigtable の内部構造

• Bigtable クラスター –複数の Bigtable テーブルからなるクラスター – 2006年 8月時点では、 388 の Bigtable クラ

スターと 24,500 のタブレットサーバーが稼働中

• (おおよそ 1~ 2PB)

Page 38: Sthseminar Gae 20090715

Bigtable の内部構造• Google の典型的なクラスターノード構成

Page 39: Sthseminar Gae 20090715

Bigtable の内部構造• 個々のノードの基本構成

– Intel ベースの安い PC – Linux OS – Scheduler スレーブ

• Scheduler マスターの指示に従ってノード上に各種サービスをデプロイする

– GFS チャンクサーバー • GFS のチャンク(データ)を保存する

– タブレットサーバー • Bigtable のタブレットを管理する

Page 40: Sthseminar Gae 20090715

Bigtable の内部構造• Bigtable クラスター全体を管理するサービ

ス– Scheduler マスター

• 各ノードに各種サービスをデプロイする – Chubby

• 分散ロックサービス – GFS マスター

• GFS チャンクサーバー群を統括する – Bigtable マスター

• tablet server群を統括する

Page 41: Sthseminar Gae 20090715

Bigtable の内部構造• タブレット

– Bigtable のテーブルを分割したもの • テーブルの内容はタブレット単位で各タブレット

サーバーに分散保存される • 1つのタブレットは 100~ 200MB 程度のデータを

保存。それ以上になると分割される • 1台のタブレットサーバーは 100 以下のタブレッ

トを保存

Page 42: Sthseminar Gae 20090715

Bigtable の内部構造• タブレット

–復旧が高速• 1台がダウンしても、その 100 個のタブレットは他の 100台のサーバーが保有している

– Bigtableマスターが負荷分散を管理• 高負荷のサーバーからタブレットを移動

Page 43: Sthseminar Gae 20090715

タブレットサーバーのメカニズム

• タブレットサーバーの階層問い合わせ

Page 44: Sthseminar Gae 20090715

タブレットサーバーのメカニズム

• タブレットサーバーの検索 – あるキーのデータを取得するとき

• クライアントはタブレットサーバーの IP アドレスを取得

• DNS ライクな階層問い合わせ –検索の流れ

• ブートストラップとなる Chubby サービスにアクセス• METADATA タブレットを持つタブレットサーバーの

IP アドレスを取得• METADATA タブレットには、各キーに対応するタブレ

ットサーバーの IP が記録されている

Page 45: Sthseminar Gae 20090715

タブレットサーバーのメカニズム

• タブレットサーバーの検索 – タブレットサーバーの検索には、最大で 3回

のネットワーク通信が必要• 通常はクライアント側に METADATA タブレット

内容がキャッシュされており、クライアントはタブレットサーバーに直接接続できる

Page 46: Sthseminar Gae 20090715

キャッシュ管理とディスクアクセス

• Bigtable のキャッシュ構造

GFS GFS GFS

SSTable (ディスク上のファイル)

memtable (キャッシュ)

minor compaction(ディスクに flush)

major compaction(ゴミファイルを GC)

テーブルへのアクセス コミットログ

Page 47: Sthseminar Gae 20090715

キャッシュ管理とディスクアクセス

• memtable によるキャッシュ管理– memtable は、タブレットにコミットされた

更新内容を記録するソート済みのバッファ – 個々の更新処理はディスク上のコミットログ

に記録され、更新内容は memtable に記録される

Page 48: Sthseminar Gae 20090715

キャッシュ管理とディスクアクセス

• マイナーコンパクション – memtable がいっぱいになると、その内容が

SSTable にフラッシュされる– ( Oracle の DBWR+チェックポイントと同様)

Page 49: Sthseminar Gae 20090715

キャッシュ管理とディスクアクセス

• SSTable によるディスク保存– SSTable とは、 memtable の内容保存に利用

されるファイルフォーマット – ソート済みのイミュータブル(変更不可)なマップ( key-value ペア)

• イミュータブルなのでファイルアクセス時のロックが不要、同時アクセスを効率的に扱える

• コピーオンライトですばやくタブレットを分割できる

Page 50: Sthseminar Gae 20090715

キャッシュ管理とディスクアクセス

• メジャーコンパクション–削除されたデータがゴミとして残るので、マーク&スウィープ GC を実行する

• ( PostgreSQL の vacuum と同様)

Page 51: Sthseminar Gae 20090715

GFS の利用• GFS によるディスクへの書き込み

– GFS ( Google File System )とは、 SSTable等のファイル保存に用いられるファイルシステム

– ファイルは必ず 3台以上のサーバーに書き込み • ローカルの GFSチャンクサーバーが空いていれば、

そこに 1 つを書き込み • 残り 2 つは、離れた場所(少なくとも同じラックで

はない場所)の GFSチャンクサーバーに書き込み

Page 52: Sthseminar Gae 20090715

GFS の利用• GFS によるデータのレプリケーション

Page 53: Sthseminar Gae 20090715

GFS の利用• GFS によるディスクへの書き込み

– タブレットが移動しない限り、タブレットサーバーはローカルの GFS チャンクサーバーにアクセス

– 負荷分散のためタブレットが移動すると、データは残したままタブレットのみ移動

– バイナリアップグレード時などに、できるだけローカルに置くようにデータを再配置

Page 54: Sthseminar Gae 20090715

Datastore とは

Page 55: Sthseminar Gae 20090715

Datastore API とは

• GAE におけるデータ保存用 API– Bigtable で実装

• Datastore API でできること– CRUD

• エンティティグループ単位で ACID を確保(後述)

– クエリ• JDOQL または GQL

Page 56: Sthseminar Gae 20090715

Datastore API とは

• GAE/J の Datastore API– JDO (Java Data Objects)

• オブジェクト永続化の標準 API• オブジェクトデータベース的• ドキュメントがもっとも豊富

– JPA (Java Persistence API)• ORM の標準 API

– low-level API• ドキュメントがあまりない

– DataNucleus ( JDO/JPA の OSS 実装)がベース

Page 57: Sthseminar Gae 20090715

Datastore API とは

• JDO による CRUD の例

        PersistenceManager pm = PMF.get().getPersistenceManager();

        Employee e = new Employee("Alfred", "Smith", new Date());

        try {            pm.makePersistent(e);        } finally {            pm.close();        }

Page 58: Sthseminar Gae 20090715

Datastore 用語

• Datastore 用語– カインド( kind )=クラス/テーブル– エンティティ( entity )=オブジェクト/レ

コード– プロパティ( property )=フィールド/カラ

ム–キー( key )= OID/プライマリーキー

Page 59: Sthseminar Gae 20090715

エンティティテーブル

• エンティティテーブルとは – エンティティを保存するテーブル

• GAE 内のすべてのエンティティテーブルが 1つのBigtable テーブルを共有

– 個々のエンティティは、キーで識別• キーの辞書順でソートされている

– 個々のエンティティのプロパティ内容は、すべて 1つのカラムにシリアライズされて格納

• Bigtable の履歴機能は使用していない

Page 60: Sthseminar Gae 20090715

キー• キー

– アプリ ID+パス+(エンティティ ID またはエンティティ名)

• エンティティ ID は自動採番• エンティティ名はアプリ側で設定

– パス• エンティティグループのルートエンティティまでのパス• /Grandparent:Ethel/Parent:Jane/Child:William

– キーの文字列表現• キーを含むプロトコルバッファを base64表示したもの

– キーによるエンティティ取得• 通常、 20~ 40ms で処理

– 参考• http://groups.google.com/group/google-appengine/browse_threa

d/thread/2d2805486432355e/ea6d4d2e3915d155?show_docid=ea6d4d2e3915d155

Page 61: Sthseminar Gae 20090715

キー/Grandparent:Alice /Grandparent:Alice/Parent:Sam /Grandparent:Ethel /Grandparent:Ethel/Parent:Jane /Grandparent:Ethel/Parent:Jane/Child:Timmy /Grandparent:Ethel/Parent:Jane/Child:William /Grandparent:Frank

カインド名 エンティティ ID

注:この図は概念図です(実際の実装ではありません)

Page 62: Sthseminar Gae 20090715

プロパティ• Datastore のプロパティの特徴

– variable properties • エンティティごとにプロパティの数や種類を変え

られる• 「プロパティがない」と「プロパティが null 」は区別される

– heterogenous property types • エンティティごとにプロパティの型を変えること

ができる • ( GAE/J でこれを使えるかは不明)

Page 63: Sthseminar Gae 20090715

プロパティ• Datastore のプロパティの特徴

– multi-value properties (List Property) • 1 つのプロパティに List や tuple を保存できる

– シリアライズして保存される

• クエリの例: name == 'Foo' – List 内のいずれか 1 つの値が Foo なら true になる

• 非正規化や設計の最適化に活用できる– 1:N 関連の代わりに使う(非正規化)– ジョインテーブルの代わりに使う

– Serializable オブジェクトを格納可能

Page 64: Sthseminar Gae 20090715

クエリ• Datastore のクエリとは

– 複数のエンティティを条件検索できる• 最大 1000件まで(!)• 通常、 160~ 200ms程度で処理

– 条件の記述方法• JDOQL• GQL

Query query = pm.newQuery("select from Employee " +                              "where lastName == lastNameParam " +                              "order by hireDate desc " +                              "parameters String lastNameParam")

    List<Employee> results = (List<Employee>) query.execute("Smith");

Page 65: Sthseminar Gae 20090715

クエリ• クエリは「インデックス+スキャン」で実装 – Bigtable はクエリをサポートしていない

• 「値」に基づく検索を実行できない

– GAE のアプリが実行するすべてのクエリは、インデックスとスキャンの組み合わせで実装される

– クエリを記述すると自動的にインデックスを作成

• 明示的な作成も可能

Page 66: Sthseminar Gae 20090715

クエリ• クエリの流れ

– 検索/ソート条件に合うインデックスを探す

– インデックスをスキャンし、検索条件に合うエンティティを取得

– インデックスの終端か 1000 件までスキャン

Page 67: Sthseminar Gae 20090715

クエリ• クエリの多用は避けよう

– クエリを多用/誤用するとインデックスが増え、更新処理が遅くなる

• インデックス爆発( index explosion)– RDB とは違い制約も多い(後述)

Page 68: Sthseminar Gae 20090715

インデックス• Datastore のインデックスとは

– エンティティテーブルとは別のテーブル– 3 種類のインデックス

• カインドインデックス • シングルプロパティインデックス • コンポジットインデックス

Page 69: Sthseminar Gae 20090715

カインドインデックス

• カインド名順でソートされたインデックス – あるクラスのすべてのエンティティの一覧を

提供 – 例: Grandparent でカインドインデックスを

スキャン

SELECT * FROM Grandparent

Page 70: Sthseminar Gae 20090715

カインドインデックス

• カインドインデックス

Page 71: Sthseminar Gae 20090715

シングルプロパティインデックス

• 「エンティティの特定のプロパティ値」の順番でソートされたインデックス

• 特定のプロパティを条件に検索/ソートできる– 例: name == 'John' – 例: ORDER BY name – 例: age > 10 AND age <= 20

Page 72: Sthseminar Gae 20090715

シングルプロパティインデックス

• シングルプロパティインデックス

Page 73: Sthseminar Gae 20090715

シングルプロパティインデックス

• 昇順用と降順用の 2つが作成される– 例: name プロパティの範囲指定+ name プロ

パティでソート – name プロパティのインデックスで range スキャン

WHERE name >= 'B' AND name < 'C' ORDER BY name

Page 74: Sthseminar Gae 20090715

コンポジットインデックス

• 「複数のプロパティ値の組み合わせ」でソートされたインデックス– index.yaml で明示的に指定して作成

• 複数のプロパティ値で検索できる– 例: lastname = 'Smith' AND firstname >= 'B' AND first

name < 'C' – インデックスで「 Parent/Smith/B 」から「 Parent/Smi

th/C 」まで range スキャン– inequality filter ( < <= > >= )はひとつのプロパティ

に対してのみ利用できる

Page 75: Sthseminar Gae 20090715

コンポジットインデックス• コンポジットインデックス

Page 76: Sthseminar Gae 20090715

コンポジットインデックス

• コンポジットインデックスは使うべきか? – すべてのプロパティ値の順列組み合わせでイン

デックス内容が作成されるので、インデックスサイズが膨大になりやすい

Page 77: Sthseminar Gae 20090715

マージジョイン

• マージジョイン( merge join )とは – コンポジットインデックスに頼らずに複数プ

ロパティの条件検索を実現–複数のシングルプロパティインデックスをマージしながら検索

– "zig-zag" アルゴリズムにより、個々のインデックスを並行してスキャン

Page 78: Sthseminar Gae 20090715

マージジョイン

• zig-zag アルゴリズムによるマージジョイン

Page 79: Sthseminar Gae 20090715

エンティティグループとトランザクション

Page 80: Sthseminar Gae 20090715

CAP と BASE

• CAP定理– Consistency, Availability, Partition

• クラウド( P)では C と A 間のトレードオフが発生する• ACID 保証の範囲を制限しないとスケーラビリティが頭打ちに

• BASE トランザクション– CAP定理を反映した、クラウドでのトランザクション– Basically Available

• 可用性の高さを優先する(悲観排他より楽観排他)– Soft-State and Eventually Consistent

• 状態の一時的な不整合を許容する• 結果的に整合性が確保される仕組みにしておく• 例: DNS 、 Google Wave の OT

Page 81: Sthseminar Gae 20090715

エンティティグループとは

• エンティティグループとは– エンティティの階層構造

• 親( parent)と子( children)

• エンティティグループのルートエンティティ( root entity)

– デフォルトでは• 個々のエンティティは個別

のエンティティグループを形成

Dept

Emp

1

*

Page 82: Sthseminar Gae 20090715

エンティティグループとは• エンティティグループの指定方法

– JDO の owned 関係• User と Address 間で親子関係を定義• unowned 関係はサポートしていない

– エンティティグループが個別になるので ACID を保証できないため

– 明示的な指定• 子のキーを、親のエンティティのキーを使って生成する

– 詳しい手順: http://d.hatena.ne.jp/uehaj/20090509/1241856856

– OOP や RDB の「関連(リレーション)」ではない

• 関連をそのまま当てはめると問題も(後述)

Page 83: Sthseminar Gae 20090715

エンティティグループとは

• エンティティグループの 2つの役割– ローカリティを指定する

• パフォーマンスの向上– トランザクション・スコープを指定

• ACID の保証– CAP定理とエンティティグループ

• 特定範囲(ローカリティ)のみを対象に ACID 保証

001 abc002 def

ACID

Page 84: Sthseminar Gae 20090715

ローカリティ• ローカリティ

– エンティティグループのすべてのエンティティは、1つのサーバーに保存される確率が高い

• より高いパフォーマンスが期待できる– 参考: http://groups.google.com/group/google-appengine-java/browse_thread/thread/fd758c65e14b5c76/e4afc1e348a36a36?

show_docid=e4afc1e348a36a36

• 大量のエンティティがある場合は複数サーバーに分割• GFS によりデータは他 2 カ所にバックアップされる• キー順でソートされている /Grandparent:Alice

/Grandparent:Alice/Parent:Sam /Grandparent:Ethel /Grandparent:Ethel/Parent:Jane /Grandparent:Ethel/Parent:Jane/Child:Timmy /Grandparent:Ethel/Parent:Jane/Child:William /Grandparent:Frank

Page 85: Sthseminar Gae 20090715

トランザクション・スコープ

• Datastore のトランザクション・スコープ– トランザクションの開始( begin )と終了( com

mit/rollback )を指示する– エンティティ・グループ

• エンティティグループ単位で ACID を保証 – Bigtable は行単位の ACID しか保証しない– Datastore ではエンティティグループ単位での A

CID を保証している• SERIALIZABLE相当

– 異なるエンティティグループ間では保証されない

Page 86: Sthseminar Gae 20090715

Datastore と BASE

• 楽観的排他制御( optimistic lock)を実装 – エンティティグループのルートエンティティにて、

トランザクションの最終コミット時間のタイムスタンプを記録

– トランザクションの開始時に同タイムスタンプを確認

– コミット時にタイムスタンプを再度確認する • タイムスタンプが変化していなければ、更新内容を保存し

て、タイムスタンプを更新• タイムスタンプが変化してれば、他のトランザクションと

の競合が発生しているので、トランザクションをロールバック

Page 87: Sthseminar Gae 20090715

Datastore と BASE

• 楽観的排他制御( optimistic lock)を実装 – RDB の悲観的排他制御( SELECT FOR

UPDATE)のようにエンティティをロックしない

– スループットは高いが、競合時のリトライが必要• Python 版は 3回まで自動リトライし、 Java 版は自動

リトライしない

Page 88: Sthseminar Gae 20090715

Datastore と BASE

• リトライの例        for (int i = 0; i < NUM_RETRIES; i++) {            pm.currentTransaction().begin();

            ClubMembers members = pm.getObjectById(ClubMembers.class, "k12345");            members.incrementCounterBy(1);

            try {                pm.currentTransaction().commit();                break;

            } catch (JDOCanRetryException ex) {                if (i == (NUM_RETRIES - 1)) {                     throw ex;                }            }        }

Page 89: Sthseminar Gae 20090715

Datastore と BASE

• 分散トランザクションへの対応 – GAE では異なるエンティティグループ間の分散

トランザクション(グローバルトランザクション)はサポートされていない

– ただしアプリレベルでの実装例はある • http://code.google.com/intl/ja/events/io/sessions/Design

DistributedTransactionLayerAppEngine.html • http://code.google.com/intl/ja/events/io/sessions/Transa

ctionsAcrossDatacenters.html

Page 90: Sthseminar Gae 20090715

GAE トランザクションの注意点• 1 TX = 1 エンティティグループ

– 1つのトランザクション内では、 1つのエンティティグループの更新処理しか実行できない

– 複数のエンティティグループを更新する場合は、個別のトランザクションが必要

• =ルートエンティティの更新は個別 TX が必要• @Transactional を使うと便利

– http://d.hatena.ne.jp/kazunori_279/20090711/1247300915

• 1つのエンティティの更新は 1回まで– 1つのトランザクション内では、 1つのエンティティ

を複数回更新できない

Page 91: Sthseminar Gae 20090715

GAE トランザクションの注意点• トランザクション内で実行可能なクエリの制

限– ancestor filter を持つクエリのみ実行可能– コミット前の値は読み込みできない

• READ_COMMITTED相当• http://code.google.com/intl/ja/appengine/articles/transa

ction_isolation.html • http://groups.google.com/group/google-appengine-java/

browse_thread/thread/4a67044929428295

Page 92: Sthseminar Gae 20090715

エンティティグループの注意点

• 1つのエンティティグループにトランザクション負荷を集中させない– エンティティグループの利用は必

要最小限に抑えた方が性能は向上する

– リレーショナルモデルやオブジェクト指向の関連をそのままあてはめると問題が生じることも

Dept

Emp

1

*

Page 93: Sthseminar Gae 20090715

エンティティグループの注意点

• 例: 1つの Department と 1000 の Employee– 1000 の Employee のうち、いずれか 1つの

Employee しか同時にトランザクションを実行できない

–他はエラーとなりリトライが必要• 例: 1つの User と数個の Address

– 1 人のユーザーの住所に対して複数のトランザクションが同時実行される頻度は低い

– 負荷は集中しない

Page 94: Sthseminar Gae 20090715

エンティティグループの注意点

• 連番の採番はどうする? – 例:メッセージの 1 つ 1 つに、書き込み順の連番を振る

– 単純な方法:採番用エンティティの MessageIndex をルートエンティティとし、 Message を子とする

• MessageIndex と Message が同じトランザクションで更新処理され、 ACID を確保できる

• しかし大量のメッセージ書き込みには対応できない

Page 95: Sthseminar Gae 20090715

エンティティグループの注意点

• 連番の採番はどうする? –対処方法:採番用エンティティを分散化する

• ユーザーごとの採番用エンティティ UserIndex をルートエンティティとし、 Message を子とする

• Message の ID には「タイムスタンプ+ユーザー ID+UserIndex で採番した値」を設定する

• UserIndex 単位のトランザクションとなり、大量の書き込みが可能となる

• タイムスタンプ順でソート可能で、かつユニークな ID となる

Page 96: Sthseminar Gae 20090715

エンティティグループの注意点

• 連番の採番はどうする? –別の対処方法: GUID ベースとする

• 「タイムスタンプ+長い乱数」など –参照: Google I/O 2008 - Building Scalable Web

Apps with App Engine

Page 97: Sthseminar Gae 20090715

Datastore ってどう使う?

Page 98: Sthseminar Gae 20090715

Datastore のパフォーマンス

• Datastore のパフォーマンスは、エンティティの数とは無関係 – 保存されているエンティティが 1件でも、 1000件でも、

1000万件でも、パフォーマンスに変化はない

• 個々のエンティティに対する更新処理のスピード– 平均 100ms程度

• http://code.google.com/status/appengine/– 個々のエンティティの更新処理は遅い – アプリケーションのパフォーマンスを決めるのは、更新

処理の実装方法。参照処理は桁違いに速い • 平均数 10ms程度

Page 99: Sthseminar Gae 20090715

Datastore のパフォーマンス

• 対応策 – エンティティグループを分散させる

• エンティティグループが個別ならば、同時に何千でも並列処理できる

– memcache や Task Queue を活用する• memcache は高速

– 参照・更新ともに 5ms 前後

• リクエスト処理では Task Queueへの登録だけ行い、処理結果は後で表示する

• リクエスト処理では memcache に書き込み、 Task Queue のバックグラウンド処理でエンティティに保存する

Page 100: Sthseminar Gae 20090715

Datastore にできないこと

• テーブル間の join ができない – 非正規化して対処する

• 「正規化するな、 JOIN済みのでっかいテーブルを作れ」

select * from PERSON p, ADDRESS a

where a.person_id = p.id and p.age > 25 and a.country = “US”

select from com.example.Person where age > 25 and country = “US”

– 複数のクエリに分割する – List Property を使う

Page 101: Sthseminar Gae 20090715

Datastore にできないこと

• 集約関数がない( group by できない) – count() で全件カウントできない

• 毎回対象データをすべて取得してループで集計するのは非効率(また最大 1000件の制限がある)

• 集約したい値は、集約用のエンティティを用意して集計

– sharding counter: 書き込みが集中しないように複数のエンティティに分散して書き込みし、後で集計

– memcache counter: memcache に書き込みし、 Task Queueでエンティティに保存

Page 102: Sthseminar Gae 20090715

Datastore にできないこと

• 集約関数がない( group by できない) – max()/min() で最大値/最小値を得られない

• 対象プロパティで降順/昇順でソートして、 1件目の値を得る

Page 103: Sthseminar Gae 20090715

Datastore にできないこと

• 関数やストアドプロシージャはない – toUpper/toLower などがない – 別のプロパティを設け、 toUpper済みの値を入

れる –書き込み時にできるだけ事前処理を行っておくことで、読み込みを高速化できる

Page 104: Sthseminar Gae 20090715

Datastore にできないこと

• クエリの構文の制約 – 全文検索ができない

• LIKE による部分一致検索はできない • 前方一致なら可能: name >= 'a' AND name <= 'a<

UTF-8 コードポイントの最大値> ' • 検索対象の文字列を形態素解析し、ワードごとのイン

デックスを作成する

Page 105: Sthseminar Gae 20090715

Datastore にできないこと

• そのほかの制約 – OR 、 != が使えない– inequality filters ( < <= >= > )は 1 つのプロパティに

のみ利用可能– Text型や Blob型のプロパティはインデックスを作成

できない(クエリできない)– あるプロパティで inequality filters を使うと、他のプロ

パティを最優先にしたソートができない

• 対策– コーディングで対処– ド・モルガンの法則で書き換える

Page 106: Sthseminar Gae 20090715

そのほかの tips

• インデックスの更新 – エンティティの更新処理ではインデックスも更新

• インデックスは最小限に抑えないと性能が落ちる

– GAE ではクエリの利用を最小限に抑えた方がよい

• 「クエリは使ったら負け」 byひがさん

Page 107: Sthseminar Gae 20090715

そのほかの tips

• インデックス更新の回避方法 –頻繁に変更されるデータに対し、範囲指定検索す

るにはどうすればよいか?• 変更のたびにたくさんの index を更新したくない

–検索用のデータを適宜計算して持たせる

Page 108: Sthseminar Gae 20090715

そのほかの tips

• オークションサイトの例–価格帯ごとのオークション一覧を表示したい– あるオークションの価格が変化したら

• 「 0~ 1000円のオークション」のフラグをそのオークションの LP に追加しておく

• 範囲指定検索が不要になり、 LP に対する equality filterですばやく検索できる

Page 109: Sthseminar Gae 20090715

RDB から GAEへの移行

• プライマリーキーの扱い – 単一カラムの場合はそのまま使える –複合キーの場合は entity の親子関係に置き換える – N:N 用のマッピングデーブル(ジョインテーブ

ル)は List Property に置き換える

Page 110: Sthseminar Gae 20090715

RDB から GAEへの移行

• トランザクションの扱い – データモデルからエンティティのルートエンティ

ティを見つける • オンラインサービスでは「ユーザー」がルートに最適

な場合が多い –複数のエンティティグループに対するトランザク

ションが必要な場合 • システムの仕様を見直す • 補償トランザクションを実装する

– 2つめのトランザクションが失敗した場合は、最初のトランザクションをキャンセルする、など