2013-02-14 11 views
6

Attualmente stiamo costruendo un servizio HTTP centrale piccolo e semplice che mappa "identità esterne" (come un ID facebook) a un "interno (uu) id", unico in tutti i nostri servizi per aiutare con l'analisi.Quali tempi di risposta ci si può aspettare da GAE/NDB?

Il primo prototipo in "il nostro stack" (flask + postgresql) è stato eseguito in un giorno. Ma dal momento che vogliamo che il servizio (quasi) non fallisca mai e scala automaticamente, abbiamo deciso di utilizzare Google App Engine.

Dopo una settimana di lettura & provare & analisi comparativa questa domanda emerge:

Che tempi di risposta sono considerati "normali" su App Engine (con NDB)?

Stiamo ottenendo tempi di risposta che sono costantemente 500ms superiori in media e ben al di sopra 1s nel 90percentile.

Ho allegato una versione ridotta del nostro codice qui sotto, sperando che qualcuno possa evidenziare l'evidente difetto. Ci piace molto la scalabilità automatica e lo storage distribuito, ma non possiamo immaginare che i 500ms siano realmente le prestazioni previste nel nostro caso. Il prototipo basato su sql ha risposto molto più velocemente (coerentemente), ospitato su un singolo droke di Heroku usando il postgresql senza cache gratuito (anche con un ORM).

Abbiamo provato entrambe le varianti sincrone e asincrone del codice seguente e ho esaminato il profilo di appstats. Sono sempre chiamate RPC (sia memcache che datastore) che richiedono molto tempo (50ms-100ms), aggravate dal fatto che ci sono sempre più chiamate (ad esempio mc.get() + ds.get() + ds.set () su una scrittura). Abbiamo anche provato a rimandare il più possibile alla coda dei task, senza vantaggi evidenti.

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) 

Per completezza la (quasi default) 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 

risposta

3

Nella mia esperienza, le prestazioni RPC oscilla da ordini di grandezza, tra 5ms-100 ms per un get datastore. Sospetto che sia correlato al carico del data center GAE. A volte migliora, a volte peggiora.

L'operazione sembra molto semplice. Mi aspetto che con 3 richieste, occorra circa 20ms, ma potrebbe arrivare a 300ms. Tuttavia una media sostenuta di 500 ms suona molto bene.

ndb esegue il caching locale durante il recupero degli oggetti tramite ID. Questo dovrebbe intervenire se stai accedendo agli stessi utenti e quelle richieste dovrebbero essere molto più veloci.

Suppongo che tu stia eseguendo test di perf sulla produzione e non su dev_appserver. Le prestazioni di dev_appserver non sono rappresentative.

Non sono sicuro quante iterazioni sono state testate, ma si potrebbe desiderare di provare un numero maggiore per vedere se 500ms è davvero la tua media.

Quando si è bloccati su semplici chiamate RPC, non c'è troppo ottimizzazione che si possa fare.

+0

Yepp, hai ragione sulle prestazioni di dev_appserver (sqlite su ssd ...), quindi testiamo la produzione (anche l'account pagato). Per quanto riguarda le iterazioni, solitamente i test vengono eseguiti per circa 5 minuti. Cerchiamo anche di assicurarci che ogni esecuzione abbia una quantità comparabile di hit/miss (svuotando il datastore/memcache tra le esecuzioni o giocando con l'intervallo 'provider_user_id' è in). – selkie

+2

Una nota: se stai eseguendo un grande benchmark, devi far ruotare il tuo traffico gradualmente (diciamo 5-10 minuti) e poi sostenerlo per un po '(altri 5-10 minuti) per misurare gli effetti realistici. App Engine non farà girare immediatamente le istanze necessarie quando il carico va da 0 a 100; c'è un "governatore" su questo processo per evitare instabilità. –

+0

Ho appena letto sul comportamento di "una scrittura al secondo per gruppo di entità" di HRD. Nel codice sopra, il suo non spiegherebbe i nostri problemi? Ci sono solo una manciata di provider (principalmente facebook) e _Identity_ ha _Provider_ come genitore, rendendoli un _entity group_? – selkie

1

Il primo momento ovvio che vedo: hai davvero bisogno di una transazione per ogni richiesta?

Credo che a meno che la maggior parte delle richieste non crei nuove entità, è meglio fare .get_by_id() al di fuori della transazione.E se l'entità non viene trovata, avviare la transazione o, ancora meglio, rinviare la creazione dell'entità.

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

Questo dovrebbe fornire tempi di risposta molto migliori per le richieste degli utenti.

La seconda e unica ottimizzazione che vedo è utilizzare ndb.put_multi() per minimizzare le chiamate RPC.

P.S. Non sicuro al 100% ma puoi provare a disabilitare il multithreading (threadsave: no) per ottenere un tempo di risposta più stabile.

Problemi correlati