ndb how google engine delete create app python google-app-engine google-cloud-datastore app-engine-ndb contention

python - how - ndb properties



Problemas de contenciĆ³n en Google App Engine (1)

Tengo problemas de contención en Google App Engine y trato de entender lo que está sucediendo.

Tengo un controlador de solicitud anotado con:

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

... y en ese código busco algunas cosas, actualizo 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 < 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" } > )

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

No entiendo por qué sucede esto. Mirando la línea en mi código, llega 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 : editado de 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 para esa entidad. Tengo entendido que pueden producirse errores de contención 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 get .." ??) Y, ¿está sucediendo esto después de los 5 reintentos de transacción ndb ..? No puedo ver nada en el registro que indique que se han realizado reintentos.

Cualquier ayuda es muy apreciada.


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

Después de que se inicia una transacción, en su caso cuando se invoca el controlador anotado con @ndb.transactional() , cualquier grupo de entidades al que se accede (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 (¡que es diferente a un error de conflicto!) Indica que muchas transacciones paralelas intentan acceder simultáneamente al mismo grupo de entidades. ¡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 reinicios automáticos de las transacciones, que pueden suceder después de conflictos de escritura reales o simplemente una contención de acceso simple. Estos reintentos pueden parecer al usuario final como una ejecución repetida sospechosa de algunas rutas de código, el controlador en su caso.

Los reintentos en realidad pueden empeorar las cosas (por un breve tiempo), lanzando aún más accesos a los grupos de entidades ya muy accedidos. He visto tales patrones con transacciones que solo funcionan después de que los retrasos exponenciales de retroceso crecen lo suficiente como para dejar que las cosas se enfríen un poco ( si el número de reintentos es lo suficientemente grande) permitiendo que se completen las transacciones que ya están en progreso.

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

Por lo general, cuando se encuentra con estos problemas, debe volver a visitar sus estructuras de datos y / o la forma en que accede a ellas (sus transacciones). Además de las soluciones que mantienen la consistencia fuerte (que puede ser bastante costosa), puede volver a verificar si la consistencia es realmente necesaria. En algunos casos, se agrega como un requisito general solo porque parece simplificar las cosas. Desde mi experiencia no lo hace :)

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