Подтвердить что ты не робот

Concurrency управление в модели Django

Как обрабатывать concurrency в модели Django? Я не хочу, чтобы изменения в записи были перезаписаны другим пользователем, который читает одну и ту же запись.

4b9b3361

Ответ 1

Короткий ответ, это действительно не вопрос Django, как представлено.

Concurrency контроль часто представлен как технический вопрос, но во многом является вопросом функциональных требований. Как вам нужно/нужно, чтобы ваше приложение работало? До тех пор, пока мы не узнаем об этом, будет трудно дать какой-либо конкретный совет по Django.

Но я чувствую себя бессвязным, поэтому здесь идет...

Есть два вопроса, которые я, как правило, задаю себе, столкнувшись с необходимостью управления concurrency:

  • Насколько вероятно, что двум пользователям потребуется одновременная модификация одной и той же записи?
  • Что влияет на пользователя, если его/ее изменения в записи теряются?

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

Пессимистическая блокировка имеет много сложности. Вы должны синхронизировать доступ к замкам, учитывать отказоустойчивость, истечение срока блокировки, блокировать блокировку суперпользователями, можете ли пользователи видеть, кто имеет блокировку, так далее и т.д.

В Django это может быть реализовано с помощью отдельной модели Lock или какого-либо внешнего ключа заблокированного пользователя в заблокированной записи. Использование таблицы блокировки дает вам немного большую гибкость с точки зрения хранения, когда была приобретена блокировка, пользователь, заметки и т.д. Если вам нужна общая таблица блокировок, которая может использоваться для блокировки любой записи, а затем взгляните на django.contrib.contenttypes framework, но быстро это может превратиться в абстрагирование синдрома космонавта.

Если столкновения маловероятны или потерянные изменения тривиально воссозданы, то вы можете функционально уйти с оптимистичными методами concurrency. Этот метод прост и проще в реализации. По сути, вы просто отслеживаете номер версии или временную метку модификации и отклоняете любые изменения, которые вы обнаруживаете, как из-за ударов.

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

В терминах Django оптимистический элемент управления concurrency может быть реализован путем переопределения метода сохранения в вашем классе модели...

def save(self, *args, **kwargs):
    if self.version != self.read_current_version():
        raise ConcurrentModificationError('Ooops!!!!')
    super(MyModel, self).save(*args, **kwargs)

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

Ответ 2

Я не думаю, что "сохранение номера версии или метки времени" работает.

Если self.version == self.read_current_version() - True, все еще есть вероятность, что номер версии был изменен другими сеансами непосредственно перед вызовом super().save().

Ответ 3

Я согласен с вступительным объяснением Джо Холлоуэя.

Я хочу внести свой вклад в рабочий фрагмент относительно самой последней части его ответа ( "В терминах Django оптимистический concurrency элемент управления может быть реализован путем переопределения метода сохранения в вашем классе модели..." )

Вы можете использовать следующий класс в качестве предка для своей собственной модели.

Если вы находитесь внутри транзакции db (например, с помощью transaction.atomic во внешней области), следующие операторы python являются безопасными и последовательными

На практике с помощью одного снимка, фильтр фильтров + update предоставляет тип записи test_and_set в записи: они проверяют версию и приобретают неявно блокировку уровня db в строке. Таким образом, следующее "сохранение" может обновлять поля записи, что это единственный сеанс, который работает с экземпляром этой модели. Окончательная фиксация (например, автоматически выполняемая __exit__ в транзакции .atomic) освобождает неявный блокировку уровня db в строке

class ConcurrentModel(models.Model):
    _change = models.IntegerField(default=0)

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        cls = self.__class__
        if self.pk:
            rows = cls.objects.filter(
                pk=self.pk, _change=self._change).update(
                _change=self._change + 1)
            if not rows:
                raise ConcurrentModificationError(cls.__name__, self.pk)
            self._change += 1
        super(ConcurrentModel, self).save(*args, **kwargs)

Это взято из https://bitbucket.org/depaolim/optlock/src/ced097dc35d3b190eb2ae19853c2348740bc7632/optimistic_lock/models.py?at=default