我有一个简单的域模型如下
Driver - key(string),run-count,unique-track-count
跟踪 - 键(字符串),运行计数,唯一驱动程序计数,最佳时间
运行 - 键(?),驱动程序键,跟踪键,时间,布尔驱动程序更新,布尔跟踪更新
我需要能够在同一个事务中更新Run和Driver;以及同一事务中的Run和Track(显然是为了确保我不会两次更新统计信息,或者错过增量计数器)
现在我尝试分配为运行键,一个由driver-key / track-key / run-key(string)组成的键
这将让我在一个事务中更新Run实体和Driver实体。
但是,如果我尝试一起更新“运行”和“跟踪”实体,它会抱怨它无法通过多个组进行交易。它说它在交易中既有司机也有卡车,它不能同时运作......
tx.begin(); run = pmf.getObjectById(Run.class, runKey); track = pmf.getObjectById(Track.class, trackKey); //This is where it fails; incrementCounters(); updateUpdatedFlags(); tx.commit();
奇怪的是,当我做类似的事情来更新Run和Driver时,它运行正常。
有关我如何映射域模型以实现相同功能的任何建议?
答案 0 :(得分:0)
使用Google App Engine,all of the datastore operations must be on entities in the same entity group。这是因为您的数据通常存储在多个表中,而Google App Engine无法跨多个表执行交易。
拥有一对一和一对多关系的实体are automatically in the same entity group。因此,如果实体包含对另一个实体或实体集合的引用,则可以在同一事务中读取或写入两者。对于没有所有者关系的实体,您可以创建具有显式实体组父实体的实体。
您可以将所有对象放在同一个实体组中,但如果有太多用户试图同时修改实体组中的对象,则可能会出现争用。如果每个对象都在其自己的实体组中,则无法执行任何有意义的事务。你想在两者之间做点什么。
一种解决方案是在同一实体组中进行跟踪和运行。您可以通过让Track包含运行列表来执行此操作(如果执行此操作,则Track可能不需要运行计数,唯一驱动程序计数和最佳时间;可以在需要时计算它们)。如果您不希望Track有一个运行列表,您可以使用unowned one-to-many relationship并指定Run为其Track的实体组parent(请参阅this page上的“使用实体组创建实体”) 。无论哪种方式,如果Run与其轨道位于同一实体组中,您可以执行涉及Run及其部分/全部轨道的事务。
对于许多大型系统而言,不是使用事务来保持一致性,而是通过进行idempotent的操作来完成更改。例如,如果驱动程序和运行不在同一个实体组中,您可以通过首先执行查询以获取过去某个日期之前的所有运行的计数来更新驱动程序的运行计数,然后在事务中,使用新计数和上次计算的日期更新驱动程序。
在使用日期时请记住,机器可以有某种时钟漂移,这就是为什么我建议使用过去的日期。
答案 1 :(得分:0)
我认为我找到了一个横向但仍然干净的解决方案,在我的域模型中仍然有意义。
域模型略有变化,如下所示:
Driver - key(string-id),driver-stats - ex。 id =“Michael”,run = 17
Track - key(string-id),track-stats - ex。 id =“Monza”,bestTime = 157
RunData - key(string-id),stat-data - ex。 id =“Michael-Monza-20101010”,时间= 148
TrackRun - 键(Track / string-id),track-stats-updated - ex。 id =“Monza / Michael-Monza-20101010”,track-stats-updated = false
DriverRun - 键(Driver / string-id),driver-stats-updated - ex。 id =“Michael / Michael-Monza-20101010”,driver-stats-updated = true
我现在可以立即或在我自己的时间以原子方式(即精确地)更新Track的统计信息和Run中的统计信息。 (与Driver / Run统计数据相同)。
所以基本上我必须以非传统的关系方式扩展我对问题进行建模的方式。你觉得怎么样?
答案 2 :(得分:0)
意识到这已经很晚了,但是......
您是否看到过银行帐户转帐的这种方法? http://blog.notdot.net/2009/9/Distributed-Transactions-on-App-Engine
在我看来,你可以通过将增量计数器分成两个步骤作为IncrementEntity来处理类似的事情,然后在事务失败等情况下获取部分。
来自博客:
- 在交易中,扣除所需的费用 来自付款帐户的金额,以及 创建一个Transfer子实体 记录这一点,指定接收 帐户在'目标'字段中,和 将“其他”字段留空 现在。
- 在第二个交易中,添加 接收所需的金额 帐户,并创建一个转移子 实体记录这个,指定 在“目标”字段中付款, 和创建的转移实体 “其他”字段中的第1步。
- 最后, 更新在中创建的转移实体 步骤1,将“其他”字段设置为 我们在第2步中创建的转移。
醇>
该博客在Python中有代码示例,但应该很容易适应
答案 3 :(得分:0)
关于此主题http://www.google.com/events/io/2010/sessions/high-throughput-data-pipelines-appengine.html
,有一个有趣的google io会话我想你可以更新Run stats然后激活两个任务来单独更新Driver和Track。