Problemas de contención en Google App Engine

Estoy teniendo problemas de contención en Google App Engine, y trato de entender lo que está pasando.

Tengo un gestor de solicitudes anotado con:

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

..y en ese código consigo algunas cosas, actualizo algunas otras, etc. Pero a veces aparece un error como este en el registro durante una solicitud:

 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  ) 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  ) 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  ) 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  ) 

.. seguido por un rastro de stack. Puedo actualizar con todo el seguimiento de la stack si es necesario, pero es un poco largo.

No entiendo por qué sucede esto. Mirando la línea en mi código allí viene la excepción, ejecuto get_by_id en una entidad totalmente diferente (Round). El “PlayerGameStates”, nombre “hannes2” que se menciona en los mensajes de error es el padre de otra entidad GameState, que ha sido get_async : ed desde la base de datos unas pocas líneas antes;

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

Lo extraño (?) Es que no hay escrituras en el almacén de datos que ocurren para esa entidad. Tengo entendido que los errores de contención pueden venir si la misma entidad se actualiza al mismo tiempo, en paralelo. O tal vez si se producen demasiadas escrituras, en un corto período de tiempo.

¿Pero puede suceder al leer entidades también? (“¿Generador suspendido …”?) ¿Y esto sucede después de los 5 bashs de transacción ndb.transaction ..? No puedo ver nada en el registro que indique que se hayan realizado rebashs.

Cualquier ayuda es muy apreciada.

Sí, la contención puede ocurrir tanto para operaciones de lectura como de escritura.

Después de que se inicie una transacción, en su caso cuando se invoca el controlador anotado con @ndb.transactional() , cualquier grupo de entidades al que se acceda (mediante operaciones de lectura o escritura, no importa) se marca inmediatamente como tal. En ese momento no se sabe si al final de la transacción habrá una operación de escritura o no, ni siquiera importa.

El error de contención demasiado (que es diferente de un error de conflicto) indica que demasiadas transacciones paralelas intentan acceder al mismo grupo de entidades simultáneamente. ¡Puede suceder incluso si ninguna de las transacciones realmente intenta escribir!

Nota: esta contención NO es emulada por el servidor de desarrollo, solo se puede ver cuando se implementa en GAE, con el almacén de datos real.

Lo que puede agregar a la confusión son los rebashs automáticos de las transacciones, que pueden suceder después de conflictos de escritura reales o simplemente de la disputa de acceso simple. Estos rebashs pueden parecerle al usuario final como una ejecución repetida sospechosa de algunas rutas de código, el controlador en su caso.

Los rebashs en realidad pueden empeorar la materia (por un breve tiempo) – lanzando aún más accesos a los grupos de entidades a los que ya hemos accedido – He visto que estos patrones con transacciones solo funcionan después de que los retrasos de retroceso exponenciales se vuelven lo suficientemente grandes como para dejar que las cosas se enfríen un poco ( si el número de rebashs es lo suficientemente grande) al permitir que se completen las transacciones en curso.

Mi enfoque para esto fue mover la mayoría de las cosas transaccionales en las tareas de la cola de inserción, deshabilitar los rebashs en el nivel de la transacción y la tarea y en su lugar volver a poner en cola la tarea por completo – menos rebashs pero más separados.

Generalmente, cuando se encuentra con tales problemas, tiene que volver a visitar sus estructuras de datos y / o la forma en que accede a ellos (sus transacciones). Además de las soluciones que mantienen la consistencia sólida (que puede ser bastante costosa), es posible que desee volver a verificar si la consistencia es realmente una necesidad. En algunos casos, se agrega como un requisito general simplemente porque parece simplificar las cosas. Desde mi experiencia no es así 🙂

Otra cosa que puede ayudar (pero solo un poco) es usar un tipo de instancia más rápido (también más caro): los tiempos de ejecución más cortos se traducen en un riesgo ligeramente menor de solapamiento de transacciones. Noté esto porque necesitaba una instancia con más memoria, lo que también resultó ser más rápido 🙂