2013-02-14 5 views
6

私たちは現在、「外部ID」(facebook idなど)を分析に役立つすべてのサービスに固有の「内部(uu)ID」にマッピングする、小型でシンプルな中央HTTPサービスを構築しています。GAE/NDBからどのような応答時間が期待できますか?

"私たちのスタック"(フラスコ+ postgresql)の最初のプロトタイプは1日以内に完了しました。しかし、サービスが(ほぼ)決して失敗することはなく、自動的に拡大することを望んでいるので、Google App Engineを使用することに決めました。この質問をベンチマーク&をしようと&を読み取る週間後

が出てくる:(NDB付き)のApp Engine上で「ノーマル」と考えられている何の応答時間

我々はよく90percentile内の1以上の平均とに一貫上記の500msである応答時間を得ています。

私は、誰かが明白な欠陥を指摘できることを願って、下記のコードを削除しました。私たちは本当にオートスケーリングと分散ストレージが好きですが、私たちのケースでは500msが実際に期待される性能であるとは想像できません。 SQLベースのプロトタイプは、高速で(一貫して)、無料のキャッシュレスpostgresql(ORMの場合でも)を使用して、1つのHeroku dynoでホストされました。

以下のコードの同期型と非同期型の両方を試し、appstatsプロファイルを見ました。非常に長い(50ms〜100ms)RPC呼び出し(常にmemcacheとdatastoreの両方)は、複数の呼び出し(たとえばmc.get()+ ds.get()+ ds.set )を書く)。また、タスクキューにできるだけ多くの遅延をかけずに、試行待ちにしました。私の経験では、完全性期すため

import json 
import uuid 

from google.appengine.ext import ndb 

import webapp2 
from webapp2_extras.routes import RedirectRoute 


def _parse_request(request): 
    if request.content_type == 'application/json': 
     try: 
      body_json = json.loads(request.body) 
      provider_name = body_json.get('provider_name', None) 
      provider_user_id = body_json.get('provider_user_id', None) 
     except ValueError: 
      return webapp2.abort(400, detail='invalid json') 
    else: 
     provider_name = request.params.get('provider_name', None) 
     provider_user_id = request.params.get('provider_user_id', None) 

    return provider_name, provider_user_id 


class Provider(ndb.Model): 
    name = ndb.StringProperty(required=True) 


class Identity(ndb.Model): 
    user = ndb.KeyProperty(kind='GlobalUser') 


class GlobalUser(ndb.Model): 
    uuid = ndb.StringProperty(required=True) 

    @property 
    def identities(self): 
     return Identity.query(Identity.user==self.key).fetch() 


class ResolveHandler(webapp2.RequestHandler): 
    @ndb.toplevel 
    def post(self): 
     provider_name, provider_user_id = _parse_request(self.request) 

     if not provider_name or not provider_user_id: 
      return self.abort(400, detail='missing provider_name and/or provider_user_id') 

     identity = ndb.Key(Provider, provider_name, Identity, provider_user_id).get() 

     if identity: 
      user_uuid = identity.user.id() 
     else: 
      user_uuid = uuid.uuid4().hex 

      GlobalUser(
       id=user_uuid, 
       uuid=user_uuid 
      ).put_async() 

      Identity(
       parent=ndb.Key(Provider, provider_name), 
       id=provider_user_id, 
       user=ndb.Key(GlobalUser, user_uuid) 
      ).put_async() 

     return webapp2.Response(
      status='200 OK', 
      content_type='application/json', 
      body = json.dumps({ 
       'provider_name' : provider_name, 
       'provider_user_id' : provider_user_id, 
       'uuid' : user_uuid 
      }) 
     ) 

app = webapp2.WSGIApplication([ 
     RedirectRoute('/v1/resolve', ResolveHandler, 'resolve', strict_slash=True) 
], debug=False) 

(ほぼデフォルト)app.yamlを

application: GAE_APP_IDENTIFIER 
version: 1 
runtime: python27 
api_version: 1 
threadsafe: yes 

handlers: 
- url: .* 
    script: main.app 

libraries: 
- name: webapp2 
    version: 2.5.2 
- name: webob 
    version: 1.2.3 

inbound_services: 
- warmup 

答えて

3

は、RPCのパフォーマンスは、データストアの取得のための5ms-100msの間、桁違いによって変動します。私はそれがGAEのデータセンターの負荷に関係していると考えています。時にはそれは良くなる、時々悪化する。

あなたの操作は非常に簡単です。私は3つの要求で、それは約20msかかるはずですが、最大300msになる可能性があります。しかし500msの持続平均は非常に高く聞こえる。

ndbは、IDでオブジェクトをフェッチするときにローカルキャッシュを行います。同じユーザーにアクセスしている場合は、そのような要求がはるかに速くなければなりません。

dev_appserverではなく、本番環境でperfテストを行っていると仮定します。 dev_appserverのパフォーマンスは代表的なものではありません。

テストした回数は不明ですが、実際には500msが平均であるかどうかを確認するために大きな数値を試してみるとよいでしょう。

単純なRPC呼び出しでブロックされた場合、あまり最適化できません。

+0

Yepp、あなたはdev_appserverのパフォーマンス(ssdのsqlite ...)について正しいので、私たちは実稼働環境(payed account even)でテストします。反復については、通常、約5分間テストを実行します。私たちはまた、各実行に匹敵する量のヒット/ミスがあることを確認しようとします(実行間のデータストア/ memcacheを空にするか、 'provider_user_id'の範囲内で再生することによって)。 – selkie

+2

注:大きなベンチマークを実行している場合は、トラフィックを徐々に(例えば5〜10分)スピンアップし、しばらく(5〜10分)持続させて現実的な効果を測定する必要があります。 App Engineは、負荷が0から100になるとすぐに必要なインスタンスをスピンアップしません。不安定さを避けるためにこのプロセスに「ガバナ」があります。 –

+0

私は、HRDの "1エンティティグループあたり1回の書き込み"動作について読みました。上のコードでは、彼が私たちの問題を説明しないでしょうか?少数のプロバイダ(主にFacebook)があり、_Identity_は_Provider_を親として持っていて、それを_entity group_にしていますか? – selkie

1

私が目にした最初の瞬間:毎回のリクエストでトランザクションが本当に必要ですか?

あなたのリクエストのほとんどが新しいエンティティを作成しない限り、トランザクション外で.get_by_id()を実行する方が良いと思います。エンティティが見つからない場合は、トランザクションを開始するか、エンティティの作成を遅らせることをお勧めします。

def request_handler(key, data): 
    entity = key.get() 
    if entity: 
    return 'ok' 
    else: 
    defer(_deferred_create, key, data) 
    return 'ok' 

def _deferred_create(key, data): 
    @ndb.transactional 
    def _tx(): 
    entity = key.get() 
    if not entity: 
     entity = CreateEntity(data) 
     entity.put() 
    _tx() 

これは、ユーザーが直面する要求に対してはるかに優れた応答時間を提供するはずです。

ndb.put_multi()を使用してRPC呼び出しを最小限に抑えることが2番目の唯一の最適化です。

P.S. 100%確実ではありませんが、より安定した応答時間を得るためにマルチスレッド(threadsave:no)を無効にしようとすることができます。

関連する問題