4

J'ai des problèmes de contention dans Google App Engine et j'essaie de comprendre ce qui se passe.Problèmes de contention dans Google App Engine

J'ai un gestionnaire de requêtes annotée avec:

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

..et dans ce code, je vais chercher des choses, mettre à jour quelques autres, etc., mais parfois une erreur comme celle-ci vient dans le journal lors d'une demande:

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

.. suivi d'une trace de pile. Je peux mettre à jour avec toute la trace de la pile si nécessaire, mais c'est assez long.

Je ne comprends pas pourquoi cela arrive. En regardant la ligne dans mon code là l'exception vient, je cours get_by_id sur une entité totalement différente (ronde). Le "PlayerGameStates", nom "hannes2" qui est mentionné dans les messages d'erreur est le parent d'une autre entité GameState, qui a été get_async: édité à partir de la base de données quelques lignes plus tôt;

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

Bizarre (?) Chose est, il n'y a pas d'écritures dans la banque de données se produisant pour cette entité. Ma compréhension est que les erreurs de contention peuvent venir si la même entité est mise à jour en même temps, ou peut-être si trop d'écritures se produisent, dans un court laps de temps ..

Mais peut-il se produire lors de la lecture des entités aussi? ("Générateur suspendu get .." ??) Et, est-ce que cela se passe après les tentatives de 5 ndb.transaction ..? Je ne vois rien dans le journal qui indique que des tentatives ont été faites.

Toute aide est grandement appréciée.

+2

Je regarderais votre structure de clé. La contention n'est pas seulement au niveau de l'entité. Vous devez également examiner les parents. Regardez la portée de vos groupes d'entités afin de comprendre pourquoi vous avez des conflits. –

+0

Merci. J'ai essayé de garder les petits groupes d'entités, et d'éviter les conflits - mais j'examinerai plus. Dans ce cas, un conflit s'est produit dans le groupe d'entités avec le parent 'ndb.Key (" PlayerGameStates "," hannes2 ")', n'est-ce pas? Je ne comprends toujours pas pourquoi ** lire ** déclenche une exception/contention? Où puis-je lire plus à ce sujet ..? – boffman

+3

@TimHoffman Ok, je l'ai trouvé dans la [Documentation sur les transactions inter-groupes] (https://cloud.google.com/appengine/docs/python/datastore/#Python_Cross_group_transactions): "Remarque: Première lecture d'une entité Un groupe dans une transaction XG peut déclencher une exception TransactionFailedError en cas de conflit avec d'autres transactions accédant à ce même groupe d'entités, ce qui signifie que même une transaction XG effectuant uniquement des lectures peut échouer avec une exception de concurrence. " – boffman

Répondre

3

Oui, un conflit peut se produire à la fois pour les opérations de lecture et d'écriture. Après le début d'une transaction - dans le cas où le gestionnaire annoté @ndb.transactional() est appelé - tout groupe d'entités accédé (par lecture ou par écriture, peu importe) est immédiatement marqué comme tel. À ce moment-là, on ne sait pas si à la fin de la transaction, il y aura une opération d'écriture ou non - cela n'a même pas d'importance.

L'erreur de contention trop importante (qui est différente d'une erreur de conflit!) Indique que trop de transactions parallèles tentent simultanément d'accéder au même groupe d'entités. Cela peut arriver même si aucune transaction ne tente d'écrire!

Note: cette affirmation est pas émulé par le serveur de développement, il ne peut être vu lorsqu'il est déployé sur GAE, avec le vrai datastore!

Ce qui peut ajouter à la confusion, ce sont les ré-essais automatiques des transactions, qui peuvent survenir après des conflits d'écriture réels ou simplement des conflits d'accès. Ces tentatives peuvent apparaître à l'utilisateur final comme une exécution répétée suspecte de certains chemins de code - le gestionnaire dans votre cas. Les tentatives peuvent réellement empirer les choses (pour un court instant) - en lançant encore plus d'accès aux groupes d'entités déjà très sollicités - J'ai vu de tels modèles avec des transactions qui ne fonctionnent qu'après que les retards exponentiels deviennent assez grands pour laisser les choses refroidir un peu (si le nombre de tentatives est suffisant) en permettant aux transactions déjà en cours de se terminer.Mon approche était de déplacer la plupart des tâches transactionnelles sur les tâches de la file d'attente push, de désactiver les tentatives au niveau de la transaction et de la tâche et de remettre la tâche en file d'attente entièrement - moins de tentatives mais espacées davantage.

Habituellement, lorsque vous rencontrez de tels problèmes, vous devez visiter à nouveau vos structures de données et/ou la façon dont vous y accédez (vos transactions). En plus des solutions qui maintiennent la forte consistance (ce qui peut être assez cher), vous pouvez vérifier à nouveau si la cohérence est vraiment un must. Dans certains cas, il est ajouté comme une exigence générale simplement parce que cela semble simplifier les choses.

Une autre chose peut aider (mais seulement un peu) utilise un type d'instance plus rapide (aussi plus cher) - des temps d'exécution plus courts se traduisent par un risque légèrement moindre de chevauchement des transactions. J'ai remarqué ceci comme j'avais besoin d'une instance avec plus de mémoire, qui s'est avérée être aussi plus rapide :)