我的应用程序中有Course
s,Topic
和Tag
s。每个Topic
可以在多个Course
中,并且有许多Tag
个。我想查找具有特定Topic
x并且在特定Tag
y中的每个Course
。
天真地,我为每个标准提供了Course
ID和Tag
ID的列表,因此我可以select * from Topic where tagIds = x && courseIds = y
。我认为这个查询需要一个爆炸性的索引:有30个课程和30个标签,我们正在查看~900个索引条目,对吧?在50 x 20时,我远远超过了5000个限制。
我可以select * from Topic where tagIds = x
,然后使用for循环浏览结果,只选择Topic
的{{1}}。这会返回比我感兴趣的结果更多的结果,并花费大量时间反序列化这些结果,但索引仍然很小。
我可以courseIds.contain(y)
和select __KEY__ from Topic where tagIds = x
在我的应用程序代码中查找交集。如果集很小,这可能不合理。
我可以使用select __KEY__ from Topic where courseIds = y
和TopicTagLookup
字段创建一种联接表tagId
。这些实体的父键将指向相关的courseId
。然后我需要为courseId x tagId x相关主题id的每个组合制作其中一个Topic
实体。这实际上就像创建自己的索引一样。它仍然会爆炸,但没有5000入口限制。但是,现在我需要将5000个实体写入同一个实体组,这将违背实体组写入速率限制!
我可以预先计算每个查询。 TopicTagLookup
实体会包含TopicTagQueryCache
,tagId
和courseId
。然后查询看起来像List<TopicId>
,获取主题ID列表,然后在列表中使用select * from TopicTagQueryCache where tagId=x && courseId = y
调用。与#3类似,但每个courseId x tagId只有一个实体。不需要实体组,但现在我有这个可能很大的列表来维护交易。
Appengine似乎非常适合您可以预先计算的查询。我只是没有找到一种方法来有效地预先计算这个查询。问题基本归结为:
组织数据的最佳方式是什么,以便我们可以执行设置操作,例如在getAllById
和Topic
的交集中查找Course
?
答案 0 :(得分:2)
您对选项的评估是正确的。但是,如果您不需要任何排序条件,则App Engine数据存储区已使用合并连接策略或多或少地为您完成了选项3。只需在选项1中详细查询,不需要任何排序或不等式过滤器,App Engine将在数据存储区内部进行合并连接,并仅返回相关结果。
选项4和5类似于this talk中记录的关系索引模式。
答案 1 :(得分:1)
我喜欢#5 - 你实际上是在创建自己的(爆炸式)索引。查询速度很快。
唯一的缺点是您必须手动维护它(下一段),并且检索Topic
实体将需要额外的查询(首先您查询TopicTagQueryCache
以获取主题ID然后您需要实际检索主题。)
更新您建议的TopicTagQueryCache
也不应该是个问题。我不担心会以交易方式进行 - 当你更新Topic
时,这个“索引”只会在短时间内失效(最糟糕的是,你的Topic
会暂时显示在结果中应该不再出现,并且可能需要花一点时间才显示出它应该出现的新结果 - 这似乎并不那么糟糕)。您甚至可以在任务队列上执行此更新(以确保这些潜在的大量数据库写入都成功,以便您可以快速完成请求,以便您的用户不会等待。)
答案 2 :(得分:0)
正如你自己所说,你应该安排你的数据,以方便你的应用程序的扩展,因此在组织数据的最佳方式是什么,这样我们就可以进行设置操作,如在交叉点找到主题课程和标签?
您可以通过创建CourseRef和TopicRef的对象来保存这些集合的索引,这些对象仅包含Key,ID部分是相应实体的实际Key。这些“Ref”实体将位于特定标记下,因此没有实际的Key重复。所以给定标签的结构是: Tag \ CourseRef ... \ TopicRef ...
通过这种方式给定标签和课程,您可以构建Key Tag \ CourseRef并执行ancestor Query,它可以获取一组您可以获取的密钥。这非常快,因为它实际上是一个直接访问,这应该处理大型课程或主题列表而不会出现List属性的问题。
此方法将要求您在某种程度上使用DataStore API。 正如您所看到的,这给出了一个特定问题的答案,而且该模型对其他类型的Set操作没有任何好处。