如何在 Google App Engine 中计算多对多关系的双方

Posted

技术标签:

【中文标题】如何在 Google App Engine 中计算多对多关系的双方【英文标题】:How to count both sides of many-to-many relationship in Google App Engine 【发布时间】:2011-01-15 17:39:03 【问题描述】:

考虑一个允许用户评论歌曲的 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 的一部分。它们不能在一个实体组中,因为这样我的所有数据都将在一个组中,并且不能由用户分发。

【问题讨论】:

【参考方案1】:

您真的不必担心处理用户在交易中评论过的歌曲数量,因为用户似乎不可能一次评论超过一首歌曲,对吧?

现在,肯定有许多用户可能同时评论同一首歌曲,因此您必须担心确保数据不会因竞争条件而无效。

但是,如果您在 Song 实体中保留对歌曲发表评论的用户数量,并使用事务锁定该实体,您将获得对该实体的非常高的争用,并且数据存储超时将导致你的应用程序有很多问题。

这个问题的答案是Sharded Counters。

为了确保您可以创建新的 SongUser 实体并更新相关 Song 的分片计数器,您应该考虑让 SongUser 实体将相关 Song 作为父级。这会将它们放在同一个实体组中,您可以在同一个事务中创建 SongUser 并更新分片计数器。 SongUser 与创建它的用户的关系可以保存在 ReferenceProperty 中。

关于您对两个更新(事务更新和用户更新)并非都成功的担忧,这总是有可能的,但鉴于任何一个更新都可能失败,您需要进行适当的异常处理以确保两者成功。这是重要的一点:事务中更新不能保证成功。如果交易因任何原因无法完成,您可能会收到 TransactionfailedError 异常。

因此,如果您的事务在没有引发异常的情况下完成,请在事务中运行对用户的更新。如果发生某些错误,这将使您自动重试对用户的更新。除非我不理解用户实体上可能存在的争用,否则它最终不会成功的可能性非常小。如果这是一个不可接受的风险,那么我认为 AppEngine 没有为您解决这个问题的完美解决方案。

首先问问自己:如果有人评论的歌曲数量减少了一首,真的那么糟糕吗?这与更新银行账户余额或完成股票销售一样重要吗?

【讨论】:

您的解决方案减少了争用,但我真正想做的是确保两个计数器都匹配底层的SongUser 记录。如果我为 Song 实体使用分片计数器,我仍然可以在创建 SongUser 成功并且增加歌曲的计数器失败(反之亦然)时遇到这种情况。 我认为您最后一段中的解决方案可能是 GAE 限制内的最佳选择。在该解决方案中,我们翻转了我的第一条评论中的示例。例如,现在有可能更新/创建歌曲计数器和 SongUser 记录,但用户记录更新失败(反之亦然)。您是否同意以事务方式更新两个计数器(分片或不分片)是不可能的? 根据您的评论更新了我的答案 我想不问这个关于歌曲 cmets 之类的琐碎问题会更公平。我同意让用户的计数可能减少一些并不重要。我真的只是想了解我能用 GAE 做什么和不能做什么。如果我问的是关于银行余额的问题,那么得到“否”的答案会容易得多。当然,还有很多其他原因不使用 GAE 进行银行交易;)

以上是关于如何在 Google App Engine 中计算多对多关系的双方的主要内容,如果未能解决你的问题,请参考以下文章

探索Google App Engine背后的奥秘- Datastore的设计

如何在 Google App Engine app.yaml 中处理尾部斜线

如何在 Google App Engine 中执行全文搜索?

如何在 Django/Google App Engine 中制作日志颜色?

如何在 Google App Engine 标准环境中使用 Google Cloud Build 或其他方法设置环境变量?

如何在 Google App Engine 中使用 sbt?