Python 优化社交排行榜
我正在使用Google App Engine(python)作为移动社交游戏的后端。该游戏使用Twitter集成,允许人们关注相关排行榜,并与他们的朋友或追随者比赛 到目前为止,这个难题中最昂贵的部分是后台(push)任务,它点击twitterapi查询给定用户的朋友和追随者,然后将这些数据存储在我们的数据存储中。我正试图优化它,尽可能降低成本 数据模型: 与此部分应用程序相关的主要模型有三种: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''
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_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])
(我记不清或找不到位置,但我读到这篇文章会更好,因为您不会浪费时间尝试读取不存在的模型对象我会这样组织任务:
- 从memcache请求数据,如果数据不存在,则调用
fetch\u async()
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查询似乎实质上是对每个等式执行一系列单独的查询,但其顺序和非并行执行除外)。