Google App Engine数据存储 - 这种方法足够快吗? (适用于500k用户)

时间:2010-01-02 23:50:59

标签: google-app-engine

假设我们有:

class User(db.Model):
  nickname = db.StringProperty()

我们在User中有500k个实体,每个实体都有一个独特的昵称。

我现在想再添加一个实体,它必须是一个独特的昵称。所以我运行这个:

to_check = User.gql("WHERE nickname = :1",new_nickname).get()
if to_check is None:
  # proceed to create entity
  User(nickname=new_nickname).put()

这种方法适用于超过500k的用户吗?我会经历缓慢的处理时间吗?

这有什么优化方法?

PS:将昵称属性编入索引是一种很好的方法吗?

我现在只能想到这个:

class User(db.Model):
  nickname = db.StringProperty(indexed=True) # index this property

EDITED: 顺便说一句,我有两个我想要维护的独特属性:userid和昵称。 userid将被自动指定为keyname(我正在创建一个facebook应用程序,它获取用户的facebook id并创建用户实体)

所以对我来说,userid更重要,所以我将它用作键名。

昵称将由facebook用户手动输入,因此我需要一种机制来检查它是否是唯一的。

现在的问题是,我怎么处理昵称?我不能有两个关键名:(

6 个答案:

答案 0 :(得分:4)

你应该查看Brett Slatkin的Google I / O视频:

http://code.google.com/events/io/2009/sessions/BuildingScalableComplexApps.html

具体来说,关于关系指数实体的位。他处理的问题与你的问题非常相似。

您可以创建另一个实体,用于存储用户昵称(并将其设置为key_name)。创建它时,将父项设置为用户实体:

UserNickname(
    parent=user,
    key_name=nickname,
    nickname=nickname
)

现在您可以非常快速地查询昵称(get_by_key_name),如果您想要排除当前用户(如果您让用户更改其昵称,您将使用它),您可以轻松地从keys_only查询中获取父级或使用直接在查询中的祖先。

编辑:刚刚注意到Kris Walker已在评论中提出这一建议。您可以使用引用属性或父级将两者链接在一起,两者都可以正常工作。

答案 1 :(得分:2)

只要您在SDK中运行此类查询,nickname属性就会自然地位于index.yaml中,因此请不要过于担心。 indexed属性默认True(通常仅用于将其明确设置为False)。

使用索引,无论如何,搜索可能出现0或1次的昵称将会非常快,无论表中有多少条目 - 比如数量级,50-100毫秒;建立一个新的实体,可能是两倍长。整件事应该在300毫秒或更短的时间内完成。

一个担心是竞争条件 - 如果两个单独的会话试图在同一时间注册完全相同的昵称怎么办?可能不太可能,但是当它发生时,你的代码就没有防御。获得这样的防御(通过在事务中运行)意味着事务锁定,因此可能影响性能(如果几个这样的会话在完全相同的时间运行,它们将被序列化)。

答案 2 :(得分:1)

get_by_key_name 将成为您最好的朋友。

我经常使用如下代码模式:

user = User.get_by_key_name(user_key_name)
if not user:
  user = User(key_name = user_key_name)

这往往比GQL查询快得多。

如果您要一次向数据存储区写入多个实体,则还应使用db.put(entities_list)模式,其中列表最多可包含500个实体 - 他们不会甚至必须是同一种型号。

答案 3 :(得分:0)

您似乎将昵称视为用户实体类型的唯一键。

所以我会这样做(这已经说明了)

class User(db.Model):
  # other properties go here, but not nickname

# put a new user
if User.get_by_key_name(user_nick) is None:
  User(key_name=user_nick).put()

索引策略是一种浪费,即使只是“500k”。

还有db.Model.get_or_insert()

http://code.google.com/appengine/docs/python/datastore/modelclass.html#Model_get_or_insert

答案 4 :(得分:0)

嘿,我只想到另一种方法来解决我的困境!

基本上当用户手动输入昵称时,我会自动将他/她的用户ID添加到其中以使其唯一。

例如:

user_nickname是托马斯。 我将userid附加到它,成为thomas_8937459874(唯一!)

所以我不需要检查以前是否存在昵称。保存我的GQL查询。

当显示昵称的时候,我只会使用字符串操作来只检索名称“thomas”

你觉得怎么样?

答案 5 :(得分:0)

所以我尝试使用ReferenceProperty来执行此操作:

告诉我你们的想法:

添加了附加功能:用户最多只能更改昵称3次

# models.py
# key_name will be whatever the user manually enters to be the nickname
class UserNickname(db.Model):
  name = db.StringProperty()

# key_name = facebook id      
class User(db.Model):
  nickname = db.ReferenceProperty(UserNickname)
  nickname_change_count = db.IntegerProperty(default=0)

# create unique entity with facebook id
User(key_name="123456789").put()

*****以下代码位于注册页面*****

# in the signup page , signup.py
# userid of 123456789 is taken from cached session
user = User.get_by_key_name("123456789")

# this is the nickname manually entered by the user
manually_entered_nick = "Superman"

to_check = UserNickname.get_by_key_name(manually_entered_nick)
if to_check is None:
  #create usernickname entity
  key = UserNickname(key_name=manually_entered_nick,name=manually_entered_nick).put()

  #assign this key to the user entity
  user.nickname = key
  db.put(user)

  print 'Unique nickname registered'
else:
  print 'Choose another nick pls'

*****以下代码位于“更改用户昵称”页面*****

# change_nickname.py
# userid is taken from cached session
user = User.get_by_key_name("123456789")

# max no. of nickname changes allowed is 3 ( hardcoded )

# checks if user can change nick
if user.nickname_change_count >= 3:
  print 'you cannot change nicks anymore. contact admin'
else:
  # delete entire nickname entity
  to_delete = UserNickname.get_by_key_name(user.nickname.key().name())
  db.delete(to_delete)

  # adds to count
  user.nickname_change_count += 1

  # for security purposes, user account is "disabled" until he/she chooses a new nick.

  # user manually enters new nickname
  new_nick = "Batman"
  to_check = UserNickname.get_by_key_name(new_nick)
  if to_check is None:
    #create usernickname entity
    key = UserNickname(key_name=new_nick,name=new_nick).put()

    #assign this nick to user entity
    user.nickname = key
    db.put(user)

    print 'new Nick registered'
  else:
    print 'Choose another nick pls'