GAE实体组/数据建模,以确保一致性和性能

时间:2013-12-31 01:49:28

标签: python entity-framework google-app-engine data-structures google-cloud-datastore

作为in this post的延续,这是一个顶点式问题,可以巩固我对的理解,并对我的数据建模决策提出一些批评。我将修改由@Jimmy Kane创建的Jukebox示例,以更好地反映我的真实案例。

在原始设置中,


想象一下,每个房间都有一个带有队列的自动点唱机。人们将歌曲排队到每个点唱机的每个队列。

J=Jukebox, Q=queue, S=Song

   Jukebox       
   / |   \        
  Q1 Q2   Q3     
 / |  \   | \
S1 S2  S3 S4 S5

首先,填写Song模型:

Song(ndb.Model):
    user_key = ndb.KeyProperty()
    status = ndb.StringProperty()
    datetime_added = ndb.DateTimeProperty()

我的修改是添加一个可以将CUD歌曲添加到任何队列的User。在前端,用户将访问UI以在每个队列中查看他们的歌曲,并进行更改。在后端,应用程序需要知道每个队列中的哪些歌曲,从每个队列中播放正确的歌曲,并在播放后从队列中删除歌曲。

为了让用户能够在队列中看到它的歌曲,我假设每个用户都是一个根实体,并且需要存储一个歌曲键列表

User(ndb.Model):
    song_keys = ndb.KeyProperty(kind='Song', repeated=True)

然后,要检索用户的歌曲,应用程序将(假设user_id已知)

user = User.get_by_id(user_id)
songs = ndb.get_multi(user.song_keys)

而且,由于get非常一致,用户总能看到非陈旧数据

然后,当队列1完成播放歌曲时,应用程序可以执行以下操作:

current_song.status = "inactive"
current_song.put()
query=Song.query(ancestor=ndb.Key('Jukebox', '1', 'Queue', '1')).filter(Song.status=="active").order(Song.datetime_added)
next_song = query.get()

我是否正确地认为祖先查询确保了当前歌曲的前一次停用以及来自用户的任何CUD的一致表示?

最后一步是在事务中更新用户的song_keys列表

user = current_song.user_key.get()
user.song_keys.remove(current_song.key)
user.put()

摘要和一些优点/缺点

  • 一致性似乎是在正确的位置做正确的事情 如果我的理解是对的?
  • 我应该关注Jukebox实体组的争用吗?
    • 我不希望它是高吞吐量类型的用例,但我的现实场景需要根据用户数量进行扩展,并且可能存在类似数量的queue s { {1}} s,可能比user多2倍 - 5倍user秒。如果整个组的写入/秒限制为1,并且许多用户以及每个队列都可以创建和更新歌曲,这可能是一个瓶颈
    • 一种解决方案可能是取消queue根实体并让每个Jukebox成为自己的根实体
  • Queue可能很长,比如100 User.song_keys s。 This article建议“避免在ListProperty中存储过多的密钥列表”。这里有什么问题?这是一个db概念,并且使用ndb处理具有song.key属性选项的列表的方式没有实际意义吗?

对这种做法的看法或对我从根本上误解的事情的批评?

  • 据推测,我也可以选择对称翻转 数据模型并具有看起来像repeated=True的实体组 - > User模型
  • 中存储了Songsong_keys个列表

2 个答案:

答案 0 :(得分:5)

我认为你应该重新考虑一下你的用例的强一致性有多重要。从我所看到的,所有这些实体都具有很强的一致性并不重要。在我看来,最终的一致性会很好。大多数情况下,您会看到最新数据,有时只读(真的很少),您会看到一些陈旧的数据。想想你总能获得最新数据与惩罚应用程序的程度有多重要。需要强一致性的实体不会以每秒读取次数的最有效方式存储。

另外,如果您查看文档Structuring Data for Strong Consistency,您会看到它提到您在使用该方法时每秒不能超过1次写入。

根据AppEngine Model Class docs,实体组也会影响数据位置。

如果您还在Google Spanner第2部分阅读了着名的Google文档,您将看到它们如何处理具有相同父键的实体。从本质上讲,它们更紧密地联系在一起。我假设Google可能会使用与AppEngine Datastore类似的方法。在某些时候,根据this来源,Google可能会在未来使用Spanner for AppEngine Datastore。

另一点,没有更便宜的更快获得然后通过键获得。话虽如此,如果你能以某种方式避免查询,这可以降低运行应用程序的成本。假设您正在开发Web应用程序,您可以将歌曲密钥存储在JSON /文本对象中,然后使用Prospective Search API获取最新结果。这种方法需要更多的工作,并且需要您接受最终的一致性模型,因为数据到达客户端时可能会略微过时。根据您的使用情况(这显然不适用于小型应用程序和小型用户群),节省的成本可能会超出成本。当我说成本时,我的意思是数据可能会略微过时。

根据我的经验,强大的一致性不是大量应用程序的要求。可以使用稍微过时的数据的应用程序数量似乎超过了不能使用的应用程序数量。以YouTube为例,如果我没有立即看到所有视频,我真的不介意(因为有这么大的数字,如果我看到所有这些视频,我甚至都不知道)。当你设计这样的东西时,首先问自己一个问题,是否真的有必要提供最新的数据或一些陈旧的数据是否足够好?用户甚至可以分辨出来吗?最新的数据要贵得多,而且有点陈旧。

答案 1 :(得分:2)

我决定采用另一种方法,即除了用户之外,还要依赖队列中的song_keys列表。这样,我在处理用户和队列时具有很强的一致性,而无需处理实体组带来的性能/一致性权衡。作为一个积极的副产品,get ting keys利用ndb自动缓存,因此我期望通过增强代码简单性来提升性能。

仍欢迎任何批评......

UDPATE:关于自动缓存的更多细节。 NDB automatically manages caching通过内存缓存和上下文缓存。出于我的目的,我最感兴趣的是自动内存缓存。通过主要使用get请求支持查询,NDB将首先检查memcache,然后从数据存储区读取所有这些读取。我预计大多数请求实际上都是通过memcache而不是数据存储来实现的。我知道我可以自己管理所有这些memcache活动,并且最有可能采用以查询为中心的方法正常工作,因此也许有些人不会认为这是设计决策的一个重要原因。但是对代码简单性的影响非常好。