2010-09-17 21 views
0

J'ai un modèle simple de domaine comme suitApp Engine JDO Transaction sur plusieurs many-to-one

Driver - clé (chaîne), exécutez comptage, comptage piste unique

Track - clé (chaîne), exécutez comptage, comptage pilote unique, le meilleur temps

Run - clé(), pilote-clé, piste clé, le temps, booléen-pilote de mise à jour, mise à jour piste booléenne

Je dois pouvoir mettre à jour un Run et un Driver dans la même transaction; ainsi qu'un Run et un Track dans la même transaction (évidemment pour m'assurer que je ne mets pas à jour les statistiques deux fois, ou manquer un compteur d'incrément)

Maintenant j'ai essayé d'assigner comme clé de course, une clé faite up de driver-key/track-key/run-key (chaîne)

Cela me permettra de mettre à jour en une transaction l'entité Run et l'entité Driver. Mais si j'essaie de mettre à jour les entités Run et Track ensemble, il se plaindra qu'il ne peut pas transiter sur plusieurs groupes. Il dit qu'il a à la fois le conducteur et le camion dans la transaction et il ne peut pas fonctionner à la fois ...

 
tx.begin(); 

run = pmf.getObjectById(Run.class, runKey); 
track = pmf.getObjectById(Track.class, trackKey); 
//This is where it fails; 

incrementCounters(); 
updateUpdatedFlags(); 
tx.commit(); 

Bizarrement quand je fais une chose semblable à mettre à jour Exécuter et pilote, il fonctionne très bien.

Des suggestions sur la façon dont je peux mapper mon modèle de domaine pour obtenir la même fonctionnalité?

+0

Je suis prêt à faire face à la contention, je n'ai pas besoin des champs de statistiques mis à jour immédiatement, c'est pourquoi j'ai les drapeaux "mis à jour" sur la course elle-même ... ce que je ne peux pas sacrifier est la précision ... – Patrick

+0

comme un peu plus de contexte, j'aurais typiquement beaucoup de pistes et de nombreux pilotes; une entité de pilote ne devrait pas avoir beaucoup de contention, elle n'est accessible que par un utilisateur à la fois, mais une entité de piste serait lue par de nombreux utilisateurs à la fois, et s'exécuterait contre elle. – Patrick

+0

Mise à jour - J'ai enfin terminé ce développement. Le résultat est sur http://www.tRacePerfect.com/. La "Piste" est le "Puzzle" et le "Pilote" est le "Joueur". Comme vous pouvez le voir, diverses statistiques sont maintenues. J'utilise une combinaison de techniques de sharding et de tâches. – Patrick

Répondre

0

Je pense avoir trouvé une solution latérale mais toujours propre qui a toujours du sens dans mon modèle de domaine.

Le modèle de domaine change légèrement comme suit:

Driver - clé (string-id), pilote-stats - ex. id = "Michael", exécute = 17

Track - clé (identificateur de chaîne), track-stats - ex. id = "Monza", bestTime = 157

RunData - clé (identificateur de chaîne), données-stat - ex. id = "Michael-Monza-20101010", time = 148

TrackRun - clé (Piste/identificateur de chaîne), track-stats-updated - ex. id = "Monza/Michael-Monza-20101010", track-stats-updated = false

DriverRun - clé (Pilote/identifiant-de-chaîne), driver-stats-updated - ex. id = « Michael/Michael-Monza-20101010 », pilote-stats-mis à jour = true

Je peux maintenant mettre à jour atomiquement (c. précisément) les statistiques d'une piste avec les statistiques d'une course, immédiatement ou dans mon propre temps. (Et même avec les statistiques Driver/Run).

Donc, fondamentalement, je dois développer un peu la façon dont je modélise mon problème, d'une manière relationnelle non conventionnelle. Qu'est-ce que tu penses?

+0

Personnellement, je n'aime pas l'idée de dupliquer les données juste pour permettre les transactions. Idéalement, votre modèle de données doit correspondre à votre modèle de domaine. Si vous n'avez pas besoin que les statistiques soient en temps réel, vous pouvez les mettre à jour hors ligne avec des opérations idempotentes. – NamshubWriter

+0

Eh bien c'est la façon dont vous voyez le modèle je suppose ... je peux réellement extraire deux types d'entités d'exécution ... je peux avoir un TrackRun, et un DriverRun. Ceux-ci stockent simplement l'association et les drapeaux "parentUpdated". Le RunData lui-même peut alors être stocké dans une entité distincte, il n'y a donc pas de duplication. RunData est uniquement créé et jamais mis à jour. Ces entités ne peuvent jamais être créées deux fois car leur identifiant est défini par l'utilisateur. Cela le rendra encore valide par rapport au modèle économique réel. J'ai besoin que les statistiques soient aussi en temps réel que possible (mais résilientes à l'échec). mettre à jour ma réponse ... – Patrick

+0

Que se passe-t-il si l'écriture dans le TrackRun réussit et que l'écriture dans le DriverRun échoue? – NamshubWriter

0

Avec Google App Engine, all of the datastore operations must be on entities in the same entity group. En effet, vos données sont généralement stockées sur plusieurs tables et Google App Engine ne peut pas effectuer de transactions sur plusieurs tables.

Entités avec des relations un-à-un et un-à-plusieurs propriétaires are automatically in the same entity group. Ainsi, si une entité contient une référence à une autre entité, ou une collection d'entités, vous pouvez lire ou écrire dans les deux transactions. Pour les entités qui n'ont pas de relation de propriétaire, vous pouvez créer une entité avec un parent de groupe d'entités explicite.

Vous pouvez placer tous les objets dans le même groupe d'entités, mais vous risquez de rencontrer des conflits si trop d'utilisateurs tentent de modifier des objets dans un groupe d'entités en même temps. Si chaque objet appartient à son propre groupe d'entités, vous ne pouvez pas effectuer de transactions significatives. Vous voulez faire quelque chose entre les deux.

Une solution consiste à avoir Track and Run dans le même groupe d'entités. Vous pouvez le faire en ayant Track contient une liste d'exécutions (si vous faites cela, alors Track peut-être pas besoin de run-count, unique-driver-count et best-time, ils pourraient être calculés en cas de besoin). Si vous ne voulez pas que Track ait une liste d'exécutions, vous pouvez utiliser un unowned one-to-many relationship et spécifier le parent du groupe d'entités de l'exécution de sa piste (voir "Création d'entités avec des groupes d'entités" au this page). Quoi qu'il en soit, si une exécution est dans le même groupe d'entités que sa piste, vous pouvez effectuer des transactions qui impliquent une exécution et certaines/toutes ses pistes.

Pour de nombreux systèmes de grande taille, au lieu d'utiliser des transactions pour la cohérence, les modifications sont effectuées en effectuant des opérations idempotent. Par exemple, si Pilote et Exécuter ne faisaient pas partie du même groupe d'entités, vous pouvez mettre à jour le nombre d'exécutions pour un Pilote en faisant d'abord une requête pour obtenir le décompte de toutes les exécutions avant une date passée, puis, dans une transaction, mettre à jour le pilote avec le nouveau compte et la date à laquelle il a été calculé pour la dernière fois.Gardez à l'esprit lorsque vous utilisez des dates que les machines peuvent avoir une sorte de dérive d'horloge, c'est pourquoi j'ai suggéré d'utiliser une date dans le passé.

+0

Merci pour vos commentaires. Je comprends bien que la contrainte entités/groupe/transaction .. Je ne suis pas trop désireux d'avoir la Piste (et le pilote) possédant une collection complète de toutes les courses ... comme je m'attends en particulier la piste à avoir beaucoup de courses, et J'aurai toujours besoin de ces statistiques de compte chaque fois que je charge une piste, mais je ne veux pas charger toutes les courses juste pour calculer des statistiques tout le temps .... sûrement il doit y avoir un moyen simple, même si je sacrifie Je pourrais traiter avec ... comment puis-je forcer "tous les objets à être dans le même groupe d'entité"? – Patrick

+0

Les statistiques doivent-elles absolument être mises à jour en temps réel? Sinon, je peux vous suggérer des moyens de les mettre à jour hors ligne. Pour voir comment sélectionner le parent du groupe d'entités pour une entité, cliquez sur l'un des liens dans mon article et voir "Création d'entités avec des groupes d'entités" – NamshubWriter

+0

Namshub, merci encore pour vos commentaires. Je comprends que je dois trouver un moyen entre. Les statistiques ne doivent pas être absolument en temps réel. Mais idéalement. C'est pourquoi ce que je pensais que je ferais est d'essayer de les mettre à jour en temps réel, et si cela échoue, je vais essayer plus tard (tant que je peux stocker le fait qu'il a échoué). L'autre complication que j'ai est que Run a essentiellement deux parents distincts (Track and Driver) ... c'est en fait la cause de la complication. Parce que je ne peux pas le faire avoir deux parents de ce que je comprends. – Patrick

0

réaliser ce retard, mais ..

Avez-vous vu cette méthode pour les transferts de compte bancaire? http://blog.notdot.net/2009/9/Distributed-Transactions-on-App-Engine

Il me semble que vous pourriez faire quelque chose de similaire en brisant vos compteurs d'incrément en deux étapes comme IncrementEntity et processus qui, ramasser les morceaux plus tard, si une transaction échoue, etc.

Depuis le blog :

  1. Dans une transaction, déduire le montant du compte payant nécessaire et créer une entité enfant de transfert à record cette, en spécifiant le Fich compte dans le champ "cible" et maintenant le champ "Autre" vide pour maintenant.
  2. Dans une deuxième transaction, ajoutez le montant requis pour la réception de compte , et créer un enfant de transfert entité pour enregistrer ce, en spécifiant le compte payant dans le champ « cible », et l'entité de transfert créée en étape 1 dans le champ 'autre'.
  3. Enfin, mettre à jour l'entité de transfert créée en étape 1, définissant le champ «autre» à le transfert que nous avons créé à l'étape 2.

Le blog a des exemples de code en Python, mais devrait être facile à adapter

+0

merci steven. Oui, cette approche est similaire à la façon dont j'ai résolu mon problème. bonne lecture et explication. – Patrick