Управление параллелизмом в модели Django - PullRequest
20 голосов
/ 29 октября 2009

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

Ответы [ 3 ]

22 голосов
/ 01 ноября 2009

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

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

Но я чувствую себя бессвязным, так что вот так ...

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

  • Насколько вероятно, что двум пользователям потребуется одновременно изменить одну и ту же запись?
  • Какое влияние оказывает пользователь, если его / ее изменения в записи будут потеряны?

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

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

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

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

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

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

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

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

11 голосов
/ 13 января 2011

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

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

2 голосов
/ 07 июня 2014

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

Я хочу внести рабочий фрагмент относительно самой последней части его ответа («С точки зрения Django, оптимистичное управление параллелизмом может быть реализовано путем переопределения метода save в вашем классе модели ...»)

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...