构建 NDB 模型以使 "antijoin" 查询成为可能

Structuring NDB models to make an "antijoin" query possible

我想为我的应用程序提供建议 API,用户可以在其中获得对他们 尚未 尚未见过的对象的建议,而我无法弄清楚我应该如何构建我的数据以使这样的查询高效。

这是一个使用书籍的例子。假设这是我的模型,最规范化的方式:

def User(ndb.Model):
  name = ndb.StringProperty()

def Book(ndb.Model):
  title = ndb.StringProperty()

def Review(ndb.Model):
  user = ndb.KeyProperty(User)
  book = ndb.KeyProperty(Book)
  stars = ndb.IntegerProperty()
  text = ndb.TextProperty()

现在给定一个用户,我想检索一本用户没有评论过的书,这似乎基本上不可能高效和大规模地完成(例如 50k 用户,100k 本书)。

我看了一圈,我意识到我应该以某种方式对我的数据进行非规范化,但对于我来说,我想不出一个好的方法来做到这一点。我考虑过将 Review 作为 StructuredProperty 放在 Book 中,但我认为这对我来说意义不大,这意味着我会受到限制我可以添加到一本书中的评论数量(由于条目的大小限制)。

当其他人问类似问题时,我看到的其他事情经常提到的是祖先和 ComputedProperty,但我也没有真正看到他们在这里如何帮助我。

当然也不是不可能,只是我对最佳实践的理解很薄弱,对吧?

一个有用的反规范化可能是添加到 User 他们评论过的书籍列表:

def User(ndb.Model):
  name = ndb.StringProperty()
  seen = ndb.KeyProperty('Book', repeated=True)

和 'Book' 整体 "score" 您将要订购的查询:

def Book(ndb.Model):
    title = ndb.StringProperty()
    score = ndb.IntegerProperty()

像往常一样,当 撰写 时,反规范化的成本就会出现——除了创建新评论之外,您还需要更新 UserBook 实体(你可能需要一个事务,因此,实体组,如果多个用户可能同时评论一本书,但我跳过那部分:-)。

优点是,当需要向给定用户推荐一本新书时,您可以查询 Book(仅键,按分数排序),使用游标(或仅在查询上循环)以"page through" 查询的结果,并拒绝那些已经存在于给定用户 seen 属性.

内存中的键

为此目的获取用户实体后,您可以将 seen 变成 set,因此检查速度会非常快。这假设用户不会评论超过几千本书,所以需要的一切应该很好地适合内存...