Как посчитать обе стороны связи «многие ко многим» в Google App Engine

StackOverflow https://stackoverflow.com/questions/2244850

Вопрос

Рассмотрим приложение 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 должны были бы быть частью одного и того же. группа сущностей.Они не могут быть в одной группе сущностей, потому что тогда все мои данные будут в одной группе и не смогут распространяться пользователем.

Это было полезно?

Решение

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top