Лучший способ избежать потери данных в приложении 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
Если это жизнеспособный подход, зависит от того, какова ваша долгосрочная задача. И пользователь все еще может перезаписать данные, которые вы сохраняете здесь.