More Related Content Similar to CBA Google App Engine 20101208 Similar to CBA Google App Engine 20101208 (20) More from Kazunori Sato (8) CBA Google App Engine 201012083. 自己紹介 スティルハウス 佐藤一憲 twitter: @kazunori_279 ブログ: 「スティルハウスの書庫」 Web: http://www.sth.co.jp/ コミュニティ活動 appengine ja night Google API Expert (App Engine) 主な業務 開発: Adobe Flex/AIR、Rails、GAE/J テクニカルライティングや翻訳(ペンネーム吉川和巳) セミナー講師など 5. Google App Engineとは Google App Engineとは Webアプリホスティングサービス 自分のアプリをGoogleのクラウド上で運用できる 2008年4月にPython版リリース 2009年4月にJava版リリース 9. 「2ケタ安」の低コスト 「ふにゃもらけ」の事例 MixiアプリをApp Engineで提供 1日600万PV以上(月1.8億PV相当) Googleからの請求は1日$15(月額4万相当) スティルハウス担当事例 従来はデータセンターのサーバーを1台使用 400万件のデータ(約11GB)をApp Engine移行 月額$4 サーバー管理者が不要に 10. どこまでもスケール+高可用性 Googleのクラスタ環境を簡単に利用できる 自動クラスタリングによる負荷分散と高可用性 負荷状況に応じてApp Serverを動的に増減 アプリ間の隔離性を維持、個々のアプリの安全性とパフォーマンスを確保 Bigtableのスケーラビリティ RDBにつきもののスケーラビリティ上限がない テーブル間の結合(join)ができない トランザクションの整合性保証の範囲を限定している App Engineの全アプリのデータは、1つのBigtableテーブルに格納されている 11. スケールアウト事例 ホワイトハウスの"Open For Questions" 2日間だけ提供された投票サイト その結果を受けてオバマ大統領が記者会見を行った 10万件の質問と360万件の投票を受け付けた ピーク時には毎秒700回のクエリを実行した App Engineをout of boxで使用 Google Moderatorのソースをベースに、ホワイトハウス側の開発者がチューンしてデプロイ。Google側による特別な作り込み等は行っていない App Engine上の他のアプリには一切影響なし 16. App Serverについて App Serverのサンドボックスによる制限 HTTPリクエスト処理は最大30秒まで App Engine最大の制約のひとつ 時間のかかる処理はTask Queueで ファイルシステムへの書き込み 外部サーバへのソケット接続 HTTPリクエスト送信は可能 スレッド生成 ガベージコレクション実行やシステム停止 カスタムクラスローダの利用 17. App Serverのスケールアウト 高負荷時の自動デプロイ 高負荷状態が一定時間続くと、新しいApp Serverが追加され負荷分散し、負荷が低くなると削除される アプリが受けられる負荷に上限はあるか? 同時30リクエストの制限 リクエスト処理時間に応じてスループット上限は変化 それ以上の負荷を扱いたい場合はGoogleに依頼する。アプリごとにsafety limitを解除できる 20. 分散KVSとは 各社のクラウド向け分散KVS Google Bigtable Amazon Web Service Amazon Dynamo マイクロソフトAzure Azure Storage Service 楽天「ROMA」 オープンソース実装 Facebookによる「Cassandra」など 24. RDBとCAP定理 CAP定理 UC Berkley大のEric Brewer教授が2000年に発表 Consistency, Availability, Partition 分散環境(P)では整合性(C)と可用性(A)間でトレードオフ発生 29. Bigtableの概要 Bigtableとは 巨大分散データストア リレーショナルモデルに基づくRDBではない いわゆる分散Key-value Store(KVS)やNoSQL 膨大な数の汎用サーバーをつなげてペタバイト規模のデータを扱えるよう設計 現在およそ数PB 全世界36か所以上のデータセンターに配置された数万台~数10万台のサーバーに分散 Googleクラウドの「虎の子」 32. サーバー冗長化による高可用性 Google独自の分散ファイルシステム「Google File System(GFS)」 異なるラックに設置された3台以上のサーバーにコピー サーバー障害によってデータが失われる可能性はきわめて低い いずれか1台のサーバーが停止しても他の2台のいずれかから同じデータを瞬時に取得 Bigtableのサービスを構成するサーバー群はすべてが冗長化 SPoF(Single Point of Failure)を排除 Oracle RACなどのハイエンドのRDBクラスタに匹敵する高可用性 34. Bigtableの構成要素 Bigtableのテーブル テーブルの実体は、巨大なkey-value store キー:行キー+カラムキー+タイムスタンプ 行キーは最大64KBまで(大半は10~100バイト) 行キーの辞書順でソートされている 行単位のCRUDはアトミックに実行 複数行にまたがる更新処理は原子性が保証されない 古い履歴データや削除された行は、自動的にGC 35. Bigtableにできること Bigtableにできること キーを指定した行単位のCRUD 行単位のACID特性が保証される 2種類のスキャン: prefix scan: キーの前方一致検索で複数行を一括取得 range scan: キーの範囲指定検索で複数行を一括取得 ソート済みなので高速に実行できる Bigtableではカラムの値に基づく検索は一切実行できない! 38. Bigtableの内部構造 個々のノードの基本構成 Intelベースの安いPC Linux OS Schedulerスレーブ Schedulerマスターの指示に従ってノード上に各種サービスをデプロイする GFSチャンクサーバー GFSのチャンク(データ)を保存する タブレットサーバー Bigtableのタブレットを管理する 40. Bigtableの内部構造 Bigtableクラスター 複数のBigtableテーブルからなるクラスター 2006年8月時点では、388のBigtableクラスターと24,500のタブレットサーバーが稼働中 タブレット Bigtableのテーブルを分割したもの テーブルの内容はタブレット単位で各タブレットサーバーに分散保存される 1つのタブレットは100~200MB程度のデータを保存。それ以上になると分割される 1台のタブレットサーバーは100以下のタブレットを保存 41. Bigtableの内部構造 タブレット 復旧が高速 1台がダウンしても、その100個のタブレットは他の100台のサーバーが保有している Bigtableマスターが負荷分散を管理 高負荷のサーバーからタブレットを移動 43. タブレットサーバーのメカニズム タブレットサーバーの検索 あるキーのデータを取得するとき クライアントはタブレットサーバーのIPアドレスを取得 DNSライクな階層問い合わせ 検索の流れ ブートストラップとなるChubbyサービスにアクセス METADATAタブレットを持つタブレットサーバーのIPアドレスを取得 METADATAタブレットには、各キーに対応するタブレットサーバーのIPが記録されている 50. GFSの利用 GFSによるディスクへの書き込み GFS(Google File System)とは、SSTable等のファイル保存に用いられるファイルシステム ファイルは必ず3台以上のサーバーに書き込み ローカルのGFSチャンクサーバーが空いていれば、そこに1つを書き込み 残り2つは、離れた場所(少なくとも同じラックではない場所)のGFSチャンクサーバーに書き込み 52. GFSの利用 GFSによるディスクへの書き込み タブレットが移動しない限り、タブレットサーバーはローカルのGFSチャンクサーバーにアクセス 負荷分散のためタブレットが移動すると、データは残したままタブレットのみ移動 バイナリアップグレード時などに、できるだけローカルに置くようにデータを再配置 55. Datastoreサービスとは JDO APIによるオブジェクト保存の例 PersistenceManager pm = PMF.get().getPersistenceManager(); Employee e = new Employee("Alfred", "Smith", new Date()); try { pm.makePersistent(e); } finally { pm.close(); } 57. エンティティテーブル エンティティテーブルとは エンティティを保存するテーブル App Engine内のすべてのエンティティテーブルが1つのBigtableテーブルに格納されている 個々のエンティティは、キーで識別 キーの辞書順でソートされている 個々のエンティティのプロパティ内容は、すべてBigtableの1つのカラムにシリアライズされて格納 Protocol Buffer形式で保存される 58. キー キー アプリID+パス(カインド名+IDまたはキー名) IDは自動採番 エンティティグループなし:カインド内でユニーク エンティティグループあり:エンティティグループ内でユニーク キー名はアプリ側で設定 ユニークにする必要がある エンティティのキーは変更できない 表記例 Foo(25) カインド名+ID(アプリIDは表示されない) agR0ZXN0cgkLEgNGb28YGQw protocol buffer+BASE64 59. プロパティ プロパティ variable properties エンティティごとにプロパティの数や種類を変えられる 「プロパティがない」と「プロパティがnull」は区別される heterogenous property types エンティティごとにプロパティの型を変えることができる (GAE/Jでこれを使えるかは不明) 60. プロパティの特徴 Datastoreのプロパティの特徴 multiple value properties (MVP) 1つのプロパティにListやtupleを保存できる シリアライズして保存される クエリの例:name == 'Foo' List内のいずれか1つの値がFooならtrueになる 非正規化や設計の最適化に活用できる 1:N関連の代わりに使う(非正規化) ジョインテーブルの代わりに使う Serializableオブジェクトを格納可能 61. Datastoreのパフォーマンス Datastoreの性能は、エンティティの数とは無関係 保存されているエンティティが1件でも、1000件でも、1000万件でも、パフォーマンスに変化はない エンティティへの読み書き速度 エンティティの読み込み:平均数10ms程度 エンティティの更新:平均100ms程度 個々のエンティティの更新処理は遅い アプリケーションのパフォーマンスを決めるのは、更新処理の実装方法。参照処理は桁違いに速い 平均数10ms程度 Datastoreパフォーマンスの監視ページ http://code.google.com/status/appengine/ 66. クエリ Datastoreのクエリとは 複数のエンティティを条件検索できる 通常、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"); 76. コンポジットインデックス コンポジットインデックスのデメリット すべてのプロパティ値の順列組み合わせでインデックス内容が作成されるので、インデックスサイズが膨大になりやすい クエリを多用/誤用するとインデックスが増え、更新処理が遅くなる multi-value property利用時のインデックス爆発(index explosion) 「ここぞ」という用途に限って使うべき できるだけコード上でのフィルタリングやソートがよい 77. マージジョイン マージジョイン(merge join)とは 複数プロパティの等号条件(equality filter)検索をコンポジットインデックスに頼らずに実現 例:dept_key = D1 & age = 40 & name = ‘佐藤’ 複数のシングルプロパティインデックスをマージしながら検索 "zig-zag"アルゴリズムにより、個々のインデックスを並行してスキャン 80. Entitiesテーブル キー App ID+パス(カインド名+IDまたはキー名) プロパティ プロパティ名+プロパティ値のペア Protocol Buffer形式 メタデータ ルートエンティティのキー、カインド名 カスタムインデックスデータ インデックスID、祖先エンティティのキー一覧、プロパティ値一覧 81. インデックステーブル EntitiesByKindテーブル カインドインデックスを保持 App ID、カインド名、エンティティキー EntitiesByPropertiy ASC/DESCテーブル シングルプロパティインデックスを保持 App ID、カインド名、プロパティ名、プロパティ値、エンティティキー EntitiesByCompositePropertyテーブル コンポジットインデックスを保持 インデックスID、App ID、カインド名、祖先エンティティのキー一覧、プロパティ値一覧、エンティティキー 83. クエリの制限 テーブル間の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” 複数のクエリに分割する multiple value propertyを使う 84. クエリの制限 集約関数がない(group byできない) count()で全件カウントできない 毎回対象データをすべて取得してループで集計するのは非効率 集約したい値は、集約用のエンティティを用意して集計 sharding counter: 書き込みが集中しないように複数のエンティティに分散して書き込みし、後で集計 memcache counter: memcacheに書き込みし、Task Queueでエンティティに保存 87. クエリの制限 クエリの構文の制約 全文検索ができない LIKEによる部分一致検索はできない 前方一致なら可能: name >= 'a' AND name <= 'a<UTF-8コードポイントの最大値>' 検索対象の文字列を形態素解析し、ワードごとのインデックスを作成する 2010年に全文検索対応予定? 88. クエリの制限 そのほかの制約 OR、!=が使えない 近日対応予定 inequality filters (< <= >= >)は1つのプロパティにのみ利用可能 Text型やBlob型のプロパティはインデックスを作成できない(クエリできない) あるプロパティでinequality filtersを使うと、他のプロパティを最優先にしたソートができない 92. エンティティグループとキー /Grandparent:123 /Grandparent:123/Parent:52 /Grandparent:287 /Grandparent:287/Parent:85 /Grandparent:287/Parent:88/Child:47 /Grandparent:287/Parent:88/Child:66 カインド名 ID 親のキー パス 93. エンティティグループとは エンティティグループの指定方法 明示的な指定 子のキーを、親のエンティティのキーを使って生成する 詳しい手順:http://d.hatena.ne.jp/uehaj/20090509/1241856856 JDOのowned関係 UserとAddress間で親子関係を定義 unowned関係はサポートしていない エンティティグループが個別になるのでACIDを保証できないため OOPやRDBの「関連(リレーション)」とは無関係 関連をそのまま当てはめると問題も(後述) 95. ローカリティ ローカリティ エンティティグループのすべてのエンティティは、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 97. DatastoreとBASE 楽観的排他制御(optimistic lock)を実装 エンティティグループのルートエンティティにて、トランザクションの最終コミット時間のタイムスタンプを記録 トランザクションの開始時に同タイムスタンプを確認 コミット時にタイムスタンプを再度確認する タイムスタンプが変化していなければ、更新内容を保存して、タイムスタンプを更新 タイムスタンプが変化してれば、他のトランザクションとの競合が発生しているので、トランザクションをロールバック 99. 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; } } } 100. DatastoreとBASE 分散トランザクションへの対応 App Engineでは異なるエンティティグループ間の分散トランザクション(グローバルトランザクション)はサポートされていない ただしアプリレベルでの実装例はある http://code.google.com/intl/ja/events/io/sessions/DesignDistributedTransactionLayerAppEngine.html http://code.google.com/intl/ja/events/io/sessions/TransactionsAcrossDatacenters.html 101. トランザクションの注意点 1 TX = 1 エンティティグループ 1つのトランザクション内では、1つのエンティティグループの更新処理しか実行できない 複数のエンティティグループを更新する場合は、個別のトランザクションが必要 =ルートエンティティの更新は個別TXが必要 1つのエンティティの更新は1回まで 1つのトランザクション内では、1つのエンティティを複数回更新できない 102. トランザクションの注意点 トランザクション内で実行可能なクエリの制限 ancestor filterを持つクエリのみ実行可能 コミット前の値は読み込みできない READ_COMMITTED相当 http://code.google.com/intl/ja/appengine/articles/transaction_isolation.html http://groups.google.com/group/google-appengine-java/browse_thread/thread/4a67044929428295