2009-06-05 9 views
25

Fondamentalmente ho il classico modello da molti a molti. Un utente, un premio e una mappatura tabella "molti a molti" tra utenti e premi.BigTable è lento o sono stupido?

Ogni utente ha un ordine di 400 premi e ogni premio viene assegnato a circa 1/2 degli utenti.

Voglio ripetere tutti i premi dell'utente e riassumere i loro punti. In SQL sarebbe un join di tabella tra i molti-a-molti e quindi camminare attraverso ciascuna delle righe. Su una macchina decente con un'istanza MySQL, 400 righe non dovrebbero essere un grosso problema.

Sul motore dell'app vedo circa 10 secondi per fare la somma. La maggior parte del tempo viene speso nel datastore di Google. Ecco le prime righe di cProfile

 
    ncalls tottime percall cumtime percall filename:lineno(function) 
     462 6.291 0.014 6.868 0.015 {google3.apphosting.runtime._apphosting_runtime___python__apiproxy.Wait} 
     913 0.148 0.000 1.437 0.002 datastore.py:524(_FromPb) 
    8212 0.130 0.000 0.502 0.000 datastore_types.py:1345(FromPropertyPb) 
     462 0.120 0.000 0.458 0.001 {google3.net.proto._net_proto___parse__python.MergeFromString} 

Il mio modello di dati è errato? Sto facendo le occhiate sbagliate? È una lacuna che devo affrontare con il caching e il bulkupdating (che sarebbe un vero rompicoglioni).

+9

+1 LOL. Adoro il titolo di questa domanda! – Elijah

+1

Il BigTable di Google non è praticamente una tabella hash? – balpha

+0

Quella superiore funziona come attesa ... perché stai trascorrendo 6 secondi in un'attesa? – workmad3

risposta

20

potrebbe essere un po 'di entrambi ;-)

Se stai facendo 400 query sul tavolo Awards, uno per ogni risultato restituito per una query sulla tabella di mapping, quindi mi aspetto che per essere doloroso . Il limite di 1000 risultati sulle query esiste perché BigTable ritiene che restituire 1000 risultati sia al limite della sua capacità di operare in un tempo ragionevole. In base all'architettura, mi aspetto che le 400 query siano molto più lente della query che restituisce 400 risultati (400 log N vs. (log M) + 400).

La buona notizia è che su GAE, la memcaching di una singola tabella contenente tutti i premi ei relativi valori dei punti è piuttosto semplice (beh, sembrava piuttosto semplice quando guardo i documenti memcache qualche istante prima. necessario per farlo ancora).

Inoltre, se non lo sapevi, for result in query.fetch(1000) è molto più veloce di for result in query e sei limitato a 1000 risultati in entrambi i modi. I vantaggi di quest'ultimo sono (1) potrebbe essere più veloce se si esegue il salvataggio in anticipo e (2) se Google aumenta il limite oltre 1000, ottiene il vantaggio senza un cambio di codice.

Potresti anche avere problemi quando elimini un utente (o un premio). Ho trovato in una prova che potevo eliminare 300 oggetti entro il limite di tempo. Questi oggetti erano più complessi degli oggetti di mappatura, con 3 proprietà e 5 indici (compresi quelli impliciti), mentre la tabella di mappatura probabilmente ha solo 2 proprietà e 2 indici (impliciti). [Modifica: ho appena realizzato che ho fatto questo test prima di sapere che db.delete() può fare una lista, che è probabilmente molto più veloce].

BigTable non fa necessariamente le cose che i database relazionali sono progettati per fare bene. Invece, distribuisce bene i dati attraverso molti nodi. Ma quasi tutti i siti web funzionano bene con un collo di bottiglia su un singolo server db, e quindi non hanno strettamente bisogno della cosa che fa BigTable.

Un'altra cosa: se si eseguono 400 query di datastore su una singola richiesta http, si scoprirà che si colpisce la quota fissa del datastore ben prima di raggiungere la quota fissa della richiesta.Ovviamente se ti trovi bene all'interno di quote o se prima stai colpendo qualcos'altro, questo potrebbe non essere pertinente per la tua app. Ma il rapporto tra le due quote è qualcosa come 8: 1, e prendo questo come un suggerimento su ciò che Google si aspetta che il mio modello di dati sia simile.

+4

Grandi consigli. Sembra che dovrei passare ai normali modelli di Django e archiviarli tutti su MySQL finché non avrò trovato un problema di ridimensionamento. –

+2

Se i tuoi dati sono migliori in MySQL rispetto a BigTable, penso che devi chiederti perché stai utilizzando il motore delle app. Se c'è una buona ragione ("hosting gratuito", ad esempio), allora credo di sì, ma a me sembra un po 'un trucco. BigTable (e in generale la distribuzione attraverso il cloud di Google) è probabilmente l'unica differenza tecnica interessante tra GAE e qualsiasi vecchio stack LAMP. –

+4

Oppure potresti rivedere i tuoi modelli. Con il datastore appengine, non si desidera eseguire iterazioni su righe durante una richiesta, ma estrarre rapidamente una riga. Un modo per farlo è quello di mantenere i totali/subtotali/aggregati aggiornati al momento della scrittura, non al momento della lettura. Un altro modo per farlo è eseguire i processi in background (con il loro cron o remote_api) per aggiornare i totali/subtotali/aggregati in modo asincrono. – dar

0

Google BigTable eseguito su Google Distributed File System.

I dati sono distribuiti. Forse 400 righe mysql hanno ancora meglio, ma per dati più grandi google BigTable potrebbe essere più veloce.

Penso che questo sia il motivo per cui ci incoraggiano ad usare memcache per renderlo più veloce.

19

Il mio modello di dati è errato? Sto facendo le ricerche errate?

Sì e sì, ho paura.

Per quanto riguarda il modello di dati, il modo migliore per gestirlo è archiviare la somma rispetto al record Utente e aggiornarla quando un utente ottiene/perde un premio. Non ha davvero senso contare il loro punteggio ogni volta che la maggior parte delle volte rimane invariato. Se si rende l'entità "Utente" di tipo un'entità figlio di "Utente", è possibile aggiornare il punteggio e inserire o eliminare la voce UserAward in una singola transazione atomica, assicurando che il conteggio sia sempre accurato.

onebyone indica che è possibile memorizzare la tabella dei premi. Questa è una buona idea, ma data la quantità limitata di dati, uno ancora migliore è quello di memorizzarlo nella memoria locale. I membri globali persistono tra le richieste HTTP e, poiché presumo che non si aggiorni spesso la tabella dei premi, non è necessario preoccuparsi di invalidare la cache. Basta caricarlo sulla prima richiesta (o persino inserirlo hardcode nella sorgente). Se modifichi l'elenco dei premi, l'implementazione di un nuovo aggiornamento minore reimposterà tutte le istanze, causandone il ricaricamento.

Per le ricerche, tenere presente che un costo notevole delle operazioni di datastore è il tempo di andata e ritorno. Un'operazione get(), che ricerca uno o più record per ID (puoi batch!) Richiede circa 20-40 ms. Una query, tuttavia, richiede circa 160-200 ms. Quindi, il potere della denormalizzazione.

+0

Grazie. Ho semplificato il mio problema un po 'per questa domanda. Aggiorno un po 'i premi, oltre ai premi. E per restituire "UserAwards" avrò bisogno di più informazioni che solo i punti. Mi piacerebbe l'icona per il premio, e probabilmente il titolo. Il tuo batch viene() fatto su referenze? Quando avrò le mie 400 righe UserAward e comincio a camminare per ottenere l'utenteAward.award otterrà l'ID e le batch? Quello potrebbe essere il salvagente giusto lì. –

+0

Non è possibile definire la risoluzione delle proprietà di riferimento in batch in modo "naturale". Quello che puoi fare è chiamare myent.properties() ['propname']. Get_value_for_datastore (myent) per recuperare la chiave, che ti consente di raggruppare le cose. Anche se si aggiornano molto i premi, suggerirei comunque di memorizzarli tutti nella memoria locale o in memcache, in qualche modo per invalidare la cache. L'altra opzione è utilizzare una ListProperty (riferimento) di premi per ciascuna entità. Se non hai bisogno di guardare gli utenti con i loro premi, puoi impostare indexed = False per ridurre anche l'overhead. –

1

Un importante idioma del motore di app è che lo storage è economico ma il tempo non è mai in eccedenza. Sembra che il modo migliore per fare molte più relazioni nel motore di app sia semplicemente archiviare le informazioni su entrambi i lati. Ad esempio, un utente ha un elenco di premi e ogni premio ha un elenco di utenti. Per cercare tutti i riconoscimenti di un utente, devi semplicemente interrogare la tabella dei premi per un determinato utente.

Questa idea è ben dimostrato qui: Building Scalable Complex Apps

0

Anche Lei parla di BigTable, penso che si sta implementando un database relazionale SQL sulla nube.

La tua modella va bene, è il modo giusto di fare qualcosa del genere. Non vedo una buona ragione per de-normalizzare gli aggregati sulla tabella degli utenti.

Hai creato indici per unire rapidamente i tavoli. È piuttosto semplice Potrebbe essere necessario disporre di indici BTree per tutti i campi che prevedono l'unione delle tabelle. Non c'è bisogno di indicizzare il campo di aggregazione (che si prende la SUM di). Fondamentalmente entrambe le chiavi esterne della tabella N: N dovrebbero essere indicizzate. Se quelle chiavi esterne si riferiscono alla chiave primaria di altri due tavoli, è sufficiente andare.

Sopra l'ordine di 100 righe, un semplice indice BTree su chiavi esterne può avere un aumento decente e notevole del throughput.

Sto eseguendo un database su CloudSQL in cui alcune tabelle di bordo contengono oltre 2 milioni di record. Solo dopo i 2,5 milioni di record sto considerando un po 'di de-normalizzazione, e anche alcuni indici extra, e ancora aggregando per la SUM. In caso contrario, farei aggiornamenti non necessari al campo SUM ogni volta che vengono aggiunti nuovi record.

Solo quando la tabella superava 1 milione di record, dovevamo considerare l'utilizzo di una replica di lettura.E cioè quando potevamo distinguere tra processi che leggono solo alcune tabelle e non scrivono.

Se si utilizza Django, fare attenzione quando si implementa LIMIT in base alla relativa documentazione; perché è molto fuorviante. Quando si [: 100] (giunzione) su un set di record, non è ciò che si aspetta dall'SQL effettivamente inviato al server SQL. Ho avuto un momento molto difficile capirlo. Django non è una buona opzione quando pianifichi di fare qualcosa che genererà generosamente su larga scala. Ma nell'ordine di 1000 record, andrebbe bene.