Лучший способ избежать потери данных в приложении Django с высокой нагрузкой?

Представьте себе довольно сложное приложение Django с частями как внешнего, так и внутреннего интерфейса. Некоторые пользователи изменяют некоторые данные в части интерфейса. Некоторые сценарии периодически изменяют одни и те же данные в серверной части.

Пример:

instance = SomeModel.objects.get(...)
# (long-running part where various fields are changed, takes from 3 to 20 seconds)
instance.field = 123
instance.another_field = 'abc'
instance.save()

Если кто-то (или что-то) изменяет экземпляр, в то время как эта часть изменяет некоторые поля, тогда изменения будут потеряны, потому что экземпляр будет сохранен в последнее время, выгружая данные из класса Python (Django). Другими словами, если что-то в коде берет данные, затем ждет некоторое время и затем сохраняет данные обратно - тогда только самый последний "хранитель" сохранит свои данные, все остальные (предыдущие) потеряют свои изменения.

Это "высоконагруженное" приложение, загрузка базы данных (мы используем Postgres) достаточно высока, и я бы хотел избежать всего, что могло бы привести к значительному увеличению активности БД или занимаемой памяти.

Другая проблема - у нас есть много подключенных сигналов, и даже метод save() переопределяется, поэтому я хотел бы избежать всего, что может нарушить сигналы или быть несовместимым с пользовательскими методами save() или update().

Что бы вы порекомендовали в этой ситуации? Любое специальное приложение для этого? Сделки? Что-нибудь еще?

Спасибо!

1 ответ

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

Одним из решений может быть чтение данных и выполнение ваших долгосрочных задач. Затем, прежде чем сохранить его, вы начинаете транзакцию, прочитайте данные еще раз, но теперь с select_for_update и убедитесь, что исходные данные не изменились. Если данные все те же, вы сохраните. Если данные изменились, вы прерываете и повторно запускаете долгосрочную задачу. Таким образом, вы будете держать замок как можно короче.

Что-то вроде:

success = False
while not success:
  instance1 = SomeModel.objects.get(...)
  # (long-running part)

  with django.db.transaction.atomic():
    instance2 = SomeModel.objects.select_for_update().get(...)
    # (compare relevant data from instance1 vs instance2)
    if unchanged:
      # (make the changes on instance2)
      instance2.field = 123
      instance2.another_field = 'abc'
      instance2.save()
      success = True

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

Другие вопросы по тегам