Может ли транзакционный db.get () вернуть устаревший результат, если недавняя транзакция вызвала особое исключение?

Это находится в документации по транзакциям appengine ...

Примечание. Если ваше приложение получает исключение при фиксации транзакции, это не всегда означает, что транзакция не удалась. Вы можете получить исключения Timeout, TransactionFailedError или InternalError в тех случаях, когда транзакции были зафиксированы и в конечном итоге будут успешно применены ...

Рассмотрим следующий сценарий

  1. Я обновляю объект A в транзакции.
  2. операция транзакции приводит к описанному выше специальному «исключению», когда транзакция была зафиксирована и в конечном итоге будет применена
  3. Я запускаю db.get(entity_a_key_goes_here) в другой транзакции сразу после или почти одновременно с шагом 2.
  4. Если шаг 3 выше возвращает None, я создаю этот объект, устанавливая ключ на entity_a_key_goes_here и db.put() его (шаг 3 и этот шаг выполняются в той же транзакции).

Мой вопрос:

Возможно ли, чтобы транзакционная db.get() операция на шаге 3 выше возвращала устаревшее значение (или не обновленное значение, установленное на шаге 1)? Гарантируется ли, что транзакционные db.get() операции вернут самый свежий результат, даже если "странное" исключение транзакции возникает прямо перед ним?


person Albert    schedule 08.06.2013    source источник


Ответы (3)


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

Итак, возможна следующая последовательность событий:

  1. Начать транзакцию 1.
  2. Внутри транзакции 1 вы читаете объект A, и он возвращает состояние A1.
  3. Внутри транзакции 1 вы обновляете объект A с состояния A1 до состояния A2.
  4. При фиксации транзакции 1 происходит сбой с одним из упомянутых исключений.
  5. Начать транзакцию 2.
  6. Внутри транзакции 2 вы читаете объект A, и он возвращает состояние A1.
  7. Транзакция 1 применяется к хранилищу данных в фоновом режиме, изменяя A из состояния A1 в состояние A2.
  8. Завершить транзакцию 2 (без фиксации, так как не было записи).

Но эта последовательность событий иная:

  1. Начать транзакцию 1.
  2. Внутри транзакции 1 вы читаете объект A, и он возвращает состояние A1.
  3. Внутри транзакции 1 вы обновляете объект A с состояния A1 до состояния A2.
  4. При фиксации транзакции 1 происходит сбой с одним из упомянутых исключений.
  5. Начать транзакцию 2.
  6. Внутри транзакции 2 вы читаете объект A, и он возвращает состояние A1.
  7. Внутри транзакции 2 вы обновляете объект A с состояния A1 до состояния A3.
  8. Транзакция 1 применяется к хранилищу данных в фоновом режиме, переводя A из состояния A1 в состояние A2.
  9. Транзакция 2 теперь не будет успешно зафиксирована, потому что запись, которую она собирается применить, основана на устаревшей версии объекта A. Она завершится ошибкой, и A останется в состоянии A2.

Итак, добавив к вопросу шаг 4, вы попадаете во вторую последовательность событий. Возможно, что get на шаге 3 вернет None, хотя сущность действительно существует, но в этом случае невозможно для успешной последующей записи: транзакция устарела и, следовательно, не может быть зафиксирована. Транзакция будет повторена, и во второй раз get вернет объект, записанный на шаге 1, что вы и хотели.

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

person Torne    schedule 13.06.2013
comment
Спасибо, @Torne! Я отредактировал свой вопрос, чтобы добавить шаг 4. Если шаг 3 возвращает None (что означает, что он не существует), я создаю его. Я использую эту сущность, чтобы гарантировать, что транзакции никогда не запускаются дважды, потому что мои операции на самом деле не идемпотентны. Мой вопрос в том, есть ли возможность для шага 3 db.get() вернуть None, когда он фактически уже был создан в результате этого отложенного возникновения транзакции, а затем приступить к успешной фиксации всего в транзакции? Спасибо! - person Albert; 14.06.2013
comment
Отредактированный ответ для уточнения, но прямой ответ на ваш комментарий - нет, он не будет зафиксирован :) - person Torne; 14.06.2013

Я думаю, что результатом шага 3 в этой ситуации будет устаревшее значение. В этом случае «самым свежим» результатом может быть не объект из шага 1.

Я предлагаю использовать кэш памяти при извлечении значений (возврат к вызову db.get(), когда кэш памяти отсутствует) и обновлять кэш памяти при обновлении объекта. Используйте ключ объекта как ключ кэша памяти. Фактически, именно так ndb работает автоматически.

person Brent Washburne    schedule 11.06.2013

Из того, что я прочитал в документах на «ndb», я понимаю, что если вы используете свои get() и put() как в «транзакциях» (@ndb.transactional), то вы не получите устаревшие данные. «ndb» либо обслужит обновленные данные, либо не сработает и то, и другое.

Транзакции либо завершаются неудачно, либо завершаются успешно. Также, как и другие dbms, ndb тоже ведет «журнал».

Надеюсь это поможет.

person Saptadeep    schedule 11.06.2013
comment
Я запускаю get () в отдельной транзакции сразу после транзакции, которая выполнила put (), которая вернула специальное исключение. - person Albert; 12.06.2013