2009-07-09 10 views
9

Sto provando a scrivere una query GQL che restituisce N record casuali di un tipo specifico. La mia attuale implementazione funziona ma richiede N chiamate al datastore. Mi piacerebbe farlo 1 chiamata al datastore se possibile.Interrogazione per N record casuali su Datastore Appengine

Attualmente assegno un numero casuale ad ogni tipo che inserisco nel datastore. Quando richiedo un record casuale, genera un altro numero casuale e interrogazione per i record> rand ORDER BY asc LIMIT 1.

Questo funziona, tuttavia, restituisce solo 1 record, quindi è necessario eseguire N query. Qualche idea su come fare questa domanda? Grazie.

+0

Ho creato un problema per questo, è possibile protagonista per aiutare farlo riparare: https://code.google.com/p/googleappengine/issues/detail?id=9044 –

risposta

5

"Sotto il cofano" una singola query di ricerca può solo restituire un insieme di righe consecutive da un indice. Questo è il motivo per cui alcune query GQL, incluso qualsiasi uso di! =, Si espandono su più chiamate di datastore.

N selezioni casuali indipendenti indipendenti non sono (in generale) consecutive in alcun indice.

QED.

Probabilmente si potrebbe usare memcache per memorizzare le entità e ridurre il costo di afferrare N di esse. Oppure, se non ti dispiace che le selezioni "casuali" siano vicine nell'indice, seleziona un blocco scelto casualmente di (diciamo) 100 in una query, quindi scegli N a caso da quelle. Dato che hai un campo già randomizzato, non sarà immediatamente ovvio a un estraneo che gli elementi N sono correlati. Almeno, non finché non guardano molti campioni e notano che gli oggetti A e Z non compaiono mai nello stesso gruppo, perché sono oltre 100 nell'indice randomizzato. E se le prestazioni lo consentono, puoi ri-randomizzare le tue entità di volta in volta.

+0

Grazie - Ho davvero bisogno di risultati randomizzati quindi suppongo che dovrò utilizzare più chiamate di datastore. Proverò a minimizzare N per quanto posso indovinare. – aloo

+0

questo non è vero. entrambe [operazioni batch] (https://developers.google.com/appengine/docs/python/datastore/entities?hl=it#Batch_Operations) e ['IN'] (https://developers.google.com/ appengine/docs/python/datastore/query # Property_Filters) l'operatore di query può restituire entità non consecutive. – ryan

+0

@ryan: lo stesso con '! ='. Sia quello che il 'IN' sono implementati come un numero limitato di sottoquery. Le operazioni batch non sono realmente rilevanti per la domanda, ma sì, è vero che alcune operazioni agiscono su entità non contigue in alcun indice. Solo non ricerche. –

3

Sembra che l'unico metodo sia memorizzare il valore intero casuale nella proprietà speciale di ogni entità e interrogare su quello. Questo può essere fatto in modo abbastanza automatico se si aggiunge una proprietà inizializzata automaticamente.

Purtroppo ciò richiederà l'elaborazione di tutte le entità una volta se il datastore è già compilato.

E 'strano, lo so.

+0

Penso che questo sia un approccio eccezionale, e si adatta al modello NoSQL di fare il lavoro sulla scrittura piuttosto che sulla lettura. Naturalmente, questo non sarebbe del tutto casuale: se avessi sempre ricevuto N voci sequenziali, un utente vedrebbe occasionalmente gli stessi record l'uno accanto all'altro. Ma questo potrebbe essere abbastanza casuale per l'OP. (Puoi anche creare diverse centinaia di proprietà con numeri casuali diversi e ruotare quale indice trai da.) – npdoty

4

Che tipo di compromessi stai cercando? Se sei disposto a sopportare un piccolo calo di prestazioni nell'inserimento di queste entità, puoi creare una soluzione per ottenere N di esse molto rapidamente.

Ecco cosa dovete fare:

Quando si inserisce il tuo Entità, specificare la chiave. Vuoi dare le chiavi alle tue entità in ordine, iniziando con 1 e salendo da lì. (Questo richiederà un po 'di sforzo, poiché il motore dell'app non ha autoincrement() quindi dovrai tenere traccia dell'ultimo id che hai usato in qualche altra entità, chiamiamolo un IdGenerator)

Ora quando hai bisogno N entità casuali, generano N numeri casuali tra 1 e qualunque sia l'ultimo id che hai generato (il tuo IdGenerator lo saprà). È quindi possibile eseguire una chiave di estrazione per chiave utilizzando i tasti N, che richiedono solo un viaggio nell'archivio dati e saranno anche più veloci di una query, poiché le chiavi vengono generalmente più veloci delle query, AFAIK.

Questo metodo non richiede che fare con alcuni dettagli fastidiosi:

  1. tuo IdGenerator potrebbe diventare un collo di bottiglia, se si sta inserendo un sacco di questi elementi al volo (più di qualche secondo), che richiederebbe una sorta di implementazione idGenerator sharded.Se tutti questi dati sono precaricati o non hanno un volume elevato, è facile.
  2. Potresti scoprire che qualche ID in realtà non ha più un'entità associata ad esso, perché lo hai eliminato o perché un put() non è riuscito da qualche parte. Se ciò accadesse, dovresti prendere un'altra entità casuale. (Se si desidera ottenere fantasia e ridurre le probabilità di questo si potrebbe rendere questo Id disponibile per IdGenerator da riutilizzare per "riempire i buchi")

Quindi la domanda si riduce a quanto velocemente è necessario questi N articoli vs quanto spesso li aggiungerai e li eliminerai, e se un po 'di complessità in più valga la pena di aumentare le prestazioni.

+1

Puoi implementare più o meno questo utilizzando la numerazione incorporata per gli ID di App Engine - se conosci l'ID massimo, puoi sceglierne alcuni in modo casuale. Alcuni non esisteranno, quindi riprovateli con nuovi ID casuali e così via. Se il tuo spazio ID è denso, funzionerà correttamente. –

+0

dolce. Non sapevo che potevamo fare affidamento sulla numerazione incorporata per iniziare da 1 e andare da 1 a 1 da lì. –

+0

Non è possibile - ma verrà assegnato in blocchi e fintanto che i blocchi vengono utilizzati principalmente, i tentativi dovrebbero essere abbastanza piccoli da essere gestibili. –

0

Ho appena avuto lo stesso problema. Ho deciso di non assegnare gli ID alle mie voci già esistenti in archivio dati e l'ho fatto, poiché avevo già il totale da un contatore di dimensioni ridotte.

Questo seleziona le voci "conta" dalle voci "totale", ordinate per chiave.

# select $count from the complete set 
    numberlist = random.sample(range(0,totalcount),count) 
    numberlist.sort() 

    pagesize=1000 

    #initbuckets 
    buckets = [ [] for i in xrange(int(max(numberlist)/pagesize)+1) ] 
    for k in numberlist: 
     thisb = int(k/pagesize) 
     buckets[thisb].append(k-(thisb*pagesize)) 
    logging.debug("Numbers: %s. Buckets %s",numberlist,buckets) 

    #page through results. 

    result = [] 
    baseq = db.Query(MyEntries,keys_only=True).order("__key__") 
    for b,l in enumerate(buckets): 
     if len(l) > 0: 
      result += [ wq.fetch(limit=1,offset=e)[0] for e in l ] 

     if b < len(buckets)-1: # not the last bucket 
      lastkey = wq.fetch(1,pagesize-1)[0] 
      wq = baseq.filter("__key__ >",lastkey) 

Attenzione che questo per me è un po 'complessa, e io non sono ancora conviced che io non sono errori di off-by-one o off-by-x.

E attenzione che se il conteggio è vicino al totale, questo può essere molto costoso. E fai attenzione che su milioni di righe potrebbe non essere possibile farlo entro i limiti temporali di appengine.

1

Accetto la risposta di Steve, non esiste un modo per recuperare N righe casuali in una query.

Tuttavia, anche il metodo di recupero di una singola entità normalmente non funziona in modo tale che la prbability dei risultati restituiti sia equamente distribuita. La probabilità di restituire una determinata entità dipende dalla differenza tra il numero assegnato a caso e il numero casuale successivo più alto. Per esempio. se sono stati assegnati numeri casuali 1,2 e 10 (e nessuno dei numeri 3-9), l'algoritmo restituirà "2" 8 volte più spesso di "1".

Ho risolto questo problema in modo leggermente più costoso. Se qualcuno è interessato, sono felice di condividere

-1

Se ho capito bene, è necessario recuperare N istanza casuale.

È facile. Basta interrogare solo con le chiavi. E fare random.choice N volte sul risultato dell'elenco di chiavi. Quindi ottieni risultati recuperando le chiavi.

keys = MyModel.all(keys_only=True) 

n = 5 # 5 random instance 

all_keys = list(keys) 
result_keys = [] 

for _ in range(0,n) 
    key = random.choice(all_keys) 
    all_keys.remove(key) 
    result_keys.append(key) 

# result_keys now contain 5 random keys. 
+0

E se hai un milione di entità nel tuo data store? Carica tutte le chiavi dall'archivio dati - sembra male ... – aloo

+0

@aloo se hai così tante istanze, puoi tenere traccia del loro numero totale in archivio dati e memcache, quindi devi semplicemente eseguire "random.choice" su intervallo di numeri tra 0 e numero totale. E dopo aver semplicemente iterato sulle chiavi con indice, che hai generato. O semplicemente usa limite e offset. –

Problemi correlati