【18-c-4】google app engine - 無限の彼方へ

40
Takashi Matsuo Google, Inc. Feb 18, 2011

Upload: developers-summit

Post on 25-May-2015

8.320 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: 【18-C-4】Google App Engine - 無限の彼方へ

Takashi MatsuoGoogle, Inc.Feb 18, 2011

Page 2: 【18-C-4】Google App Engine - 無限の彼方へ

App Engineto infinity and beyond

Takashi MatsuoGoogle, Inc.Feb 18, 2011

Page 3: 【18-C-4】Google App Engine - 無限の彼方へ

自己紹介

松尾貴史 @tmatsuo

App Engine Developer AdvocateKay's Daddyhttp://code.google.com/p/kay-framework/

Page 4: 【18-C-4】Google App Engine - 無限の彼方へ

> 10万 Developers / Month

> 15万 Apps / Week

> 10億 Page Views / Day

Page 5: 【18-C-4】Google App Engine - 無限の彼方へ

Apr 2008 Python launchMay 2008 Memcache API, Images APIJul 2008 Logs exportAug 2008 Batch write/deleteOct 2008 HTTPS supportDec 2008 Status dashboard, quota detailsFeb 2009 Billing, Remote API, Larger HTTP request/response size limits (10MB)Apr 2009 Java launch, Bulkloader (DB import), Cron jobs, SDCMay 2009 Key-only queries, Quota APIJun 2009 Task queue API, Django 1.0 supportSep 2009 XMPP API, Remote API shell, Django 1.1 supportOct 2009 Incoming emailDec 2009 Blobstore APIFeb 2010 Datastore cursors, Async URLfetch, App statsMar 2010 Denial-of-Service filtering, eventual consistency supportMay 2010 OpenID, OAuth, App Engine for Business, new bulkloaderAug 2010 Namespaces, increased quotas, high perf image servingOct 2010 Instances console, datastore admin & bulk entity deletesDec 2010 Channel API, 10-minute tasks & cron jobs, AlwaysOn & WarmupJan 2011 High Replication datastore, entity copy b/w apps, 10-minute URLfetchFeb 2011 Improved XMPP and Task Queue, Django 1.2 support

App Engine のこれまで 3 年 - 進化を続けるプラットフォーム

Page 6: 【18-C-4】Google App Engine - 無限の彼方へ

ロードマップ

SSL access on non-appspot.com domainsFull-text Search over DatastoreSupport for Python 2.7Background servers capable of running for longer than 30sSupport for running MapReduce jobs across App Engine datasetsBulk Datastore Import and Export toolImproved monitoring and alerting of application servingLogging system improvements to remove limits on size and storageRaise HTTP request and response size limitsIntegration with Google Storage for DevelopersProgrammatic Blob creation in BlobstoreQuota and presence improvements for Channel API

Page 7: 【18-C-4】Google App Engine - 無限の彼方へ

日本での事例

たくさんのミクシィアプリthe Actress - 60万ユーザーMixi Xmas 2010 - 200万ユーザー

朝日新聞 - メディア配信サービスソニー - Chan-Toru業務用アプリケーションも多数

Page 8: 【18-C-4】Google App Engine - 無限の彼方へ

App Engine for Business とは?

App Engine プラットフォームに加え

99.9% SLAクラウド SQL有償サポートドメインコンソールHosted SSL

Page 9: 【18-C-4】Google App Engine - 無限の彼方へ

本あります

プログラミング Google App Enginehttp://www.oreilly.co.jp/books/9784873114750/

オープンソース徹底活用 Slim3 on Google App Engine for JavaISBN-10: 4798026999

Page 10: 【18-C-4】Google App Engine - 無限の彼方へ

アジェンダ

App Engine で開発する際の注意点

Datastore の設計最小限の仕事をするDatastore Contention を避ける

シャーディングカウンターFork-join queue

Memcache を効果的に使用するさらなる高みへ

Page 11: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計

非正規化 を嫌がらない - No join必要な index のみ作成最小限の Entity Group

トランザクションが必要な箇所のみ可能なら速い方を使う

query より keys_only query が速いquery より get が速い複数の get より batch get が速い

kind の分割が有効なケース一部のプロパティだけ取得

少しの失敗を受け入れるリトライ、deadline の指定

Page 12: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計

非正規化 を嫌がらない - No join必要な index のみ作成最小限の Entity Group

トランザクションが必要な箇所のみ可能なら速い方を使う

query より keys_only query が速いquery より get が速い複数の get より batch get が速い

kind の分割が有効なケース一部のプロパティだけ取得

少しの失敗を受け入れるリトライ、deadline の指定

Page 13: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計 - 非正規化

from google.appengine.ext import db

class User(db.Model): name = db.StringProperty() groups = db.ListProperty(db.Key)

class Group(db.Model): name = db.StringProperty()

user = User.get(user_key)group_names = [group.name for group in db.get(user.groups)]

Before

Page 14: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計 - 非正規化

from google.appengine.ext import db

class User(db.Model): name = db.StringProperty() groups = db.ListProperty(db.Key) group_names = db.StringListProperty()

class Group(db.Model): name = db.StringProperty()

user = User.get(user_key)# group_names = user.group_names

After

Page 15: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計

非正規化 を嫌がらない - No join必要な index のみ作成最小限の Entity Group

トランザクションが必要な箇所のみ可能なら速い方を使う

query より keys_only query が速いquery より get が速い複数の get より batch get が速い

kind の分割が有効なケース一部のプロパティだけ取得

少しの失敗を受け入れるリトライ、deadline の指定

Page 16: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計 - 必要な index のみ作成

from google.appengine.ext import db

class MyModel(db.Model): name = db.StringProperty() total = db.IntegerProperty(indexed=False)

index が増えると書き込み速度は遅くなります

Page 17: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計

非正規化 を嫌がらない - No join必要な index のみ作成最小限の Entity Group

トランザクションが必要な箇所のみ可能なら速い方を使う

query より keys_only query が速いquery より get が速い複数の get より batch get が速い

kind の分割が有効なケース一部のプロパティだけ取得

少しの失敗を受け入れるリトライ、deadline の指定

Page 18: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計 - Entity Group

class MyModel(db.Model): # ...

# 単純にエンティティを作成すると、それ自体新しい Entity Group になる

my_entity = MyModel()my_entity.put()

# 親を指定すると、親と同じ Entity Group に属する

my_second_entity = MyModel(parent=my_entity)my_second_entity.put()

# 同じ kind である必要はない

my_third_entity = MyOtherModel(parent=my_second_entity)my_third_entity.put()

Entity Group 作成の方法

Page 19: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計 - Entity Group

class MyModel(db.Model): # ...

# 単純にエンティティを作成すると、それ自体新しい Entity Group になる

my_entity = MyModel()my_entity.put()

# 親を指定すると、親と同じ Entity Group に属する

my_second_entity = MyModel(parent=my_entity)my_second_entity.put()

# 同じ kind である必要はない

my_third_entity = MyOtherModel(parent=my_second_entity)my_third_entity.put()

Entity Group 作成の方法

Page 20: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計 - Entity Group

親子関係全てに Entity Group を使用するべきではない例えば BlogEntry と Comment には不適切

基本的にはトランザクションが必要な箇所に使う検索用インデックスを自前で作る時などにも有効

Page 21: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計 - Entity Group の使用例

検索用インデックス - Before

class Sentence(db.Model): body = db.TextProperty() indexes = db.StringListProperty()

query = Sentence.all().filter( "indexes =", search_word)search_result = query.fetch(20)

fetch 時に不必要な indexes のデシリアライズが発生

Page 22: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計 - Entity Group の使用例

検索用インデックス - After

class Sentence(db.Model): body = db.TextProperty()

class SearchIndex(db.Model): indexes = db.StringListProperty()

query = SearchIndex.all(keys_only=True).\ filter("indexes =", search_word)search_result = db.get( [key.parent() for key in query.fetch(20)])

保存時に Entity Group を形成

Page 23: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計

非正規化 を嫌がらない - No join必要な index のみ作成最小限の Entity Group

トランザクションが必要な箇所のみ可能なら速い方を使う

query より keys_only query が速いquery より get が速い複数の get より batch get が速い

kind の分割が有効なケース一部のプロパティだけ取得

少しの失敗を受け入れるリトライ、deadline の指定

Page 24: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計

非正規化 を嫌がらない - No join必要な index のみ作成最小限の Entity Group

トランザクションが必要な箇所のみ可能なら速い方を使う

query より keys_only query が速いquery より get が速い複数の get より batch get が速い

kind の分割が有効なケース一部のプロパティだけ取得

少しの失敗を受け入れるリトライ、deadline の指定

Page 25: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計 - Entity 分割

例: ファイルシェアリングサービスファイル自身メタデータ

ファイルの一覧ページで必要なのはメタデータだけファイル自身とメタデータを別エンティティに分割一覧ページでの処理が高速で無駄がなくなる

Page 26: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計

非正規化 を嫌がらない - No join必要な index のみ作成最小限の Entity Group

トランザクションが必要な箇所のみ可能なら速い方を使う

query より keys_only query が速いquery より get が速い複数の get より batch get が速い

kind の分割が有効なケース一部のプロパティだけ取得

少しの失敗を受け入れるリトライ、deadline の指定

Page 27: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore の設計 - リトライ、deadline

from google.appengine.ext import dbfrom google.appengine.runtime.apiproxy_errors import DeadlineExceededErrorfrom google.appengine.runtime import DeadlineExceededError as D

def somefunction(somevalue): handmaid_key = db.Key.from_path("MyModel", 1) new_id = db.allocate_ids(handmaid_key, 1) new_key = db.Key.from_path("MyModel", new_id) my_entity = MyModel(key=new_key, name=somevalue) try: while True: sleep_ms = 100 try: db.put(my_entity, config=db.create_config(deadline=5)) break except (DeadlineExceededError, db.Timeout): time.sleep(sleep_ms) sleep_ms *= 2 except D: # Ran out of retry. Return an error to users.

自動リトライのレシピ

Page 28: 【18-C-4】Google App Engine - 無限の彼方へ

最小限の仕事をする

大事な用語ユーザー向けリクエスト(User Facing Request)

インターネット側から来るもの全てバックグラウンドリクエスト(Background Request)

TaskQueue, Cron 等

大事なポイントユーザー向けリクエストは 1000 ms 以内で返すこと

Page 29: 【18-C-4】Google App Engine - 無限の彼方へ

最小限の仕事をする - より速い方を使用

query より keys_only queryquery より getdatastore より memcachememcache より global 変数(static 変数)

Page 30: 【18-C-4】Google App Engine - 無限の彼方へ

最小限の仕事をする - Task Queue を使用

時間のかかる処理は Task Queue に投げる。Channel API を使えば結果を簡単にプッシュ

Page 31: 【18-C-4】Google App Engine - 無限の彼方へ

Datastore Contention をさける

Entity または Entity Group に対しての書き込み

目安:  1 秒に 1 度程度対策:

Entity Group はなるべく小さくするシャーディングカウンターなどのテクニックを使用する

Page 32: 【18-C-4】Google App Engine - 無限の彼方へ

シャーディングカウンター - シンプルな実装例

from google.appengine.ext import dbimport random

class SimpleCounterShard(db.Model): """Shards for the counter""" count = db.IntegerProperty(required=True, default=0)

NUM_SHARDS = 20

def get_count(): """Retrieve the value for a given sharded counter.""" total = 0 for counter in SimpleCounterShard.all(): total += counter.count return total

def increment(): """Increment the value for a given sharded counter.""" def txn(): index = random.randint(0, NUM_SHARDS - 1) shard_name = "shard" + str(index) counter = SimpleCounterShard.get_by_key_name(shard_name) if counter is None: counter = SimpleCounterShard(key_name=shard_name) counter.count += 1 counter.put() db.run_in_transaction(txn)

http://goo.gl/8dGO

Page 33: 【18-C-4】Google App Engine - 無限の彼方へ

Fork-join queue

Building high-throughput data pipelines with Google App Engine

http://goo.gl/ntlH

シンプルなカウンターの例http://paste.shehas.net/show/137/

Page 34: 【18-C-4】Google App Engine - 無限の彼方へ

memcache を効果的に使用する

使用例人気のあるページを html 丸ごとキャッシュmemcache.incr を使用したカウンター

高速だが 100% 正確とは保証できない頻繁に使用される entity や query 結果をキャッシュ

Page 35: 【18-C-4】Google App Engine - 無限の彼方へ

memcache を効果的に使用する

基本パターン

results = memcache.get('results')if results is None: results = some_complex_work() memcache.set('results', results)

# use results

古いキャッシュを破棄する

Page 36: 【18-C-4】Google App Engine - 無限の彼方へ

AppStats - パフォーマンス測定用ツール

http://goo.gl/eYdhD

Before

After

Page 37: 【18-C-4】Google App Engine - 無限の彼方へ

さらなる高みへ

Request 数の Fixed Quota はデフォルト 500 QPS 程度それ以上必要な場合は、Quota Increase のフォームから申請するか私宛に相談してください

Tablet Server の Split を抑制頻繁に(数百/秒) Entity の作成などを行う場合

自動採番の id では Split を引き起こしやすいuuid などを key_name として使用する

key_name を使用する場合でも、頭文字が類似だと Split が起きやすい

Hash 値を前置する

Page 38: 【18-C-4】Google App Engine - 無限の彼方へ

まとめ

Datastore に適した設計最小限の仕事をするDatastore Contention を避けるMemcache を効果的に使用するAppStats を使用してパフォーマンス測定するFixed Quota を増やすTablet Server の Split を抑制

Page 39: 【18-C-4】Google App Engine - 無限の彼方へ

無限の彼方へ

Page 40: 【18-C-4】Google App Engine - 無限の彼方へ

Takashi MatsuoGoogle, Inc.e-mail: [email protected]: @tmatsuo