5

我在 Google App Engine 中遇到了争用问题,并尝试了解发生了什么。

我有一个带有注释的请求处理程序:

@ndb.transactional(xg=True, retries=5) 

..在该代码中,我获取了一些东西,更新了其他一些东西等。但有时在请求期间日志中会出现这样的错误:

16:06:20.930 suspended generator _get_tasklet(context.py:329) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
path <
  Element {
    type: "PlayerGameStates"
    name: "hannes2"
  }
>
)
16:06:20.930 suspended generator get(context.py:744) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
  path <
    Element {
      type: "PlayerGameStates"
      name: "hannes2"
    }
  >
  )
16:06:20.930 suspended generator get(context.py:744) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
  path <
    Element {
      type: "PlayerGameStates"
      name: "hannes2"
    }
  >
  )
16:06:20.936 suspended generator transaction(context.py:1004) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
  path <
    Element {
      type: "PlayerGameStates"
      name: "hannes2"
    }
  >
  )

..接着是堆栈跟踪。如果需要,我可以更新整个堆栈跟踪,但它有点长。

我不明白为什么会这样。查看我的代码中出现异常的行,我get_by_id在一个完全不同的实体(Round)上运行。错误消息中提到的“PlayerGameStates”,名称“hannes2”是另一个实体 GameState 的父级,该实体已get_async在前几行从数据库中 :ed;

# GameState is read by get_async
gamestate_future = GameState.get_by_id_async(id, ndb.Key('PlayerGameStates', player_key))
...
gamestate = gamestate_future.get_result()
...

奇怪(?)的事情是,该实体没有写入数据存储区。我的理解是,如果同一实体同时更新,可能会出现争用错误,并行..或者如果在短时间内发生太多写入..

但是在读取实体时也会发生这种情况吗?("suspended generator get.."??) 而且,这是否发生在 5 次 ndb.transaction 重试之后..?我在日志中看不到任何表明已进行任何重试的内容。

任何帮助是极大的赞赏。

4

1 回答 1

8

是的,读写操作都可能发生争用。

在事务开始后 - 在您的情况下,当@ndb.transactional()调用带有注释的处理程序时 - 任何访问的实体组(通过读取或写入操作,无关紧要)都会立即被标记为这样。在那一刻,不知道在事务结束时是否会有写操作 - 这甚至都没有关系。

太多争用错误(这与冲突错误不同!)表示太多并行事务同时尝试访问同一个实体组。即使没有任何事务实际尝试写入,它也可能发生!

注意:这个争用不是由开发服务器模拟的,只有在 GAE 上部署时才能看到,使用真实的数据存储!

可能会增加混乱的是事务的自动重试,这可能发生在实际的写入冲突或只是简单的访问争用之后。这些重试在最终用户看来可能是某些代码路径的可疑重复执行 - 在您的情况下是处理程序。

重试实际上会使事情变得更糟(在很短的时间内)——在已经被大量访问的实体组上抛出更多的访问——我已经看到这种模式,只有在指数退避延迟增长到足以让事情冷却一点之后,事务才起作用(如果重试次数足够大)通过允许已经在进行中的事务完成。

我的方法是在推送队列任务上移动大部分事务性内容,在事务和任务级别禁用重试,而是完全重新排队任务 - 重试次数更少但间隔更远。

通常,当您遇到此类问题时,您必须重新访问您的数据结构和/或您访问它们的方式(您的事务)。除了保持强一致性的解决方案(这可能非常昂贵)之外,您可能还需要重新检查一致性是否真的是必须的。在某些情况下,它被添加为一揽子要求只是因为它似乎简化了事情。根据我的经验,它没有:)

另一件事可以帮助(但只有一点)是使用更快(也更昂贵)的实例类型 - 更短的执行时间转化为事务重叠的风险略低。我注意到了这一点,因为我需要一个具有更多内存的实例,而这恰好也更快:)

于 2017-08-03T23:24:44.970 回答