Google App Engine 中的争用问题
Contention problems in Google App Engine
我在 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
: 在几行前从数据库中编辑;
# 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.transactional()
注释的处理程序时 - 访问的任何实体组(通过读或写操作,无关紧要)立即被标记为这样。那时还不知道在事务结束时是否会有写操作——这甚至都不重要。
争用错误太多(与冲突错误不同!)表示同时尝试访问同一实体组的并行事务过多。即使 none 的交易实际尝试写入,它也会发生!
注意:这个争用不是开发服务器模拟的,只有部署在GAE上才能看到,真正数据存储!
事务的自动重试可能会增加混乱,这可能发生在实际写入冲突或只是简单的访问争用之后。这些重试在最终用户看来可能是某些代码路径的可疑重复执行 - 在您的案例中是处理程序。
重试实际上会使事情变得更糟(在短时间内)——在已经被大量访问的实体组中抛出更多的访问——我见过这样的模式,只有在指数退避延迟增长到足以让通过允许已经在进行中的事务完成,事情会变得更酷(如果重试次数足够大)。
我的方法是将大部分事务性内容移动到推送队列任务上,在事务和任务级别禁用重试,而是完全重新排队任务 - 重试次数更少但间隔更远。
通常当您 运行 遇到此类问题时,您必须重新访问您的数据结构 and/or 您访问它们的方式(您的事务)。除了保持强一致性(这可能非常昂贵)的解决方案之外,您可能需要重新检查一致性是否真的是必须的。在某些情况下,它被添加为一揽子要求只是因为似乎可以简化事情。根据我的经验,它不会:)
另一件事可以帮助(但只有一点点)是使用更快(也更昂贵)的实例类型 - 更短的执行时间转化为交易重叠的风险略低。我注意到这一点,因为我需要一个具有更多内存的实例,它恰好也更快 :)
我在 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
: 在几行前从数据库中编辑;
# 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.transactional()
注释的处理程序时 - 访问的任何实体组(通过读或写操作,无关紧要)立即被标记为这样。那时还不知道在事务结束时是否会有写操作——这甚至都不重要。
争用错误太多(与冲突错误不同!)表示同时尝试访问同一实体组的并行事务过多。即使 none 的交易实际尝试写入,它也会发生!
注意:这个争用不是开发服务器模拟的,只有部署在GAE上才能看到,真正数据存储!
事务的自动重试可能会增加混乱,这可能发生在实际写入冲突或只是简单的访问争用之后。这些重试在最终用户看来可能是某些代码路径的可疑重复执行 - 在您的案例中是处理程序。
重试实际上会使事情变得更糟(在短时间内)——在已经被大量访问的实体组中抛出更多的访问——我见过这样的模式,只有在指数退避延迟增长到足以让通过允许已经在进行中的事务完成,事情会变得更酷(如果重试次数足够大)。
我的方法是将大部分事务性内容移动到推送队列任务上,在事务和任务级别禁用重试,而是完全重新排队任务 - 重试次数更少但间隔更远。
通常当您 运行 遇到此类问题时,您必须重新访问您的数据结构 and/or 您访问它们的方式(您的事务)。除了保持强一致性(这可能非常昂贵)的解决方案之外,您可能需要重新检查一致性是否真的是必须的。在某些情况下,它被添加为一揽子要求只是因为似乎可以简化事情。根据我的经验,它不会:)
另一件事可以帮助(但只有一点点)是使用更快(也更昂贵)的实例类型 - 更短的执行时间转化为交易重叠的风险略低。我注意到这一点,因为我需要一个具有更多内存的实例,它恰好也更快 :)