JDO-транзакция App Engine для нескольких операций «многие к одному»

У меня есть простая модель домена следующим образом

Драйвер - ключ (строка), количество запусков, количество уникальных треков

Трек - ключ (строка), количество прогонов, количество уникальных водителей, лучшее время

Выполнить - ключ(?), ключ-драйвер, ключ-трек, время, логическое-обновление-драйвера, логическое-обновление-трека

Мне нужно иметь возможность обновлять Run и Driver в одной и той же транзакции; а также Run и Track в одной и той же транзакции (очевидно, чтобы убедиться, что я не обновляю статистику дважды или не пропускаю счетчик приращений)

Теперь я попытался назначить в качестве ключа запуска ключ, состоящий из ключа водителя / ключа дорожки / ключа запуска (строка)

Это позволит мне обновить за одну транзакцию сущность Run и сущность Driver.

Но если я попытаюсь обновить объекты Run и Track вместе, он будет жаловаться, что не может выполнить транзакцию в нескольких группах. В нем говорится, что в транзакции есть и водитель, и грузовик, и он не может работать с обоими...

tx.begin();

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

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

Как ни странно, когда я делаю то же самое для обновления Run and Driver, все работает нормально.

Любые предложения о том, как еще я могу сопоставить свою модель предметной области для достижения той же функциональности?


person Patrick    schedule 17.09.2010    source источник
comment
Я готов иметь дело с конкуренцией, мне не нужно, чтобы статистические поля обновлялись немедленно, поэтому у меня есть обновленные флаги на самом прогоне... потому что я могу запускать обновления, когда система менее загружена... чем я не могу пожертвовать это точность...   -  person Patrick    schedule 18.09.2010
comment
как немного больше контекста, у меня обычно было бы много треков и много драйверов; у сущности драйвера не будет много конфликтов, к ней одновременно обращается только один пользователь, но сущность дорожки будет читаться многими пользователями одновременно и запускаться против нее.   -  person Patrick    schedule 18.09.2010
comment
Обновление - я наконец закончил эту разработку. Результат находится на tRacePerfect.com . Трек — это головоломка, а водитель — игрок. как видите, ведется различная статистика. Я использую комбинацию методов сегментирования и задач.   -  person Patrick    schedule 04.02.2011


Ответы (4)


В Google App Engine все операции с хранилищем данных должны выполняться над сущностями. в той же группе объектов. Это связано с тем, что ваши данные обычно хранятся в нескольких таблицах, а Google App Engine не может выполнять транзакции между несколькими таблицами.

Сущности с собственными отношениями "один к одному" и "один ко многим" автоматически находятся в одной группе объектов. Таким образом, если сущность содержит ссылку на другую сущность или набор сущностей, вы можете читать или записывать их в одних и тех же транзакциях. Для сущностей, у которых нет отношения владельца, можно создать сущность с явным родительским элементом группы сущностей.

Вы можете поместить все объекты в одну и ту же группу сущностей, но могут возникнуть конфликты, если слишком много пользователей попытаются изменить объекты в группе сущностей одновременно. Если каждый объект находится в своей группе сущностей, вы не можете выполнять какие-либо значимые транзакции. Вы хотите сделать что-то среднее.

Одно из решений состоит в том, чтобы иметь Track и Run в одной и той же группе сущностей. Вы могли бы сделать это, если бы Track содержал список пробежек (если вы это сделаете, то Track может не нуждаться в подсчете пробежек, подсчете уникальных водителей и лучшем времени; они могут быть вычислены при необходимости). Если вы не хотите, чтобы у Track был список запусков, вы можете использовать отношение "один ко многим" без владельца и укажите группу сущностей, родительскую для Run, в качестве ее Track (см. "Создание сущностей с группами сущностей" на этой страницы). В любом случае, если прогон находится в той же группе сущностей, что и его трек, вы можете выполнять транзакции, включающие прогон и некоторые/все его треки.

Во многих крупных системах вместо использования транзакций для согласованности изменения выполняются путем создания операций, которые являются idempotent< /а>. Например, если Driver и Run не были в одной и той же группе сущностей, вы могли бы обновить количество запусков для Driver, сначала выполнив запрос, чтобы получить количество всех запусков до некоторой даты в прошлом, а затем в транзакции обновите драйвер, указав новый счетчик и дату последнего расчета.

Имейте в виду, что при использовании дат у машин может быть некоторый дрейф часов, поэтому я предложил использовать дату в прошлом.

person NamshubWriter    schedule 18.09.2010
comment
Спасибо за ваш отзыв. Я понимаю, что ограничения сущностей/группы/транзакций. Я не слишком заинтересован в том, чтобы трек (и драйвер) владел полной коллекцией всех прогонов... поскольку я ожидаю, что у трека будет много много прогонов, и мне будет постоянно нужна эта статистика подсчета всякий раз, когда я загружаю трек, но я не хочу загружать все прогоны только для того, чтобы постоянно подсчитывать статистику .... конечно, должен быть простой способ, даже если я пожертвую каким-то сочинением, которое я мог бы иметь дело с .... как заставить все объекты находиться в одной группе сущностей? - person Patrick; 18.09.2010
comment
Обязательно ли обновлять статистику в режиме реального времени? Если нет, я могу предложить, как вы можете обновить их в автономном режиме. Чтобы узнать, как выбрать родительскую группу сущностей для сущности, щелкните одну из ссылок в моем посте и см. Создание сущностей с группами сущностей. - person NamshubWriter; 18.09.2010
comment
Намшаб, еще раз спасибо за ваш отзыв. Я понимаю, что я должен найти путь между ними. Статистика не должна быть абсолютно в реальном времени. Но в идеале. Вот почему я подумал, что я попытаюсь обновить их в режиме реального времени, и если это не удастся, я попытаюсь позже (пока я могу сохранить тот факт, что это не удалось). Другая сложность, с которой я сталкиваюсь, заключается в том, что у Run по существу есть два отдельных родителя (Track и Driver) ... на самом деле это основная причина сложности. Потому что я не могу заставить его иметь двух родителей, насколько я понимаю. - person Patrick; 19.09.2010
comment
Сущность может иметь только одну родительскую группу сущностей. Похоже, идемпотентные операции удовлетворят ваши потребности. - person NamshubWriter; 19.09.2010
comment
Намшуб, еще раз спасибо за ваш вклад. Мой предложенный ответ решает мою проблему без необходимости идемпотентных операций. Но это, очевидно, упрощенный взгляд на мою реальную модель. Поэтому я обязательно рассмотрю идемпотентные операции, основанные на времени, когда моя проблема станет более сложной. еще раз спасибо. - person Patrick; 19.09.2010

Я думаю, что нашел боковое, но все же чистое решение, которое все еще имеет смысл в моей модели предметной области.

Модель предметной области немного меняется следующим образом:

Водитель - ключ (строка-идентификатор), статистика водителя - напр. id = "Майкл", пробеги = 17

Трек - ключ (строка-идентификатор), трек-статистика - напр. id="Монца", bestTime=157

RunData - ключ(string-id), stat-data - ex. id="Майкл-Монца-20101010", время=148

TrackRun — ключ (идентификатор трека/строки), обновление статистики отслеживания — напр. id="Монца/Майкл-Монца-20101010", отслеживать-статистику-обновлено=false

DriverRun — ключ (ID драйвера/строки), обновленная статистика драйвера — напр. id="Майкл/Майкл-Монца-20101010", обновленная статистика водителя=истина

Теперь я могу атомарно (т.е. точно) обновить статистику Трека статистикой Пробега, немедленно или в свободное время. (И то же самое со статистикой Driver/Run).

Так что, по сути, я должен немного расширить то, как я моделирую свою проблему, нетрадиционным реляционным способом. Что вы думаете?

person Patrick    schedule 18.09.2010
comment
Лично мне не нравится идея дублирования данных только для того, чтобы разрешить транзакции. В идеале ваша модель данных должна сопоставляться с вашей моделью предметной области. Если вам не нужна статистика в режиме реального времени, вы можете обновить ее в автономном режиме с помощью идемпотентных операций. - person NamshubWriter; 19.09.2010
comment
Ну, это то, как вы видите модель, я полагаю... я действительно могу извлечь два типа объектов запуска... я могу иметь TrackRun и DriverRun. Они просто хранят ассоциацию и флаги parentUpdated. Затем сами RunData могут храниться в отдельном объекте, поэтому нет дублирования. RunData только создается и никогда не обновляется. Эти объекты никогда не могут быть созданы дважды, потому что их идентификатор определяется пользователем. Это по-прежнему делает его действительным по сравнению с реальной бизнес-моделью. Мне нужно, чтобы статистика была как можно в реальном времени (но устойчивой к сбоям). обновляю свой ответ... - person Patrick; 19.09.2010
comment
Что произойдет, если запись в TrackRun завершится успешно, а запись в DriverRun не удастся? - person NamshubWriter; 19.09.2010
comment
Когда запуск создается пользователем в первый раз, в этом случае несколько повторных попыток могут произойти автоматически. если по-прежнему не получается, пользователь получает уведомление, и он может повторить попытку столько раз, сколько пожелает. Нет риска, что TrackRun (или DriverRun, или DriverData) будет создан дважды из-за подразумеваемого строкового идентификатора (создать, если не существует). Это создание. - person Patrick; 20.09.2010
comment
Затем этап обновления статистики может произойти в любое время. Система попробует сразу после создания. Но если какое-либо обновление завершается неудачно, пользователь все равно информируется об успехе... и статистика будет обновляться атомарно на более позднем этапе, когда система снова будет менее загружена/включена. - person Patrick; 20.09.2010
comment
Я реализовал это решение, и оно выполняет свою работу. спасибо NamshubWriter за участие в обсуждении - person Patrick; 21.09.2010

понимаю, что поздно, но..

Вы видели этот метод для банковских переводов? http://blog.notdot.net/2009/9/Distributed-Transactions-on-App-Engine

Мне кажется, что вы могли бы сделать что-то подобное, разбив свои счетчики приращений на два шага как IncrementEntity и обработав это, собирая кусочки позже, если транзакция не удалась и т. д.

Из блога:

  1. В транзакции вычтите требуемую сумму из платежной учетной записи и создайте дочернюю сущность «Перевод», чтобы записать это, указав получающую учетную запись в поле «Цель» и оставив поле «Другое» пустым.
  2. Во второй транзакции добавьте необходимую сумму на счет-получатель и создайте дочернюю сущность «Перевод» для ее записи, указав платежную учетную запись в поле «Цель» и сущность «Перевод», созданную на шаге 1, в поле «Другое».
  3. Наконец, обновите сущность Transfer, созданную на шаге 1, установив в поле «другое» значение Transfer, созданное на шаге 2.

В блоге есть примеры кода на Python, но их легко адаптировать.

person Steven Veltema    schedule 30.12.2010
comment
спасибо Стивен. да, этот подход действительно похож на то, как я решил свою проблему. хорошее чтение и объяснение. - person Patrick; 30.12.2010

На эту тему есть интересный сеанс google io http://www.google.com/events/io/2010/sessions/high-throughput-data-pipelines-appengine.html

Я думаю, вы могли бы обновить статистику пробега, а затем запустить две задачи, чтобы обновить драйвер и трек по отдельности.

person guigouz    schedule 06.01.2011
comment
Привет Гигуз. Спасибо за ваш отзыв. Да, это очень похоже на то, что я в итоге реализовал. - person Patrick; 04.02.2011