2014-09-11 12 views
5

Sto cercando di ridurre il tempo di esecuzione di una query di AppEngine eseguendo più sottoclassi in modo asincrono, utilizzando query.fetch_async(). Tuttavia, sembra che il guadagno sia minimo rispetto all'esecuzione seriale delle query.AppEngine Query.fetch_async non molto asincrono?

seguito alcune codice minimo del campione (in Python) illustra il problema - prima una funzione per l'esecuzione asincrona:

def run_parallel(self, repeats): 
    start = datetime.utcnow() 

    futures = [] 
    for i in xrange(0, repeats): 
     q = User.query() 
     f = q.fetch_async(300, keys_only=True) 
     futures.append(f) 

    while futures: 
     f = ndb.Future.wait_any(futures) 
     futures.remove(f) 
     results = f.get_result() 
     delta_secs = (datetime.utcnow() - start).total_seconds() 
     self.response.out.write("Got %d results, delta_sec: %f<br>\n" %(len(results), delta_secs)) 

Poi una funzione per l'esecuzione seriale corrispondente:

def run_serial(self, repeats): 
    start = datetime.utcnow() 
    for i in xrange(0, repeats): 
     q = User.query() 
     results = q.fetch(300, keys_only=True) 
     delta_secs = (datetime.utcnow() - start).total_seconds() 
     self.response.out.write("Got %d results, delta_sec: %f<br>\n" %(len(results), delta_secs)) 

L' uscita di eseguire queste due funzioni 10 volte ciascuna (non sul dev-server), cioè delle seguenti chiamate:

run_parallel(10) 
run_serial(10) 

è la seguente:

Esecuzione di query parallele ...
Got 300 risultati, delta_sec: 0,401,09 mila
Got 300 risultati, delta_sec: 0,501,7 mila
Got 300 risultati, delta_sec: 0.596110
Got 300 risultati, delta_sec : 0.686120
Si 300 risultati, delta_sec: 0,709,22 mila
Si 300 risultati, delta_sec: 0,792,07 mila
Si 300 risultati, delta_sec: 0,816,5 mille
Si 300 risultati, delta_sec: 0,904,36 mila
Got 300 risultati, delta_sec: 0,993,6 mila
Got 300 risultati, delta_sec: 1,017320

l'esecuzione di query di serie ...
Got 300 risultati, delta_sec: 0,114,95 mila
Got 300 risultati, delta_sec: 0,269,01 mila
Got 300 risultati, delta_sec: 0.370590
Si 300 risultati, delta_sec: 0.472090
Si 300 risultati, delta_sec: 0.575130
Si 300 risultati, delta_sec: 0.678900
Got 300 res ULT, delta_sec: 0.782540
Si 300 risultati, delta_sec: 0,883,96 mila
Si 300 risultati, delta_sec: 0,986,37 mila
Si 300 risultati, delta_sec: 1,086500

Quindi le versioni parallele e seriali prendere approssimativamente allo stesso tempo, intorno 1 secondo. L'Appstat sono i seguenti, in cui i primi 10 domande sono quelle parallele e il seguente 10 sono quelli di serie:

enter image description here

Da queste statistiche sembra che i primi 10 query sono effettivamente in esecuzione in parallelo, ma che ognuno di loro sta prendendo una quantità sproporzionata di tempo rispetto alle singole interrogazioni seriali. Sembra che potrebbero bloccarsi in qualche modo, in attesa che si completino l'un l'altro.

Quindi la mia domanda: C'è qualcosa di sbagliato nel mio codice per l'esecuzione di query asincrone? Oppure esiste una limitazione intrinseca nell'efficienza delle query asincrone su AppEngine?

ho chiesto se il comportamento può essere causato da una delle seguenti:

  1. Esecuzione query asincrone sullo stesso tipo di entità. Tuttavia, un esempio simile che utilizza più tipi di entità differenti mostra risultati simili.
  2. Esecuzione di query identiche, in qualche modo blocchi delle sezioni dell'indice. Tuttavia, un esempio simile in cui ogni query è diversa (restituendo set di risultati disgiunti) produce risultati simili.

Quindi, sono un po 'una perdita. Ogni suggerimento sarà molto apprezzato.

Update 1

Seguendo il suggerimento di Bruyere Ho provato con db piuttosto che NDB, e ho cercato di scambiare l'ordine del parallelo e le versioni di serie. I risultati sono gli stessi.

Update 2

Ecco un post correlato in questione con lo stesso problema; ancora nessuna risposta sul motivo per cui le query parallele sono così inefficienti:

Best practice to query large number of ndb entities from datastore

Update 3

Il codice corrispondente utilizzando l'SDK di Java è parallelised molto ordinatamente. Ecco il Java appstats:

enter image description here

per essere precisi, questa implementazione Java è esplicitamente, le query multi-threaded in esecuzione in thread separati; questo è necessario perché, contrariamente a quanto afferma la documentazione di AppEngine, l'utilizzo degli iteratori di query non è non, in realtà le query vengono eseguite in parallelo.

Ho provato a utilizzare il multi-threading esplicito con chiamate di query sincrone nella versione Python, ma con gli stessi scarsi risultati della versione Python originale.

Il fatto che la versione Java funzioni come previsto implica che le scarse prestazioni asincrone di Python non siano causate da un collo di bottiglia della CPU di AppEngine.

L'unica spiegazione alternativa che posso pensare è che il Global Interpreter Lock di Python sta causando il thrashing. Ciò è supportato dal fatto che la riduzione dell'intervallo di controllo GIL (utilizzando sys.setcheckinterval) esaspera le scarse prestazioni asincrone.

Questo è sorprendente: il GIL non dovrebbe avere un impatto così grave dato che le query sono legate all'IO. Suppongo che forse i buffer di input RPC siano abbastanza piccoli da richiamare frequentemente le chiamate asincrone durante il recupero dei risultati, il che potrebbe causare il thriller GIL. Ho dato un'occhiata al codice della libreria di AppEngine Python, ma le chiamate RPC di basso livello sono fatte da _apphosting_runtime ___ python__apiproxy.MakeCall() che sembra essere closed-source.

Ahimé, la mia conclusione è che il runtime di AppEngine Python non è adatto per il tipo di query parallela richiesta, lasciandomi senza altra opzione che passare al runtime Java.Mi piacerebbe davvero evitare questo, e quindi spero davvero che mi sbagli e mi sia sfuggito qualcosa di ovvio. Qualsiasi suggerimento o suggerimento sarebbe molto apprezzato.

Grazie!

+0

Sto avendo gli stessi problemi e ho sperimentato sys.setcheckinterval, query parallele in esecuzione, ma nulla ha avuto un impatto significativo sulle prestazioni. Ottenere 9k elementi dall'archivio dati richiede 19 secondi su istanze F2 e 10 secondi su istanze F3. Sembra che l'unico modo per farcela sia buttare l'hardware su di esso? – thomasf1

+0

PS: istanze F1 testate per il confronto ... 27 secondi. Insopportabile. – thomasf1

+0

PS: Sto cercando una soluzione più generale [qui # 26759950] (https://stackoverflow.com/questions/26759950/gae-aggregate-work-results-from-tasks-for-a-gae-query -performance-issue) – thomasf1

risposta

0

Il problema principale è che il tuo esempio è per lo più legato alla CPU e non legato all'IO. In particolare, la maggior parte del tempo è probabilmente spesa nella decodifica dei risultati RPC che non viene eseguita in modo efficiente in Python a causa della GIL. Uno dei problemi con Appstats è che misura i tempi RPC da quando viene inviato l'RPC quando viene chiamato get_result(). Ciò significa che il tempo speso prima che venga richiamato get_result sembrerà provenire dagli RPC.

Se si emettono invece RPC con rilegatura IO (vale a dire le query che rendono il datastore più duro) inizierete a vedere i guadagni in termini di prestazioni delle interrogazioni parallele.

+0

Grazie Patrick, ho anche raggiunto questa conclusione e ho accettato la tua risposta. Quello che trovo sorprendente è che non esiste una soluzione migliore: non vedo alcun motivo per cui l'API non dovrebbe essere in grado di evitare il thrash di GIL di 1) emettendo tutte le richieste in modo asincrono e 2) la sincronizzazione per recuperare i risultati in modo sequenziale. –

1

Stai sempre eseguendo run_parallel prima di run_serial? Se così ndb memorizza nella cache i risultati ed è in grado di estrarre le informazioni molto più velocemente. Prova a capovolgere i risultati o prova ancora meglio con DB, dato che ndb è solo un wrapper per includere i risultati di memcache.

+0

Bruyere, grazie per il tuo suggerimento, molto apprezzato. Tuttavia, lo scambio di run_parallel e run_serial non fa differenza. I risultati sono simmetrici. Per quanto riguarda l'uso di ndb: per quanto ne so, non avviene alcuna memorizzazione nella cache per le query, almeno non nel caso delle sole chiavi. Ho sbagliato? In tal caso, potrei provare a creare un caso di test separato (le funzioni sopra elencate funzionano contro entità esistenti di ndb). –

+0

@MichaelPedersen Credo che tu abbia ragione sul fatto che non ci sia memorizzazione nella cache per le query. Documenti: "I risultati delle query vengono riscritti nella cache nel contesto se la politica della cache lo dice (ma mai su Memcache)." https://cloud.google.com/appengine/docs/python/ndb/cache. L'unica volta che ho visto un memcache in AppStats è stato da una ricerca diretta della chiave (ad esempio ndb.Key get(), ndb.Model get_by_id(), ndb.get_multi()). Non sono sicuro di come misurare gli hit della cache nel contesto. – Scotty