Как подсчитать обе стороны отношения "многие ко многим" в Google App Engine

Рассмотрим приложение GAE (python), которое позволяет пользователям комментировать песни. Ожидаемое количество пользователей - 1000000+. Ожидаемое количество песен - 5000.

Приложение должно уметь:

  • Укажите количество песен, которые прокомментировал пользователь.
  • Укажите количество пользователей, которые прокомментировали песню.

Управление счетчиками должно быть транзакционным, чтобы они всегда отражали лежащие в основе данные.

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

Моя модель данных

class Song(BaseModel):
    name = db.StringProperty()
    # Number of users commenting on the song
    user_count = db.IntegerProperty('user count', default=0, required=True)
    date_added = db.DateTimeProperty('date added', False, True)
    date_updated = db.DateTimeProperty('date updated', True, False)

class User(BaseModel):
    email = db.StringProperty()
    # Number of songs commented on by the user
    song_count = db.IntegerProperty('song count', default=0, required=True)
    date_added = db.DateTimeProperty('date added', False, True)
    date_updated = db.DateTimeProperty('date updated', True, False)

class SongUser(BaseModel):
    # Will be child of User
    song = db.ReferenceProperty(Song, required=True, collection_name='songs')
    comment = db.StringProperty('comment', required=True)
    date_added = db.DateTimeProperty('date added', False, True)
    date_updated = db.DateTimeProperty('date updated', True, False)

Код
Он обрабатывает количество песен пользователя транзакциями, но не количество пользователей песни.

s = Song(name='Hey Jude')
s.put()

u = User(email='[email protected]')
u.put()

def add_mapping(song_key, song_comment, user_key):
    u = User.get(user_key)

    su = SongUser(parent=u, song=song_key, song_comment=song_comment, user=u);
    u.song_count += 1

    u.put()
    su.put()

# Transactionally add mapping and increase user's song count
db.run_in_transaction(add_mapping, s.key(), 'Awesome', u.key())

# Increase song's user count (non-transactional)
s.user_count += 1
s.put()

Возникает вопрос: как я могу управлять обоими счетчиками транзакциями?

Насколько я понимаю, это невозможно, поскольку User, Song и SongUser должны быть частью одного и того же группа объектов. Они не могут быть в одной группе сущностей, потому что тогда все мои данные будут в одной группе, и они не могут быть распределены пользователем.


person cope360    schedule 11.02.2010    source источник


Ответы (1)


Вам действительно не следует беспокоиться об обработке количества песен пользователя, которые они прокомментировали внутри транзакции, потому что маловероятно, что пользователь сможет комментировать более одной песни за раз, верно?

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

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

Ответ на эту проблему - сегментированные счетчики.

Чтобы убедиться, что вы можете создать новую сущность SongUser и обновить сегментированный счетчик связанной песни, вам следует подумать о том, чтобы у сущности SongUser была связанная Песня в качестве родительской. Это поместит их в одну группу сущностей, и вы сможете создать SongUser и обновить сегментированный счетчик в одной транзакции. Отношение SongUser к пользователю, создавшему его, может храниться в ReferenceProperty.

Что касается вашего беспокойства по поводу того, что два обновления (транзакционное и пользовательское) не оба будут успешными, это всегда возможно, но, учитывая, что любое обновление может завершиться неудачно, вам потребуется надлежащая обработка исключений, чтобы гарантировать, что оба будут успешными. Это важный момент: нет гарантии, что обновления внутри транзакции будут успешными. Вы можете получить исключение TransactionfailedError, если транзакция не может быть завершена по какой-либо причине.

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

Сначала спросите себя: неужели это так плохо, если количество песен, которые кто-то прокомментировал, меньше на одну? Так ли это важно, как обновление баланса банковского счета или завершение продажи акций?

person Adam Crossland    schedule 11.02.2010
comment
Ваше решение снижает конкуренцию, но что я действительно пытаюсь сделать, так это убедиться, что оба счетчика соответствуют базовым SongUser записям. Если я использую сегментированные счетчики для Song сущностей, у меня все еще может быть случай, когда создание SongUser завершается успешно, а увеличение счетчика песни не удается (или наоборот). - person cope360; 11.02.2010
comment
Я думаю, что решение в вашем последнем абзаце, вероятно, лучший вариант в пределах возможностей GAE. В этом решении мы перевернули пример из моего первого комментария. Теперь возможно, например, что счетчик песни и записи SongUser будут обновлены / созданы, но обновление записи пользователя не удастся (или наоборот). Согласны ли вы, что невозможно обновить оба счетчика (сегментированные или нет) транзакционным способом? - person cope360; 11.02.2010
comment
Обновил мой ответ на основе вашего комментария - person Adam Crossland; 11.02.2010
comment
Думаю, было бы справедливее не задавать этот вопрос о чем-то тривиальном, например, о комментариях к песням. Я согласен с тем, что подсчет пользователей, возможно, снизится на нескольких, не имеет значения. Я просто пытаюсь понять, что я могу и чего не могу делать с GAE. Если бы я задал вопрос об остатках на счетах в банках, было бы намного проще прийти к ответу «Нет». Конечно, есть много других причин не использовать GAE для банковских транзакций;) - person cope360; 11.02.2010