Google 应用引擎:使用 属性 值作为键

Google app engine: using a property value as a key

我有下一个实体:

class Player(ndb.Model):
    player_id = ndb.IntegerProperty()

class TimeRecord(ndb.Model):
    time = ndb.StringProperty()

所以 TimeRecord 的实例是某个 Player 实例的子实例。 如果我需要将 TimeRecord 的实例与某个播放器放在一起,我会这样做:

tr = TimeRecord(parent = ndb.Key("Player", Player.query(Player.player_id == int(certain_id)).get().key.integer_id()), time = value)

此查询既昂贵又复杂。据此doc

qry = Account.query(Account.userid == 42)

如果您确定只有一个帐户具有该用户 ID,您可能更愿意使用用户 ID 作为密钥。 Account.get_by_id(...) 比 Account.query(...).get().

据我所知,我需要更改数据存储的结构:

使用player_id作为Player的key,将TimeRecord(时间)移动到Players的属性。 player_id 是唯一值。

class Player(ndb.Model):
    time = ndb.StringProperty()

问:这是正确的做法吗?

这类似于混合不同级别的实体继承,因为我认为每个数据都应该是不同的实体。并通过祖先键实现继承。

更新: 但在这种情况下,我可以为某个播放器只存储一个 TimeRecord 值。 我需要一组播放器的 TimeRecords。 属性这个问题有重复解法吗?

从关系数据库用户的角度来看,您提议的重新设计本质上是 "de-normalization" -- 这在关系领域几乎是一个坏词,但绝对是 "normal" (哈哈)一旦你进入NoSQL。

如果您知道将如何查询和更新事物,反规范化会提高性能(通常)and/or 存储(有时),但会牺牲一些灵活性。

不过,请注意权衡取舍。通常,反规范化会提高 querying/reading 中的性能,但会增加更新的额外负担——这可能很好,因为通常读取比写入更频繁,但您需要知道 [= 是否属于这种情况39=]你的申请。

检查您的具体用例,我发现存储空间明显节省(特别是如果您可以使用更专业的类型 属性,请参阅 https://cloud.google.com/appengine/docs/python/ndb/properties#Date_and_Time)并且与后端的交互更少(因此性能更好)在检索上。它还可以简化您的代码(简单是好的:错误风险更少,更容易进行单元测试)。

但是,如果保存新的"time records"是一个玩家非常频繁的需求,重复的属性会变得越来越大(在某些时候这尽管它仍然是一个单一的交互,但它会减慢速度;最坏的情况下,它会 "bump its head" 反对单个实体的最大大小,即 1 兆字节——当然,这将需要每个玩家数万 "time records" ,但是,完全不了解您的应用程序, 无法判断这是否存在风险...只有 可以!-).

查询也可能是个问题,同样完全取决于您的应用程序需要什么。我特别想到不平等查询。假设您需要所有时间记录大于 '20141215-10:00:00' 且小于 '20141215-18:00:00' 的玩家。 las,对重复 属性 的不等式查询不会为您做那件事!也就是说,如果您查询

ndb.AND(Player.time > '20141215-10:00:00',
        Player.time < '20141215-18:00:00')

你会得到时间大于第一个常量和时间小于第二个常量的玩家——不一定是 相同 时间!这意味着查询可能 return 比你希望的多得多的玩家,你需要 "filter" 在你的应用程序代码中得到的玩家群。

如果您有一个实体,其中 time 不是 重复的 属性(例如您原来的 TimeRecord 实体),那么与此类似的查询将 return 正是感兴趣的一堆实体(尽管如果您随后需要获取那些时间的 玩家 ,则您需要与存储后端,通常是 ndb.get_multi,因此如果不了解 更多 应用程序的操作参数,就很难预测性能影响!)。

这就是去规范化通常归结为:"desirability" 不同方面之间的权衡(简单性、存储节省、更少的后端交互、更少量的数据进入 to/from 后端 - - 我们甚至没有涉及原子事务和异步技术的适用性!-) - 只有对应用程序的操作参数有深入了解才能做出权衡。

确实可能值得部署两个或更多原型,每个原型都针对一小组用户,以获得关于它们如何执行的实际 数据(新的云监控产品可以提供帮助使用 "get actual data" 部分),在选择 "definitive"(哈哈!)架构之前——尽管将数据从原型迁移到 "definitive" 模式会产生开销。

如果该应用程序一夜成名,突然间您每秒收到数万个查询,而不是您计划的数量级减少,性能特征可能会突然改变到一定程度重新架构和再次迁移的痛苦可能是有道理的(当然,这是一个好问题,但仍然......)。