6

現在、「外部ID」(Facebook IDなど)を「内部(uu)ID」にマッピングする、小さくてシンプルな中央HTTPサービスを構築しています。これは、分析に役立つすべてのサービスで一意です。

「スタック」(flask + postgresql)の最初のプロトタイプは、1日以内に完了しました。ただし、サービスが(ほぼ)失敗せず、自動的にスケーリングされることを望んでいるため、GoogleAppEngineを使用することにしました。

1週間の読書、試行、ベンチマークの後に、この質問が浮かび上がります。

App Engine(NDBを使用)で「正常」と見なされる応答時間はどれくらいですか?

応答時間は、平均で一貫して500ミリ秒を超え、90パーセントで1秒をはるかに上回っています。

誰かが明らかな欠陥を指摘できることを期待して、以下にコードの簡略版を添付しました。自動スケーリングと分散ストレージは本当に気に入っていますが、私たちの場合、500msが実際に期待されるパフォーマンスであるとは想像できません。SQLベースのプロトタイプは、(ORMを使用している場合でも)無料のキャッシュレスpostgresqlを使用して、単一のHeroku dynoでホストされ、はるかに高速に(一貫して)応答しました。

以下のコードの同期バリアントと非同期バリアントの両方を試し、appstatsプロファイルを確認しました。非常に長い時間(50ms〜100ms)かかるのは常にRPC呼び出し(memcacheとデータストアの両方)であり、常に複数の呼び出しがあるという事実によってさらに悪化します(たとえば、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
4

2 に答える 2

3

In my experience, RPC performance fluctuates by orders of magnitude, between 5ms-100ms for a datastore get. I suspect it's related to the GAE datacenter load. Sometimes it gets better, sometimes it gets worse.

Your operation looks very simple. I expect that with 3 requests, it should take about 20ms, but it could be up to 300ms. A sustained average of 500ms sounds very high though.

ndb does local caching when fetching objects by ID. That should kick in if you're accessing the same users, and those requests should be much faster.

I assume you're doing perf testing on the production and not dev_appserver. dev_appserver performance is not representative.

Not sure how many iterations you've tested, but you might want to try a larger number to see if 500ms is really your average.

When you're blocked on simple RPC calls, there's not too optimizing you can do.

于 2013-02-14T16:15:56.153 に答える
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()

これにより、ユーザー向けのリクエストに対する応答時間が大幅に短縮されます。

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

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

于 2013-02-15T22:14:07.193 に答える