Google App Engine中的争用问题

bof*_*man 4 python google-app-engine contention app-engine-ndb google-cloud-datastore

我在Google App Engine中遇到了争用问题,并尝试了解正在发生的事情.

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

@ndb.transactional(xg=True, retries=5) 
Run Code Online (Sandbox Code Playgroud)

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

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"
    }
  >
  )
Run Code Online (Sandbox Code Playgroud)

..跟随堆栈跟踪.如果需要,我可以使用整个堆栈跟踪进行更新,但它有点长.

我不明白为什么会这样.查看我的代码中的行,异常来了,我运行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()
...
Run Code Online (Sandbox Code Playgroud)

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

但是在阅读实体时也会发生这种情况吗?("暂停生成器获取.."??)并且,这是在5 ndb.transaction重试之后发生的吗?我在日志中看不到任何表明已经进行任何重试的内容.

任何帮助是极大的赞赏.

Dan*_*scu 6

是的,读取和写入操作都会发生争用.

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

过多的争用错误(与冲突错误不同!)表示太多并行事务同时尝试访问同一实体组.即使没有任何事务真正尝试写,也会发生这种情况!

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

可以增加混淆的是自动重新尝试事务,这可能发生在实际写入冲突或只是普通访问争用之后.这些重试可能对最终用户显示为某些代码路径的可疑重复执行 - 在您的情况下处理程序.

重试实际上可以使事情变得更糟(在短时间内) - 在已经被大量访问的实体组中投入更多访问 - 我已经看到这样的模式,只有在指数退避延迟变得足够大以使事情变得冷却之后交易才起作用(如果重试次数足够大,则允许正在进行的事务完成.

我的方法是将大部分事务处理事务移到推送队列任务上,在事务和任务级别禁用重试,而是完全重新排队任务 - 更少的重试但间隔更远.

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

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