sthseminar gae 20090715
TRANSCRIPT
GAE/J ってどう使う?
スティルハウス 佐藤一憲
GAE/J ってどう使う? • この資料のダウンロード
– http://tinyurl.com/kaz279
自己紹介• スティルハウス 佐藤一憲
– id:kazunori_279– http://www.sth.co.jp/
• 主な業務– 開発
• Adobe Flex/AIR 、 Rails 、 GAE/J
– テクニカルライティングや翻訳• ペンネーム吉川和巳• @IT や ITpro など
– セミナー講師など
アジェンダ• GAE と「ご都合 .com 」事例• GAE のサーバー構成とスケーラビリティ• Bigtable と Datastore
• Datastore ってどう使う?
GAE と「ご都合 .com 」事例
GAE とは• Google App Engine
– 自分のアプリを Google インフラで運用• Python 版と Java 版
– クラウドコンピューティングサービス• GAE のすごいところ
– 無償で使える– Bigtable が簡単に使える– サーバ構築不要、デプロイ簡単
ご都合 .com (gotsugo.com)
• ご都合 .com– スケジュール共有ツー
ル– 会議・飲み会に– 無償・登録不要– 5/6 公開– 6 日間で 2000 UU
ご都合 .com の中身• Flex クライアント• Google App Engine for Java サーバ
– BlazeDS– ビジネスロジックのクラス– Entity クラス
• 開発期間: 5 日間– 設計・実装・テスト・ドキュメント作成など
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
GAE のサーバー構成とスケーラビリティ
GAE の現状
• 現在の利用状況 – 8 万以上のアプリを収容 – 140M PV/day – 20 万人以上の開発者が利用
• 統合環境を提供 – サーバーの構築や管理が不要。デプロイが容易 – ログ管理、管理コンソールや各種ツールを提供
GAE の特徴
• スケーラブルな Web アプリ構築のためのベストプラクティス利用を促す環境 – 個々のリクエストの処理時間やリソース消費を
制限 – ステートレス設計と内蔵 API の利用 – Datastore によるパーティション化されたデータ
モデルの利用
GAE の特徴
• 非常に高いスケーラビリティと可用性を備える Google のクラスタ環境を簡単に利用できる – 自動クラスタリングによる負荷分散と高可用性を
提供 – アプリの負荷状況に応じて App Server を動的に
増減 – アプリ間の隔離性を維持、個々のアプリの安全性
とパフォーマンスを確保 • 既存の Google テクノロジーがベース
Google App Engine Stack の構成
• Google App Engine Stack の構成
GAE Stack の構成要素
• App Master – アプリのデプロイ管理やバージョン管理 – Front End への App Server 位置の通知 – 全体の統括
GAE Stack の構成要素
• Front End – HTTP のリクエスト受信とレスポンス送信を担当 – リクエストはクライアントから最も近い Google デ
ータセンターに到着 – Google 内部ネットワークを経由して Front End に
到着 – リクエストは最大 10MB まで
• 独自ドメイン利用時は 1MB まで – スタティックコンテンツはスタティックファイルサ
ーバーに、ダイナミックコンテンツは App Serverに転送
GAE Stack の構成要素
• スタティックファイルサーバー – スタティックコンテンツを提供するサーバー – スタティックコンテンツとは
• appengine-web.xml で static 要素に指定した、静的コンテンツ
• HTML ファイルや画像ファイル、動画ファイルなど
– アプリコードとは別の専用サーバーに配置される
– Front End が直接処理し、 App Server は関与しない
GAE Stack の構成要素
• App Server – ダイナミックコンテンツのリクエストを処理。
アプリコードが動作するアプリサーバー – 多数のアプリを同時に収容し、アプリ間の隔離性
を確保する • 各 API のサーバー
– Datastore 、 Memcache など、各種 API に基づくサービスを提供
App Server について
• App Server の構成 – Java 版は JDK 1.6 ベースの JVM
• サンの 1.6 版 JVM をそのまま使っている • アプリのロードを速くするため( Server VM ではな
く) HotSpot Client VM を使っている • Jetty ベースのアプリケーションサーバー
App Server について
• App Server によるアプリの扱い – アプリはステートレスに設計する
• アプリにステートは持たせない – ステート=サーブレットのフィールドなど、 JVM 上の状態や変
数– アプリが異なる App Server に移動する場合でも、ステートは引
き継がれない – 多数の App Server による負荷分散とフェイルオーバーを簡素化 – ステートは Datastore や memcache に保存する
– Datastore と memcache の内容は共有• どの App Server からでも同じ内容にアクセスできる• HttpSession は Datastore に保存される
– セッションの掃除が必要
App Server について
• ApiProxy による API コールの扱い – API コールは ApiProxy を経由して実行される – 個々のリクエストの情報( ThreadLocal )を保存
する – API コールを AOP のようにインターセプトして、
その前後に追加処理を挿入できる • ログ記録など
– クラスパス変更( JAR 入れ替え?)だけで、 API のサービスプロバイダーを変更できる
• 単体テストの記述に便利
App Server について
• App Server のサンドボックスによる制限– 30 秒以上を要するリクエスト処理
• 時間のかかる処理は Task Queue で– レスポンス送出時のデータストリーミング
• comet できない– ファイルシステムへの書き込み– 外部サーバへのソケット接続– スレッド生成– ガベージコレクション実行やシステム停止– カスタムクラスローダの利用
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.
GAE のスケールアウト
• 高負荷時の自動デプロイ – 高負荷状態が 50 分程度続くと新しい App Server
が追加されデプロイされて負荷分散し、負荷が低くなるとアンデプロイされるようです
• http://groups.google.com/group/google-app-engine-japan/browse_thread/thread/24cedb6808d634bf
• アプリが受けられる負荷に上限はあるか? – 現状、 safety limit が設けられている。それを超え
る負荷を扱いたい場合は、 app engine チームに連絡してほしい。案件ごとに safety limit を解除できる
GAE のスケールアウト事例
• ホワイトハウスの "Open For Questions" – 2 日間だけ提供された投票サイト
• その結果を受けてオバマ大統領が記者会見を行った – 10 万件の質問と 360 万件の投票を受け付けた
• ピーク時には毎秒 700回のクエリを実行した – GAE を out of box で使用
• Google Moderator のソースをベースに、ホワイトハウス側の開発者がチューンしてデプロイ。 Google側による特別な作り込み等は行っていない
– GAE 上の他のアプリケーションには一切影響なし
そのほか
• Java 版と Python 版の性能の違いは? – きちんとした比較はしていない。使い方によっ
て異なるが、あまり大きな違いはない。 GAE 以外の環境で両者を比べた場合と同じ程度。
• Comet サポートは? – 現状ではレスポンスのストリーム送信に対応し
ないため、 Comet は使えない。現状、 Comet サポートはロードマップにはないが、 XMPP はサポート予定なので、プッシュ通信は実装できるようになる
そのほか
• Virgin America 事例– http://www.dayinthecloud.com/lounge/
Bigtable とは
Bigtable の概要
• Bigtable とは –構造化データを管理するための分
散化ストレージ –膨大な数の汎用サーバーをつなげ
てペタバイト規模のデータを扱えるよう設計されている • 現在およそ数 PB
Bigtable の概要
• Bigtable の歴史
–およそ 7人年の開発作業を経て、 2005年 4月からプロダクション利用を開始
–2006年時点では、 Google の 60 以上のプロジェクトが Bigtable を利用 •検索 , Analytics, Finance, Orkut, Earth, YouT
ube, Map など
Bigtable の構成要素
• Bigtable のテーブル – Bigtable のテーブルは、「分散化された多次元ソートマップ」
• 簡単に言うと、ソート済みの Excel表のようなもの
• 個々のセルは履歴を残せる(multidimensional)
Bigtable の構成要素
• Bigtable のテーブル – テーブルの実体は、巨大な key-value store
• キー:行キー+カラムキー+タイムスタンプ• 行キーは最大 64KB まで(大半は 10~ 100 バイト)
– 行キーの辞書順でソートされている – 行単位の CRUD はアトミックに実行
• 複数行にまたがる更新処理は原子性が保証されない –古い履歴データや削除された行は、自動的に GC
Bigtable にできること• Bigtable にできること
–キーを指定した行単位の CRUD • 行単位の ACID 特性が保証される
– 2 種類のスキャン: • prefix scan: キーの前方一致検索で複数行を一括取得
• range scan: キーの範囲指定検索で複数行を一括取得
• ソート済みなので高速に実行できる
– Bigtable ではカラムの値に基づく検索は一切実行できない!
Bigtable にできること• prefix scan と range scan
Bigtable の内部構造
• Bigtable クラスター –複数の Bigtable テーブルからなるクラスター – 2006年 8月時点では、 388 の Bigtable クラ
スターと 24,500 のタブレットサーバーが稼働中
• (おおよそ 1~ 2PB)
Bigtable の内部構造• Google の典型的なクラスターノード構成
Bigtable の内部構造• 個々のノードの基本構成
– Intel ベースの安い PC – Linux OS – Scheduler スレーブ
• Scheduler マスターの指示に従ってノード上に各種サービスをデプロイする
– GFS チャンクサーバー • GFS のチャンク(データ)を保存する
– タブレットサーバー • Bigtable のタブレットを管理する
Bigtable の内部構造• Bigtable クラスター全体を管理するサービ
ス– Scheduler マスター
• 各ノードに各種サービスをデプロイする – Chubby
• 分散ロックサービス – GFS マスター
• GFS チャンクサーバー群を統括する – Bigtable マスター
• tablet server群を統括する
Bigtable の内部構造• タブレット
– Bigtable のテーブルを分割したもの • テーブルの内容はタブレット単位で各タブレット
サーバーに分散保存される • 1つのタブレットは 100~ 200MB 程度のデータを
保存。それ以上になると分割される • 1台のタブレットサーバーは 100 以下のタブレッ
トを保存
Bigtable の内部構造• タブレット
–復旧が高速• 1台がダウンしても、その 100 個のタブレットは他の 100台のサーバーが保有している
– Bigtableマスターが負荷分散を管理• 高負荷のサーバーからタブレットを移動
タブレットサーバーのメカニズム
• タブレットサーバーの検索 – あるキーのデータを取得するとき
• クライアントはタブレットサーバーの IP アドレスを取得
• DNS ライクな階層問い合わせ –検索の流れ
• ブートストラップとなる Chubby サービスにアクセス• METADATA タブレットを持つタブレットサーバーの
IP アドレスを取得• METADATA タブレットには、各キーに対応するタブレ
ットサーバーの IP が記録されている
タブレットサーバーのメカニズム
• タブレットサーバーの検索 – タブレットサーバーの検索には、最大で 3回
のネットワーク通信が必要• 通常はクライアント側に METADATA タブレット
内容がキャッシュされており、クライアントはタブレットサーバーに直接接続できる
キャッシュ管理とディスクアクセス
• Bigtable のキャッシュ構造
GFS GFS GFS
SSTable (ディスク上のファイル)
memtable (キャッシュ)
minor compaction(ディスクに flush)
major compaction(ゴミファイルを GC)
テーブルへのアクセス コミットログ
キャッシュ管理とディスクアクセス
• memtable によるキャッシュ管理– memtable は、タブレットにコミットされた
更新内容を記録するソート済みのバッファ – 個々の更新処理はディスク上のコミットログ
に記録され、更新内容は memtable に記録される
キャッシュ管理とディスクアクセス
• マイナーコンパクション – memtable がいっぱいになると、その内容が
SSTable にフラッシュされる– ( Oracle の DBWR+チェックポイントと同様)
キャッシュ管理とディスクアクセス
• SSTable によるディスク保存– SSTable とは、 memtable の内容保存に利用
されるファイルフォーマット – ソート済みのイミュータブル(変更不可)なマップ( key-value ペア)
• イミュータブルなのでファイルアクセス時のロックが不要、同時アクセスを効率的に扱える
• コピーオンライトですばやくタブレットを分割できる
キャッシュ管理とディスクアクセス
• メジャーコンパクション–削除されたデータがゴミとして残るので、マーク&スウィープ GC を実行する
• ( PostgreSQL の vacuum と同様)
GFS の利用• GFS によるディスクへの書き込み
– GFS ( Google File System )とは、 SSTable等のファイル保存に用いられるファイルシステム
– ファイルは必ず 3台以上のサーバーに書き込み • ローカルの GFSチャンクサーバーが空いていれば、
そこに 1 つを書き込み • 残り 2 つは、離れた場所(少なくとも同じラックで
はない場所)の GFSチャンクサーバーに書き込み
GFS の利用• GFS によるデータのレプリケーション
GFS の利用• GFS によるディスクへの書き込み
– タブレットが移動しない限り、タブレットサーバーはローカルの GFS チャンクサーバーにアクセス
– 負荷分散のためタブレットが移動すると、データは残したままタブレットのみ移動
– バイナリアップグレード時などに、できるだけローカルに置くようにデータを再配置
Datastore とは
Datastore API とは
• GAE におけるデータ保存用 API– Bigtable で実装
• Datastore API でできること– CRUD
• エンティティグループ単位で ACID を確保(後述)
– クエリ• JDOQL または GQL
Datastore API とは
• GAE/J の Datastore API– JDO (Java Data Objects)
• オブジェクト永続化の標準 API• オブジェクトデータベース的• ドキュメントがもっとも豊富
– JPA (Java Persistence API)• ORM の標準 API
– low-level API• ドキュメントがあまりない
– DataNucleus ( JDO/JPA の OSS 実装)がベース
Datastore API とは
• JDO による CRUD の例
PersistenceManager pm = PMF.get().getPersistenceManager();
Employee e = new Employee("Alfred", "Smith", new Date());
try { pm.makePersistent(e); } finally { pm.close(); }
Datastore 用語
• Datastore 用語– カインド( kind )=クラス/テーブル– エンティティ( entity )=オブジェクト/レ
コード– プロパティ( property )=フィールド/カラ
ム–キー( key )= OID/プライマリーキー
エンティティテーブル
• エンティティテーブルとは – エンティティを保存するテーブル
• GAE 内のすべてのエンティティテーブルが 1つのBigtable テーブルを共有
– 個々のエンティティは、キーで識別• キーの辞書順でソートされている
– 個々のエンティティのプロパティ内容は、すべて 1つのカラムにシリアライズされて格納
• Bigtable の履歴機能は使用していない
キー• キー
– アプリ 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
キー/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
注:この図は概念図です(実際の実装ではありません)
プロパティ• Datastore のプロパティの特徴
– variable properties • エンティティごとにプロパティの数や種類を変え
られる• 「プロパティがない」と「プロパティが null 」は区別される
– heterogenous property types • エンティティごとにプロパティの型を変えること
ができる • ( GAE/J でこれを使えるかは不明)
プロパティ• Datastore のプロパティの特徴
– multi-value properties (List Property) • 1 つのプロパティに List や tuple を保存できる
– シリアライズして保存される
• クエリの例: name == 'Foo' – List 内のいずれか 1 つの値が Foo なら true になる
• 非正規化や設計の最適化に活用できる– 1:N 関連の代わりに使う(非正規化)– ジョインテーブルの代わりに使う
– Serializable オブジェクトを格納可能
クエリ• 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");
クエリ• クエリは「インデックス+スキャン」で実装 – Bigtable はクエリをサポートしていない
• 「値」に基づく検索を実行できない
– GAE のアプリが実行するすべてのクエリは、インデックスとスキャンの組み合わせで実装される
– クエリを記述すると自動的にインデックスを作成
• 明示的な作成も可能
クエリ• クエリの流れ
– 検索/ソート条件に合うインデックスを探す
– インデックスをスキャンし、検索条件に合うエンティティを取得
– インデックスの終端か 1000 件までスキャン
クエリ• クエリの多用は避けよう
– クエリを多用/誤用するとインデックスが増え、更新処理が遅くなる
• インデックス爆発( index explosion)– RDB とは違い制約も多い(後述)
インデックス• Datastore のインデックスとは
– エンティティテーブルとは別のテーブル– 3 種類のインデックス
• カインドインデックス • シングルプロパティインデックス • コンポジットインデックス
カインドインデックス
• カインド名順でソートされたインデックス – あるクラスのすべてのエンティティの一覧を
提供 – 例: Grandparent でカインドインデックスを
スキャン
SELECT * FROM Grandparent
シングルプロパティインデックス
• 「エンティティの特定のプロパティ値」の順番でソートされたインデックス
• 特定のプロパティを条件に検索/ソートできる– 例: name == 'John' – 例: ORDER BY name – 例: age > 10 AND age <= 20
シングルプロパティインデックス
• 昇順用と降順用の 2つが作成される– 例: name プロパティの範囲指定+ name プロ
パティでソート – name プロパティのインデックスで range スキャン
WHERE name >= 'B' AND name < 'C' ORDER BY name
コンポジットインデックス
• 「複数のプロパティ値の組み合わせ」でソートされたインデックス– index.yaml で明示的に指定して作成
• 複数のプロパティ値で検索できる– 例: lastname = 'Smith' AND firstname >= 'B' AND first
name < 'C' – インデックスで「 Parent/Smith/B 」から「 Parent/Smi
th/C 」まで range スキャン– inequality filter ( < <= > >= )はひとつのプロパティ
に対してのみ利用できる
コンポジットインデックス• コンポジットインデックス
コンポジットインデックス
• コンポジットインデックスは使うべきか? – すべてのプロパティ値の順列組み合わせでイン
デックス内容が作成されるので、インデックスサイズが膨大になりやすい
マージジョイン
• マージジョイン( merge join )とは – コンポジットインデックスに頼らずに複数プ
ロパティの条件検索を実現–複数のシングルプロパティインデックスをマージしながら検索
– "zig-zag" アルゴリズムにより、個々のインデックスを並行してスキャン
エンティティグループとトランザクション
CAP と BASE
• CAP定理– Consistency, Availability, Partition
• クラウド( P)では C と A 間のトレードオフが発生する• ACID 保証の範囲を制限しないとスケーラビリティが頭打ちに
• BASE トランザクション– CAP定理を反映した、クラウドでのトランザクション– Basically Available
• 可用性の高さを優先する(悲観排他より楽観排他)– Soft-State and Eventually Consistent
• 状態の一時的な不整合を許容する• 結果的に整合性が確保される仕組みにしておく• 例: DNS 、 Google Wave の OT
エンティティグループとは
• エンティティグループとは– エンティティの階層構造
• 親( parent)と子( children)
• エンティティグループのルートエンティティ( root entity)
– デフォルトでは• 個々のエンティティは個別
のエンティティグループを形成
Dept
Emp
1
*
エンティティグループとは• エンティティグループの指定方法
– JDO の owned 関係• User と Address 間で親子関係を定義• unowned 関係はサポートしていない
– エンティティグループが個別になるので ACID を保証できないため
– 明示的な指定• 子のキーを、親のエンティティのキーを使って生成する
– 詳しい手順: http://d.hatena.ne.jp/uehaj/20090509/1241856856
– OOP や RDB の「関連(リレーション)」ではない
• 関連をそのまま当てはめると問題も(後述)
エンティティグループとは
• エンティティグループの 2つの役割– ローカリティを指定する
• パフォーマンスの向上– トランザクション・スコープを指定
• ACID の保証– CAP定理とエンティティグループ
• 特定範囲(ローカリティ)のみを対象に ACID 保証
001 abc002 def
ACID
ローカリティ• ローカリティ
– エンティティグループのすべてのエンティティは、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
トランザクション・スコープ
• Datastore のトランザクション・スコープ– トランザクションの開始( begin )と終了( com
mit/rollback )を指示する– エンティティ・グループ
• エンティティグループ単位で ACID を保証 – Bigtable は行単位の ACID しか保証しない– Datastore ではエンティティグループ単位での A
CID を保証している• SERIALIZABLE相当
– 異なるエンティティグループ間では保証されない
Datastore と BASE
• 楽観的排他制御( optimistic lock)を実装 – エンティティグループのルートエンティティにて、
トランザクションの最終コミット時間のタイムスタンプを記録
– トランザクションの開始時に同タイムスタンプを確認
– コミット時にタイムスタンプを再度確認する • タイムスタンプが変化していなければ、更新内容を保存し
て、タイムスタンプを更新• タイムスタンプが変化してれば、他のトランザクションと
の競合が発生しているので、トランザクションをロールバック
Datastore と BASE
• 楽観的排他制御( optimistic lock)を実装 – RDB の悲観的排他制御( SELECT FOR
UPDATE)のようにエンティティをロックしない
– スループットは高いが、競合時のリトライが必要• Python 版は 3回まで自動リトライし、 Java 版は自動
リトライしない
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; } } }
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
GAE トランザクションの注意点• 1 TX = 1 エンティティグループ
– 1つのトランザクション内では、 1つのエンティティグループの更新処理しか実行できない
– 複数のエンティティグループを更新する場合は、個別のトランザクションが必要
• =ルートエンティティの更新は個別 TX が必要• @Transactional を使うと便利
– http://d.hatena.ne.jp/kazunori_279/20090711/1247300915
• 1つのエンティティの更新は 1回まで– 1つのトランザクション内では、 1つのエンティティ
を複数回更新できない
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
エンティティグループの注意点
• 1つのエンティティグループにトランザクション負荷を集中させない– エンティティグループの利用は必
要最小限に抑えた方が性能は向上する
– リレーショナルモデルやオブジェクト指向の関連をそのままあてはめると問題が生じることも
Dept
Emp
1
*
エンティティグループの注意点
• 例: 1つの Department と 1000 の Employee– 1000 の Employee のうち、いずれか 1つの
Employee しか同時にトランザクションを実行できない
–他はエラーとなりリトライが必要• 例: 1つの User と数個の Address
– 1 人のユーザーの住所に対して複数のトランザクションが同時実行される頻度は低い
– 負荷は集中しない
エンティティグループの注意点
• 連番の採番はどうする? – 例:メッセージの 1 つ 1 つに、書き込み順の連番を振る
– 単純な方法:採番用エンティティの MessageIndex をルートエンティティとし、 Message を子とする
• MessageIndex と Message が同じトランザクションで更新処理され、 ACID を確保できる
• しかし大量のメッセージ書き込みには対応できない
エンティティグループの注意点
• 連番の採番はどうする? –対処方法:採番用エンティティを分散化する
• ユーザーごとの採番用エンティティ UserIndex をルートエンティティとし、 Message を子とする
• Message の ID には「タイムスタンプ+ユーザー ID+UserIndex で採番した値」を設定する
• UserIndex 単位のトランザクションとなり、大量の書き込みが可能となる
• タイムスタンプ順でソート可能で、かつユニークな ID となる
エンティティグループの注意点
• 連番の採番はどうする? –別の対処方法: GUID ベースとする
• 「タイムスタンプ+長い乱数」など –参照: Google I/O 2008 - Building Scalable Web
Apps with App Engine
Datastore ってどう使う?
Datastore のパフォーマンス
• Datastore のパフォーマンスは、エンティティの数とは無関係 – 保存されているエンティティが 1件でも、 1000件でも、
1000万件でも、パフォーマンスに変化はない
• 個々のエンティティに対する更新処理のスピード– 平均 100ms程度
• http://code.google.com/status/appengine/– 個々のエンティティの更新処理は遅い – アプリケーションのパフォーマンスを決めるのは、更新
処理の実装方法。参照処理は桁違いに速い • 平均数 10ms程度
Datastore のパフォーマンス
• 対応策 – エンティティグループを分散させる
• エンティティグループが個別ならば、同時に何千でも並列処理できる
– memcache や Task Queue を活用する• memcache は高速
– 参照・更新ともに 5ms 前後
• リクエスト処理では Task Queueへの登録だけ行い、処理結果は後で表示する
• リクエスト処理では memcache に書き込み、 Task Queue のバックグラウンド処理でエンティティに保存する
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 を使う
Datastore にできないこと
• 集約関数がない( group by できない) – count() で全件カウントできない
• 毎回対象データをすべて取得してループで集計するのは非効率(また最大 1000件の制限がある)
• 集約したい値は、集約用のエンティティを用意して集計
– sharding counter: 書き込みが集中しないように複数のエンティティに分散して書き込みし、後で集計
– memcache counter: memcache に書き込みし、 Task Queueでエンティティに保存
Datastore にできないこと
• 集約関数がない( group by できない) – max()/min() で最大値/最小値を得られない
• 対象プロパティで降順/昇順でソートして、 1件目の値を得る
Datastore にできないこと
• 関数やストアドプロシージャはない – toUpper/toLower などがない – 別のプロパティを設け、 toUpper済みの値を入
れる –書き込み時にできるだけ事前処理を行っておくことで、読み込みを高速化できる
Datastore にできないこと
• クエリの構文の制約 – 全文検索ができない
• LIKE による部分一致検索はできない • 前方一致なら可能: name >= 'a' AND name <= 'a<
UTF-8 コードポイントの最大値> ' • 検索対象の文字列を形態素解析し、ワードごとのイン
デックスを作成する
Datastore にできないこと
• そのほかの制約 – OR 、 != が使えない– inequality filters ( < <= >= > )は 1 つのプロパティに
のみ利用可能– Text型や Blob型のプロパティはインデックスを作成
できない(クエリできない)– あるプロパティで inequality filters を使うと、他のプロ
パティを最優先にしたソートができない
• 対策– コーディングで対処– ド・モルガンの法則で書き換える
そのほかの tips
• インデックスの更新 – エンティティの更新処理ではインデックスも更新
• インデックスは最小限に抑えないと性能が落ちる
– GAE ではクエリの利用を最小限に抑えた方がよい
• 「クエリは使ったら負け」 byひがさん
そのほかの tips
• インデックス更新の回避方法 –頻繁に変更されるデータに対し、範囲指定検索す
るにはどうすればよいか?• 変更のたびにたくさんの index を更新したくない
–検索用のデータを適宜計算して持たせる
そのほかの tips
• オークションサイトの例–価格帯ごとのオークション一覧を表示したい– あるオークションの価格が変化したら
• 「 0~ 1000円のオークション」のフラグをそのオークションの LP に追加しておく
• 範囲指定検索が不要になり、 LP に対する equality filterですばやく検索できる
RDB から GAEへの移行
• プライマリーキーの扱い – 単一カラムの場合はそのまま使える –複合キーの場合は entity の親子関係に置き換える – N:N 用のマッピングデーブル(ジョインテーブ
ル)は List Property に置き換える
RDB から GAEへの移行
• トランザクションの扱い – データモデルからエンティティのルートエンティ
ティを見つける • オンラインサービスでは「ユーザー」がルートに最適
な場合が多い –複数のエンティティグループに対するトランザク
ションが必要な場合 • システムの仕様を見直す • 補償トランザクションを実装する
– 2つめのトランザクションが失敗した場合は、最初のトランザクションをキャンセルする、など