2012-05-03 2 views
8

Ho un'app Google App Engine che utilizza un'istanza di Google Cloud SQL per l'archiviazione dei dati. Ho bisogno che la mia istanza sia in grado di servire centinaia di client alla volta, tramite chiamate restful, che generano ciascuna una o più query DB. Ho avvolto i metodi che richiedono l'accesso ai DB e archiviamo l'handle nella connessione DB in os.environ. Vedi this SO domanda/risposta per fondamentalmente come sto facendo.Quali sono i limiti di connessione per Google Cloud SQL da App Engine e come riutilizzare al meglio le connessioni DB?

Tuttavia, non appena un paio di centinaia di client si connettono al mio app e di trigger chiamate al database, ho iniziare a ricevere questi errori nei log degli errori di Google App Engine (e la mia app restituisce 500, ovviamente):

could not connect: ApplicationError: 1033 Instance has too many concurrent requests: 100 Traceback (most recent call last): File "/base/python27_run 

Qualche consiglio da utenti esperti di Google App Engine e Google Cloud SQL? Grazie in anticipo.

Ecco il codice per il decoratore che uso intorno metodi che richiedono connessione DB:

def with_db_cursor(do_commit = False): 
    """ Decorator for managing DB connection by wrapping around web calls. 
    Stores connections and open connection count in the os.environ dictionary 
    between calls. Sets a cursor variable in the wrapped function. Optionally 
    does a commit. Closes the cursor when wrapped method returns, and closes 
    the DB connection if there are no outstanding cursors. 

    If the wrapped method has a keyword argument 'existing_cursor', whose value 
    is non-False, this wrapper is bypassed, as it is assumed another cursor is 
    already in force because of an alternate call stack. 

    Based mostly on post by : Shay Erlichmen 
    At: https://stackoverflow.com/a/10162674/379037 
    """ 

    def method_wrap(method): 
     def wrap(*args, **kwargs): 
      if kwargs.get('existing_cursor', False): 
       #Bypass everything if method called with existing open cursor 
       vdbg('Shortcircuiting db wrapper due to exisiting_cursor') 
       return method(None, *args, **kwargs) 

      conn = os.environ.get("__data_conn") 

      # Recycling connection for the current request 
      # For some reason threading.local() didn't work 
      # and yes os.environ is supposed to be thread safe 
      if not conn:      
       conn = _db_connect() 
       os.environ["__data_conn"] = conn 
       os.environ["__data_conn_ref"] = 1 
       dbg('Opening first DB connection via wrapper.') 
      else: 
       os.environ["__data_conn_ref"] = (os.environ["__data_conn_ref"] + 1) 
       vdbg('Reusing existing DB connection. Count using is now: {0}', 
        os.environ["__data_conn_ref"])   
      try: 
       cursor = conn.cursor() 
       try: 
        result = method(cursor, *args, **kwargs) 
        if do_commit or os.environ.get("__data_conn_commit"): 
         os.environ["__data_conn_commit"] = False 
         dbg('Wrapper executing DB commit.') 
         conn.commit() 
        return result       
       finally: 
        cursor.close()      
      finally: 
       os.environ["__data_conn_ref"] = (os.environ["__data_conn_ref"] - 
         1) 
       vdbg('One less user of DB connection. Count using is now: {0}', 
        os.environ["__data_conn_ref"]) 
       if os.environ["__data_conn_ref"] == 0: 
        dbg("No more users of this DB connection. Closing.") 
        os.environ["__data_conn"] = None 
        db_close(conn) 
     return wrap 
    return method_wrap 

def db_close(db_conn): 
    if db_conn: 
     try: 
      db_conn.close() 
     except: 
      err('Unable to close the DB connection.',) 
      raise 
    else: 
     err('Tried to close a non-connected DB handle.') 
+0

Hai thread sicuro: vero in app.yaml? – proppy

+0

@proppy sì, lo so. Grazie. – JJC

+0

L'utilizzo di "threadsafe: true" non funzionerà bene con l'utilizzo di os.environ poiché le connessioni non possono essere condivise tra thread. Vedere la mia risposta http://stackoverflow.com/a/10438622/1373093 per una soluzione a prova di thread. –

risposta

14

Risposta breve: Le query sono probabilmente troppo lento e il server MySQL non ha abbastanza thread per elaborare tutti le richieste si sta tentando di inviarlo.

lungo Risposta:

Come sfondo, Cloud SQL ha due limiti rilevanti qui:

  • Connessioni: Questi corrispondono all'oggetto 'conn' nel codice. C'è un datastructure corrispondente sul server. Una volta che ci sono troppi oggetti (attualmente configurati su 1000), il meno recente usato verrà automaticamente chiuso. Quando una connessione viene chiusa al di sotto di te, riceverai un errore di connessione sconosciuto (ApplicationError: 1007) la prossima volta che proverai a usare quella connessione.
  • richieste simultanee: sono queste query che sono in esecuzione sul server. Ogni legami di query che eseguono un thread nel server, quindi non c'è un limite di 100. Quando ci sono troppe richieste simultanee, le successive richieste saranno respinte con l'errore che si stanno ottenendo (ApplicationError: 1033)

E ' non sembra che il limite di connessione ti stia influenzando, ma volevo menzionarlo per ogni evenienza.

Quando si tratta di richieste simultanee, aumentare il limite potrebbe aiutare, ma di solito rende il problema peggiore. Ci sono due casi che abbiamo visto in passato:

  • Deadlock: una query di lunga durata blocca una riga critica del database. Tutte le query successive bloccano su quel blocco. L'app scade a termine su quelle query, ma continuano a essere eseguite sul server, vincolando i thread fino ai trigger deadlock timeout.
  • Query lente: ogni query è davvero molto lenta. Questo di solito accade quando la query richiede un ordinamento di file temporaneo. L'applicazione scade e riprova la query mentre il primo tentativo della query è ancora in esecuzione e il conteggio si scontra con il limite di richieste simultanee. Se riesci a trovare il tempo medio di interrogazione, puoi ottenere una stima di quanti QPS la tua istanza mysql può supportare (ad es. 5 ms per query significa 200 QPS per ogni thread. Poiché ci sono 100 thread, potresti fare 20.000 QPS. per query significa 2000 QPS.)

Si dovrebbe usare EXPLAIN e SHOW ENGINE INNODB STATUS per vedere quale dei due problemi che sta succedendo.

Naturalmente, è anche possibile che si sta guidando un sacco di traffico alla tua istanza e non ci sono abbastanza thread. In tal caso, probabilmente si estenderà comunque la CPU per l'istanza, quindi l'aggiunta di ulteriori thread non aiuterà.

+0

Grazie Ken. Non vedo alcun errore 1007 e credo di aver chiuso correttamente le connessioni. Inoltre, non credo che le query lente siano il colpevole, poiché tutte le query sono abbastanza semplici (niente join, pochi group-by) le tabelle sono piccole (sia orizzontalmente che verticalmente), ci sono indici (o vincoli equivalenti) su tutti colonne in cui è stata cercata, ecc. Anche il calcolo massimo della CPU sull'istanza non è probabilmente un problema, a partire dallo stato del dashboard. Quindi, penso che questo si riduca a deadlock. Sto guardando oltre. Potresti inviarti un consiglio dopo, se va bene. – JJC

+0

Nel tuo frammento di sopra, non vedo dove la connessione è chiusa. Forse non l'hai incluso tutto? –

+1

Inoltre, un GROUP BY può ancora causare un fileort, quindi anche uno potrebbe farti del male. –

4

Ho letto dalla documentazione e ho notato c'è un collegamento 12/limite esempio:

cercare "Ogni istanza App Engine non può avere più di 12 connessioni simultanee a un'istanza di Google Cloud SQL." in https://developers.google.com/appengine/docs/python/cloud-sql/

+0

Grazie. Non ho visto questo nei documenti quando ho postato la domanda due anni fa, ma forse era lì e l'ho mancato. MrGreen In ogni caso, aiuta a spiegare perché il mio progetto non sarebbe scalabile e alla fine è stato necessario abbandonarlo. :-( – JJC

+0

Quindi hai risolto il problema? Oltre a eliminare le vecchie connessioni, sei riuscito a capire qualcos'altro? – smaura777