考虑一个GAE(python)应用程序,它允许用户评论歌曲。预期用户数为1000000+。预计歌曲数量为5000首。
应用程序必须能够:
给出用户评论的歌曲数
给出对歌曲发表评论的用户数
计数器管理必须是事务性的,以便它们始终反映底层数据。
似乎GAE应用程序必须始终计算这些类型的计数,因为在请求时查询它们将是低效的。
我的数据模型

class Song(BaseModel):
    name = db.StringProperty()
    # Number of users commenting on the song
    user_count = db.IntegerProperty('user count', default=0, required=True)
    date_added = db.DateTimeProperty('date added', False, True)
    date_updated = db.DateTimeProperty('date updated', True, False)

class User(BaseModel):
    email = db.StringProperty()
    # Number of songs commented on by the user
    song_count = db.IntegerProperty('song count', default=0, required=True)
    date_added = db.DateTimeProperty('date added', False, True)
    date_updated = db.DateTimeProperty('date updated', True, False)

class SongUser(BaseModel):
    # Will be child of User
    song = db.ReferenceProperty(Song, required=True, collection_name='songs')
    comment = db.StringProperty('comment', required=True)
    date_added = db.DateTimeProperty('date added', False, True)
    date_updated = db.DateTimeProperty('date updated', True, False)

代码
这处理用户的歌曲计数事务,但不处理歌曲的用户计数。
s = Song(name='Hey Jude')
s.put()

u = User(email='[email protected]')
u.put()

def add_mapping(song_key, song_comment, user_key):
    u = User.get(user_key)

    su = SongUser(parent=u, song=song_key, song_comment=song_comment, user=u);
    u.song_count += 1

    u.put()
    su.put()

# Transactionally add mapping and increase user's song count
db.run_in_transaction(add_mapping, s.key(), 'Awesome', u.key())

# Increase song's user count (non-transactional)
s.user_count += 1
s.put()

问题是:如何在事务上管理这两个计数器?
根据我的理解,这是不可能的,因为User、Song和SongUser必须是同一entity group的一部分。它们不能在一个实体组中,因为这样我的所有数据都将在一个组中,并且不能由用户分发。

最佳答案

你真的不必担心处理用户在事务中评论的歌曲数量,因为用户似乎不太可能一次评论多首歌曲,对吧?
现在,肯定是这样的情况,许多用户可能会在同一时间评论同一首歌,所以这是你必须担心的地方,以确保数据不是由种族条件无效。
但是,如果您将对歌曲发表评论的用户数保留在song实体中,并用事务锁定该实体,则会对该实体产生很高的争用,并且数据存储超时将使您的应用程序遇到很多问题。
这个问题的答案是Sharded Counters
为了确保您可以创建一个新的SongUser实体并更新相关歌曲的sharded计数器,您应该考虑让SongUser实体将相关歌曲作为父级。这将把它们放在同一个实体组中,您可以在同一事务中创建SongUser并更新sharded计数器。SongUser与创建它的用户的关系可以保存在ReferenceProperty中。
关于您对两个更新(事务性更新和用户更新)都不成功的担心,这总是一种可能性,但是考虑到两个更新都可能失败,您需要进行适当的异常处理以确保两个更新都成功。这是很重要的一点:事务内更新不能保证成功。如果事务因任何原因无法完成,则可能会出现TransactionfailedError异常。
因此,如果您的事务在没有引发异常的情况下完成,请在事务中运行对用户的更新。如果发生错误,这将使您自动重试用户的更新。除非在用户实体上有一些我不理解的可能的争用,否则它最终不会成功的可能性是非常小的。如果这是一个不可接受的风险,那么我不认为AppEngine有一个完美的解决方案来解决这个问题。
首先扪心自问:如果有人评论的歌曲数被一个关掉,真的有那么糟糕吗?这和更新银行账户余额或完成股票销售一样重要吗?

关于python - 如何计算Google App Engine中多对多关系的双方,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2244850/

10-12 17:11