Сильно согласованные запросы для корневых сущностей в GAE?


Я хотел бы получить несколько советов о том, как лучше всего выполнять строго согласованное чтение / запись в Google App Engine.

Мои данные хранятся в классе, подобном этому.

class UserGroupData(ndb.Model):
  users_in_group = ndb.StringProperty(repeated=True)
  data = ndb.StringProperty(repeated=True)

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

def update_data(user_id, additional_data):
  entity = UserGroupData.query(UserGroupData.users_in_group==user_id).get()
  entity.data.append(additional_data)
  entity.put()

Если сущность, возвращаемая запросом черствеет, данные теряются.

Для достижения сильной согласованности, кажется, у меня есть несколько различных вариантов. Я хотел бы знать, какой вариант лучше:

Вариант 1:

Используйте get_by_id(), который всегда сильно согласован. Однако здесь, похоже, нет аккуратного способа сделать это. Нет чистого способа получить ключ для UserGroupData непосредственно из user_id, потому что отношение много к одному. Это также кажется довольно хрупким и рискованным, чтобы требовать моего внешние клиенты для хранения и отправки ключа UserGroupData.

Вариант 2: Поместите мои сущности в группу предков и выполните запрос предка. Что-то вроде:

def update_data(user_id, additional_data):
  entity = UserGroupData.query(UserGroupData.users_in_group==user_id,
                               ancestor=ancestor_for_all_ugd_entities()).get()
  entity.data.append(additional_data)
  entity.put()
Я думаю, что это должно сработать, но поместить все сущности UserGroupData в одну группу предков кажется экстремальной вещью. Это приводит к тому, что записи ограничиваются ~1/С. Это кажется неправильным подходом, поскольку каждый UserGroupData на самом деле логически независим. На самом деле то, что я хотел бы сделать, это выполнить строго согласованный запрос для корневой сущности. Есть ли какой-то способ сделать это? Я заметил предложение в другом ответе по существу осколить группу предков. Это лучшее, что можно сделать?

Вариант 3:

Третий вариант - сделать запрос keys_only, за которым следует get_by_id(), например:
def update_data(user_id, additional_data):
  entity_key = UserGroupData.query(UserGroupData.users_in_group==user_id,
                                   ).get(keys_only=True)
  entity = entity_key.get()
  entity.data.append(additional_data)
  entity.put()
Насколько я могу судить, этот метод безопасен от потери данных, так как мои ключи не меняются и get() дает строго последовательные результаты. Однако, я не видел этот подход упоминается где угодно. Разумно ли это делать? Есть ли у него какие-то недостатки, которые мне нужно понять?
1 2

1 ответ:

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

Запрос, подобный приведенному в вашем примере UserGroupData.query(UserGroupData.users_in_group==user_id).get(), всегда возвращает только одну сущность, если идентификатор пользователя находится в группе.

Если он только что был добавлен и индекс не обновлен, то вы не получите запись и, следовательно, не будете обновлять запись.

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

Что касается предков, улучшающих согласованность запроса, то это не очевидно, если вы планируете иметь несколько сущностей UserGroupData. В таком случае, почему вы делаете get().

Так что Вариант 3, вероятно, ваш лучший выбор, сделайте запрос только ключей, а затем внутри транзакции сделайте ключ.get () и update. Помните, что межгрупповые транзакции ограничены 5 группами сущностей.

Учитывая этот подход, если индекс, на котором основан запрос, устарел, то 1 из 3 вещей может случись,

  1. нужная запись не найдена, так как вновь добавленный идентификатор пользователя не отражается в индексе.
  2. запись, которую вы хотите найти, будет последовательно извлекаться get ()
  3. Запись, которую вы хотите найти, найдена, но идентификатор пользователя фактически удален, а индекс устарел. Get() будет последовательно извлекать индекс, а идентификатор пользователя отсутствует.

Затем вы можете решить, какой курс действий выбрать.

Каков пример использования запрашивать все сущности UserGroupData, членом которых является конкретный пользователь, для которых требуются обновления ?