考虑一个允许用户评论歌曲的GAE(python)应用程序。预计用户数为1,000,000+。预期的歌曲数量为5,000。
应用必须能够:
计数器管理必须是交易性的,以便它们始终反映基础数据。
似乎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='me@example.com')
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的一部分。它们不能在一个实体组中,因为那时我的所有数据都在一个组中,并且用户无法分发它。
答案 0 :(得分:1)
你真的不必担心处理用户在交易中评论的歌曲数量,因为用户似乎不太可能一次评论多首歌曲,对吧? / p>
现在,很多用户肯定会同时评论同一首歌,因此您必须担心确保数据不会因竞争条件而无效。
但是,如果您保留对Song实体内的歌曲进行评论的用户数量,并使用事务锁定该实体,那么您将获得该实体的非常高的争用,并且数据存储区超时将使你的申请有很多问题。
这个问题的答案是Sharded Counters。
为了确保您可以创建新的SongUser实体并更新相关歌曲的分片计数器,您应该考虑让SongUser实体将相关歌曲作为父级。这将把它们放在同一个实体组中,你可以创建SongUser并在同一个事务中更新分片计数器。 SongUser与创建它的用户的关系可以保存在ReferenceProperty中。
关于您对两个更新(事务性更新和用户更新)的关注并非都成功,这始终是可能的,但鉴于任一更新都可能失败,您将需要进行适当的异常处理以确保两者都有成功。这是一个重点:交易中更新不能保证成功。如果由于任何原因无法完成事务,您可能会收到TransactionfailedError异常。
因此,如果您的事务在没有引发异常的情况下完成,请在事务中运行对User的更新。如果发生某些错误,这将使您自动重试用户的更新。除非我不理解用户实体可能存在的争用,否则它最终无法成功的可能性超级小。如果这是一个不可接受的风险,那么我认为AppEngine并不能为您解决这个问题。
首先问问自己:如果有人评论过的歌曲的数量被一个人拒绝,那真是那么糟糕吗?这对于更新银行账户余额或完成股票销售至关重要吗?