Python 优化社交排行榜

Python 优化社交排行榜,python,google-app-engine,optimization,twitter,leaderboard,Python,Google App Engine,Optimization,Twitter,Leaderboard,我正在使用Google App Engine(python)作为移动社交游戏的后端。该游戏使用Twitter集成,允许人们关注相关排行榜,并与他们的朋友或追随者比赛 到目前为止,这个难题中最昂贵的部分是后台(push)任务,它点击twitterapi查询给定用户的朋友和追随者,然后将这些数据存储在我们的数据存储中。我正试图优化它,尽可能降低成本 数据模型: 与此部分应用程序相关的主要模型有三种: User '''General user info, like scores and stats''

我正在使用Google App Engine(python)作为移动社交游戏的后端。该游戏使用Twitter集成,允许人们关注相关排行榜,并与他们的朋友或追随者比赛

到目前为止,这个难题中最昂贵的部分是后台(push)任务,它点击twitterapi查询给定用户的朋友和追随者,然后将这些数据存储在我们的数据存储中。我正试图优化它,尽可能降低成本

数据模型:

与此部分应用程序相关的主要模型有三种:

User
'''General user info, like scores and stats'''
# key id => randomly generated string that uniquely identifies a user
#   along the lines of user_kdsgj326
#   (I realize I probably should have just used the integer ID that GAE
#   creates, but its too late for that)

AuthAccount
'''Authentication mechanism.
     A user may have multiple auth accounts- one for each provider'''
# key id => concatenation of the auth provider and the auth provider's unique
#   ID for that user, ie, "tw:555555", where '555555' is their twitter ID
auth_id = ndb.StringProperty(indexed=True) # ie, '555555'
user = ndb.KeyProperty(kind=User, indexed=True)
extra_data = ndb.JsonProperty(indexed=False) # twitter picture url, name, etc.

RelativeUserScore
'''Denormalization for quickly generated relative leaderboards'''
# key id => same as their User id, ie, user_kdsgj326, so that we can quickly
#     retrieve the object for each user
follower_ids = ndb.StringProperty(indexed=True, repeated=True)
# misc properties for the user's score, name, etc. needed for leaderboard
我不认为这个问题有必要,但为了以防万一,有一个更详细的讨论导致了这个设计

任务

后台线程接收twitter身份验证数据,并通过tweepy从twitterapi请求朋友id块。默认情况下,Twitter最多发送5000个好友ID,如果可以避免的话,我不想随意限制更多(你每分钟只能向他们的API发出这么多请求)

一旦我获得了好友ID列表,我就可以轻松地将其转换为“tw:”authcount密钥ID,并使用get_multi检索authcounts。然后,我删除系统中没有的twitter用户的所有空帐户,并获取系统中twitter好友的所有用户ID。这些ID也是RelativeUserScore的键,因此我使用一组事务性的_tasklet将该用户的ID添加到RelativeUserScore的追随者列表中

优化问题

  • 发生的第一件事是调用Twitter的API。考虑到这是任务中其他所有内容所必需的,我假设我不会从异步中获得任何收益,对吗?(GAE已经足够聪明,可以在服务器阻塞时使用该服务器处理其他任务?)
  • 当确定一个twitter好友是否正在玩我们的游戏时,我当前将所有twitter好友ID转换为身份验证帐户ID,并通过get_multi检索。考虑到这些数据是稀疏的(大多数twitter朋友很可能不会玩我们的游戏),我是否最好使用直接检索用户ID的投影查询?类似于

    twitter_friend_ids = twitter_api.friend_ids() # potentially 5000 values
    friend_system_ids = AuthAccount\
        .query(AuthAccount.auth_id.IN(twitter_friend_ids))\
        .fetch(projection=[AuthAccount.user_id])
    
    (我记不清或找不到位置,但我读到这篇文章会更好,因为您不会浪费时间尝试读取不存在的模型对象

  • 无论我最终使用get_multi还是投影查询,将请求分解为多个异步查询,而不是尝试一次获取/查询潜在的5000个对象,有什么好处吗

  • 我会这样组织任务:

  • 对Twitter提要进行异步获取调用
  • 要保存所有AuthAccount->User数据:
    • 从memcache请求数据,如果数据不存在,则调用
      fetch\u async()
  • 通过dict运行每个twitter id
  • 以下是一些示例代码:

    future = twitter_api.friend_ids()    # make this asynchronous
    
    auth_users = memcache.get('auth_users')
    if auth_users is None:
        auth_accounts = AuthAccount.query()
                                   .fetch(projection=[AuthAccount.auth_id,
                                                      AuthAccount.user_id])
        auth_users = dict([(a.auth_id, a.user_id) for a in auth_accounts])
        memcache.add('auth_users', auth_users, 60)
    
    twitter_friend_ids = future.get_result()  # get async twitter results
    
    friend_system_ids = []
    for id in twitter_friend_ids:
        friend_id = auth_users.get("tw:%s" % id)
        if friend_id:
            friend_system_ids.append(friend_id)
    
    这是针对相对较少的用户数和较高的请求率而优化的。您上面的评论表明用户数较高,请求率较低,因此我仅对您的代码进行以下更改:

    twitter_friend_ids = twitter_api.friend_ids() # potentially 5000 values
    auth_account_keys = [ndb.Key("AuthAccount", "tw:%s" % id) for id in twitter_friend_ids]
    friend_system_ids = filter(None, ndb.get_multi(auth_account_keys))
    

    这将使用ndb的内置memcache在使用带有键的
    get_multi()
    时保存数据。

    您希望有多少个Twitter ID(AuthAccount实体),它们是否都可以放在内存中?接下来,您希望多久运行一次此任务?使用Twitter登录的每个用户都有一个对应的AuthAccount(技术上更多,因为他们的电子邮件/密码登录有一个系统AuthAccount;但我可以添加一个过滤器来过滤这些)。我们需要一个可以扩展到非常大的数字的系统。我知道大的增长是一个长期的目标,但我们需要为大的数字设计,因为游戏将有一些促销活动。“它们都可以放在内存中吗?”我不确定你在这里的确切意思。它们不会全部放在实例的内存中。我怀疑任何用户是否会有足够的追随者玩游戏,使他们不能放在重复的追随者中。至于这项任务运行的频率,我已经尝试过优化,并将进行更多的优化。它主要通过他们的所有朋友和追随者运行他们第一次使用他们的twitter帐户登录。然后,如果我们注意到他们的朋友比以前多,它会再次运行。我计划添加一些逻辑,以便它不会请求所有人,直到获得新朋友(twitter api首先返回最新朋友)。然后我可能需要偶尔检查(每月?每周?)如果他们删除了用户,或者添加了相同数量的用户,请检查整个列表。正如您在第一种方法中提到的,它针对不同的目标进行了优化。至于第二种方法,我目前正在这样做:
    tw_auth_accounts=ndb.get_multi([ndb.Key(authcount),tw:%s”%id)用于tw_id中的id])
    。map(None,get_mult(…)比使用get_mult(…)有什么好处?
    map()
    调用将过滤掉
    None
    值。嗯,我是不是遗漏了什么?x=map(None,[None,1,2,3])返回[None,1,2,3]。总之,我只是使用列表理解来过滤掉它们。我的错误,应该是
    filter()
    。列表理解也可以。好吧,我只是做了一系列负载测试来比较这两个选项。在get\u multi上使用投影查询似乎没有什么好处(RPC或数据存储读取/写入)。get\u multi通常比较便宜,因为memcache(如您所建议的)。此外,查询方法通常也比较慢(b/c执行IN查询似乎实质上是对每个等式执行一系列单独的查询,但其顺序和非并行执行除外)。