Как сосчитать обе стороны отношения многих ко многим в Google App Engine - PullRequest
3 голосов
/ 11 февраля 2010

Рассмотрим приложение GAE (python), которое позволяет пользователям комментировать песни. Ожидаемое количество пользователей - 1 000 000+. Ожидаемое количество песен - 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='me@example.com')
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 должны были быть частью одной и той же группы сущностей . Они не могут быть в одной группе сущностей, потому что тогда все мои данные будут в одной группе, и они не смогут быть распределены пользователем.

1 Ответ

1 голос
/ 11 февраля 2010

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

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

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

Этот ответ для этой проблемы: Sharded Counter .

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

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

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

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

...