2010-08-10 3 views
15

Actuellement, je suis en utilisant quelque chose comme ceci:Comment obtenir quelque chose de aléatoire dans le magasin de données (AppEngine)?

images = Image.all() 
    count = images.count() 
    random_numb = random.randrange(1, count) 
    image = Image.get_by_id(random_numb) 

Mais il se trouve que les IDs datastore sur AppEngine ne sont pas partis de 1. J'ai deux images et leurs datastore ids sont 6001 et 7001.

Existe-t-il un meilleur moyen de récupérer des images aléatoires?

Répondre

17

La banque de données est distribuée, de sorte que les ID sont non séquentiels: deux nœuds de banque de données doivent être en mesure de générer un ID en même temps sans provoquer de conflit.

Pour obtenir une entité aléatoire, vous pouvez attacher un flottant aléatoire entre 0 et 1 à chaque entité sur créer. Puis d'interroger, faire quelque chose comme ceci:

rand_num = random.random() 
entity = MyModel.all().order('rand_num').filter('rand_num >=', rand_num).get() 
if entity is None: 
    entity = MyModel.all().order('rand_num').get() 

Edit: Mis à jour le cas de chute par par la suggestion de Nick.

+4

Dans le cas 'entity is None', vous devez simplement aller chercher la première entité, ordonnée par' rand_num', traitant ainsi les entités comme un tampon circulaire. La façon dont vous le faites actuellement rend la dernière entité très légèrement plus susceptible d'être choisie que toutes les autres. –

+0

Comment cette requête serait-elle indexée? J'ai dû mettre en œuvre une solution pour cela et n'ai pas choisi cette méthode par peur du manque d'efficacité de ce type de requête. Je ne sais pas si mes craintes sont fondées (voir la solution ci-dessous). –

+0

Chaque propriété inclut automatiquement un index croissant et décroissant, à moins que vous ne le désactiviez explicitement. Le code ci-dessus devrait être efficace à l'échelle. Je l'ai mis à jour pour refléter la révision de Nick. –

1

Une autre méthode (moins efficace), qui ne nécessite aucune installation:

query = MyModel.all(keys_only=True) 

# query.filter("...") 

selected_key = None 
n = 0 
for key in query: 
    if random.randint(0,n)==0: 
    selected_key = key 
    n += 1 

# just in case the query is empty 
if selected_key is None: 
    entry = None 
else: 
    entry = MyModel.get(selected_key) 
10

Une autre solution (si vous ne voulez pas ajouter une propriété supplémentaire). Conservez un ensemble de clés en mémoire.

import random 

# Get all the keys, not the Entities 
q = ItemUser.all(keys_only=True).filter('is_active =', True) 
item_keys = q.fetch(2000) 

# Get a random set of those keys, in this case 20 
random_keys = random.sample(item_keys, 20) 

# Get those 20 Entities 
items = db.get(random_keys) 

Le code ci-dessus illustre la méthode de base pour obtenir uniquement des clés, puis créer un ensemble aléatoire avec lequel obtenir un lot. Vous pouvez conserver cet ensemble de clés en mémoire, l'ajouter à mesure que vous créez de nouvelles entités ItemUser, puis disposer d'une méthode qui renvoie une n Entités aléatoires. Vous devrez implémenter une surcharge pour gérer les clés memcached. J'aime mieux cette solution si vous effectuez souvent la requête pour des éléments aléatoires (je suppose que l'utilisation d'un batch donne n Entities est plus efficace qu'une requête pour n Entities).

+0

Comment pouvons-nous l'appliquer au NDB? –

6

Je pense que la réponse de Drew Sears ci-dessus (attacher un flottant aléatoire à chaque entité sur créer) a un problème potentiel: chaque élément n'a pas une chance égale d'être choisi. Par exemple, s'il n'y a que 2 entités, et que l'une obtient un rand_num de 0,2499, et l'autre de 0,25, la 0,25 sera choisie presque tout le temps. Cela pourrait ou non avoir de l'importance pour votre application. Vous pouvez résoudre ce problème en changeant le rand_num d'une entité chaque fois qu'elle est sélectionnée, mais cela signifie que chaque lecture nécessite également une écriture.

Et la réponse pix sélectionnera toujours la première clé.

est ici la meilleure solution polyvalente que je pourrais venir avec:

num_images = Image.all().count() 
offset = random.randrange(0, num_images) 
image = Image.all().fetch(1, offset)[0] 

Pas de propriétés supplémentaires nécessaires, mais l'inconvénient est que le nombre() et fetch() à la fois avoir des répercussions sur la performance si le nombre d'images est large.

+0

pour résoudre le problème de performance count(), j'utilise Shardingcounters (https://developers.google.com/appengine/articles/sharding_counters) – John

Questions connexes