【问题标题】:Contention problems in Google App EngineGoogle App Engine 中的争用问题
【发布时间】:2015-10-03 20:18:07
【问题描述】:

我在 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"
    }
  >
  )

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

我不明白为什么会这样。查看我的代码中出现异常的行,我在一个完全不同的实体(Round)上运行get_by_id。错误消息中提到的“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 重试之后..?我在日志中看不到任何表明已进行任何重试的内容。

非常感谢任何帮助。

【问题讨论】:

  • 我会看看你的关键结构。争用不仅仅在实体层面。您还需要检查父母。查看您的实体组的范围,以便了解您为什么会出现争用。
  • 谢谢。我试图让实体组保持较小,并避免争用——但我会进行更多研究。在这种情况下,在与父ndb.Key("PlayerGameStates", "hannes2") 的实体组中发生了争用,对吧?我仍然不明白为什么从中读取会触发异常/争用?我在哪里可以阅读更多关于此的信息..?
  • @TimHoffman 好的,刚刚在Cross-group transactions documentation 下找到了这个:“注意:如果与访问该实体组的其他事务发生冲突,则在 XG 事务中第一次读取实体组可能会引发 TransactionFailedError 异常相同的实体组。这意味着即使是仅执行读取的 XG 事务也可能因并发异常而失败。"
  • 尝试构建您的应用程序以使用任务队列(您可以将任务过渡到队列)来更新根实体和/或使用分片。在大多数情况下,可以有独立实体的解决方案并通过任务队列更新聚合/​​根/邻居。在修改数据之前不要开始事务。这是一个很好的模式先读取实体,检查它是否需要修改,如果是则启动事务。

标签: python google-app-engine google-cloud-datastore app-engine-ndb contention


【解决方案1】:

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

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

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

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2011-09-08
    • 1970-01-01
    • 1970-01-01
    • 2010-11-30
    • 2014-01-12
    • 2013-10-25
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多