2013-02-14 7 views
6

Wir bauen gerade einen kleinen und einfachen zentralen HTTP-Service auf, der "externe Identitäten" (wie eine Facebook-ID) einer "internen (uu) ID" zuordnet, die in all unseren Services einzigartig ist und die Analyse unterstützt.Welche Antwortzeiten können von GAE/NDB erwartet werden?

Der erste Prototyp in "unserem Stapel" (Kolben + Postgresql) wurde innerhalb eines Tages erstellt. Da wir jedoch möchten, dass der Service (fast) nie fehlschlägt und automatisch skaliert, haben wir uns für Google App Engine entschieden.

Nach einer Woche & & versucht Lesen dieser Frage Benchmarking ergibt:

Welche Reaktionszeiten gelten als "normal" auf App Engine (mit NDB)?

Wir bekommen Reaktionszeiten, die konsequent über 500 ms im Durchschnitt und deutlich über 1 s im 90percentile sind.

Ich habe unten eine abgespeckte Version unseres Codes beigefügt, in der Hoffnung, dass jemand auf den offensichtlichen Fehler hinweisen kann. Wir mögen das Autoscaling und den verteilten Speicher wirklich, aber wir können uns nicht vorstellen, dass 500ms wirklich die erwartete Leistung in unserem Fall ist. Der sql-basierte Prototyp reagierte viel schneller (konsistent), gehostet auf einem einzelnen Heroku-Dyno mit dem kostenlosen, cache-losen Postgresql (sogar mit einem ORM).

Wir haben sowohl synchrone als auch asynchrone Varianten des folgenden Codes ausprobiert und das Profil von appstats betrachtet. Es sind immer RPC-Aufrufe (Memcache und Datenspeicher), die sehr lange dauern (50ms-100ms), verschlimmert durch die Tatsache, dass es immer mehrere Aufrufe gibt (zB. Mc.get() + ds.get() + ds.set () auf einem schreiben). Wir haben auch versucht, so viel wie möglich auf die Aufgabenwarteschlange zu verschieben, ohne merkliche Gewinne.

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) 

Der Vollständigkeit halber die (fast Standard) 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 

Antwort

3

Nach meiner Erfahrung schwankt RPC Leistung um Größenordnungen zwischen 5 ms-100 ms für einen Datenspeicher erhalten. Ich vermute, dass es mit der Auslastung des GAE-Rechenzentrums zusammenhängt. Manchmal wird es besser, manchmal wird es schlimmer.

Ihre Operation sieht sehr einfach aus. Ich erwarte, dass es mit 3 Anfragen ungefähr 20ms dauern würde, aber es könnte bis zu 300ms dauern. Ein anhaltender Durchschnitt von 500ms klingt jedoch sehr hoch.

Beim Speichern von Objekten nach ID führt ndb lokales Caching durch. Wenn Sie auf die gleichen Benutzer zugreifen, sollten Sie darauf zugreifen, und diese Anforderungen sollten viel schneller sein.

Ich nehme an, Sie machen Perf-Tests auf der Produktion und nicht dev_appserver. Die Leistung von dev_appserver ist nicht repräsentativ.

Nicht sicher, wie viele Iterationen Sie getestet haben, aber Sie möchten vielleicht eine größere Zahl versuchen, um zu sehen, ob 500ms wirklich Ihr Durchschnitt ist.

Wenn Sie bei einfachen RPC-Anrufen blockiert sind, können Sie nicht optimieren.

+0

Yepp, Sie haben recht über die Leistung von dev_appserver (sqlite auf ssd ...), so testen wir auf Produktion (bezahlte Konto sogar). Bei den Iterationen halten wir die Tests in der Regel für ca. 5 Minuten. Wir versuchen auch sicherzustellen, dass jeder Lauf eine vergleichbare Anzahl an Treffern/Fehlschlägen aufweist (durch Leeren des Datenspeichers/Memcache zwischen Läufen oder durch Herumspielen mit dem Bereich, in dem sich "provider_user_id" befindet). – selkie

+2

Ein Hinweis: Wenn Sie einen großen Benchmark betreiben, müssen Sie Ihren Traffic schrittweise erhöhen (sagen wir 5-10 Minuten) und ihn dann für eine Weile (weitere 5-10 Minuten) unterstützen, um realistische Effekte zu messen. App Engine startet die erforderlichen Instanzen nicht sofort, wenn die Auslastung von 0 auf 100 wechselt. Es gibt einen "Gouverneur" bei diesem Prozess, um Instabilitäten zu vermeiden. –

+0

Ich habe gerade gelesen über HRDs "ein Schreiben pro Sekunde pro Entitätsgruppe" Verhalten. Würde er im obigen Code nicht unsere Probleme erklären? Es gibt nur eine Handvoll Anbieter (hauptsächlich Facebook), und _Identity_ hat _Provider_ als Eltern, was sie zu einer _entity group_ macht? – selkie

1

Der erste offensichtliche Moment, den ich sehe: Brauchen Sie wirklich eine Transaktion auf jede Anfrage?

Ich glaube, dass, wenn die meisten Ihrer Anfragen neue Entitäten erstellen, es besser ist, .get_by_id() außerhalb der Transaktion zu tun.Und wenn die Entität nicht gefunden wurde, dann starte die Transaktion oder verschiebe die Erstellung der Entity sogar noch weiter.

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() 

Das sollte eine viel bessere Antwortzeit für Benutzer konfrontiert Anforderungen geben.

Die zweite und einzige Optimierung, die ich sehe, ist die Verwendung von ndb.put_multi(), um RPC-Aufrufe zu minimieren.

P.S. Nicht 100% sicher, aber Sie können versuchen, Multithreading (threadsave: no) zu deaktivieren, um eine stabilere Antwortzeit zu erhalten.

Verwandte Themen