Сбой транзакции GAE и идемпотентность

Документация Google App Engine содержит этот абзац:

Примечание. Если ваше приложение получает исключение при совершении транзакции, это не всегда означает, что транзакция завершилась неудачно. Вы может принимать DatastoreTimeoutException, ConcurrentModificationException или DatastoreFailureException исключения в случаях совершения сделок и в конечном итоге будут успешно применены. Когда это возможно, сделайте свой Операции хранилища данных идемпотент, чтобы, если вы повторяете транзакцию, конечный результат будет таким же.

Подождите, что? Похоже, что существует очень важный класс транзакций, который просто невозможно сделать идемпотентным, поскольку они зависят от текущего состояния хранилища данных. Например, простой счетчик, как в подобной кнопке. Транзакция должна считывать текущий счетчик, увеличивать его и выписывать счет снова. Если транзакция выглядит "сбой", но НЕ ДЕЙСТВИТЕЛЬНО не работает, и мне нечего сказать об этом на стороне клиента, тогда мне нужно попробовать еще раз, что приведет к одному щелчку, генерирующему два "симпатия". Неужели есть какой-то способ предотвратить это с помощью GAE?

Edit:

похоже, что это проблема, присущая распределенным системам, как и не только Guido van Rossum - см. эту ссылку:

исключение транзакции хранилища данных приложения

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

Мне было интересно, можно ли реализовать глобальную систему в целом приложении для обеспечения идемпотентности. Ключом будет поддерживать журнал транзакций в хранилище данных. Клиент сгенерировал GUID, а затем включил бы этот GUID с запросом (тот же идентификатор GUID будет повторно отправлен на повторные попытки для того же запроса). На сервере в начале каждой транзакции он будет выглядеть в хранилище данных для записи в группе сущностей транзакций с этим идентификатором. Если он найдет это, то это повторная транзакция, поэтому она вернется без каких-либо действий.

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

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

Ответ 1

dan wilkerson, simon goldsmith, et al. разработали глобальную транзакционную систему поверх локальных транзакций (для каждой группы объектов). на высоком уровне он использует методы, похожие на GUID, который вы описываете. dan рассматривается как "подводная запись", т.е. транзакции, которые вы описываете, о том, что сообщение об отказе, но позже поверхности, как это удалось, а также многие другие теоретические и практические детали хранилища данных. erick armbrust реализована в дизайне tapioca-orm.

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

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

хранилище данных внедряется поверх мегастраста, которое подробно описано в в этой статье. Короче говоря, он использует multi-version concurrency control внутри каждой группы сущностей и Paxos для репликации в центрах обработки данных, которые могут способствовать написанию подводных лодок. Я не знаю, есть ли публичные номера на частоте записи подводных лодок в хранилище данных, но если есть, поиск с этими условиями и списками рассылки хранилища данных должен найти их.

amazon S3 на самом деле не сопоставимая система; это больше CDN, чем распределенная база данных. amazon SimpleDB сопоставим. он изначально предоставлял конечную согласованность и в конечном итоге добавлял очень ограниченный вид транзакций, которые они называют условная запись, но у нее нет истинных транзакций. другие базы данных NoSQL (redis, mongo, couchdb и т.д.) имеют разные варианты транзакций и согласованности.

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

CAP.

Ответ 2

Лучший способ, которым я придумал создание счетчиков idempotent, - использовать набор вместо целого числа для подсчета. Таким образом, когда человек "любит" что-то, вместо того, чтобы увеличивать счетчик, я добавляю подобное к следующему:

class Thing {
Set<User> likes = ....

public void like (User u) {
  likes.add(u);
}
public Integer getLikeCount() {
  return likes.size();
}
}

это в java, но я надеюсь, что вы получите мой смысл, даже если вы используете python.

Этот метод является идемпотентным, и вы можете добавить одного пользователя за сколько раз вам нравится, он будет учитываться только один раз. Конечно, у него есть штраф за хранение огромного набора вместо простого счетчика. Но, эй, тебе все равно нужно следить за нравами? Если вы не хотите раздувать объект Thing, создайте еще один объект ThingLikes и кешируйте подобный счет на объекте Thing.

Ответ 3

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

если вы предпочитаете читать переполнение стека, этот вопрос SO имеет более подробную информацию.