Состояние гонки в Django

В Django я столкнулся с серьезным состоянием гонки. Проблема начинается, когда два участника пытаются одновременно выполнить some_method (). Журнал создается следующим образом:

Job 3: Candidate
Job 3: Already taken
Job 3: Candidate
Job 3: Already taken
Job 3: Candidate
Job 3: Already taken
(et cetera for 18 MB)

Следующий метод вызывает у меня проблемы. Следует отметить, что метод запускается повторно до тех пор, пока метод не вернет False:

def some_method():
    conditions = #(amongst others, excludes jobs with status EXECUTING)

    try:
        cjob = Job.objects.filter(conditions).order_by(some_fields)[0]
    except IndexError:
        return False

    print 'Job %s: Candidate' % cjob.id

    job = cjob.for_update()

    if cjob.status != job.status:
        print 'Job %s: Already taken' % cjob.id
        return True

    print 'Job %s: Starting...' % job.id

    job.status = Job.EXECUTING
    job.save()
    # Critical section

# In models.py:
class Job(models.Model):
    # ...

    def for_update(self):
        return Job.objects.raw('SELECT * FROM `backend_job` WHERE `id` = %s FOR UPDATE', (self.id, ))[0]

В настоящее время Django не имеет специального метода for_update, и для предотвращения создания запроса со всеми условиями, которые мы используем для определения необходимости выполнения задания, мы выполняем сложный запрос перед простым запросом FOR UPDATE.

Я действительно не понимаю, как это могло вызвать проблемы, которые мы видим, мы выполняем запрос, за которым следует инструкция, которая блокирует, когда другой бегун удерживает блокировку на задании. Блокировка снимается только после изменения статуса задания. Второй бегун теперь получает блокировку, но статус задания был изменен, поэтому он возвращается из метода только для того, чтобы повторно войти в него позже; но cjob-запрос больше не вернет то же задание, так как его статус теперь исключен фильтром.

Неправильно ли я истолковал предложение FOR UPDATE или я упустил что-то еще?

Следует отметить, что я использую MySQL с InnoDB и что Celery не подходит для этого решения.


person ralphje    schedule 17.03.2011    source источник
comment
Каковы значения job.status и cjob.status? Какие бывают типы?   -  person dappawit    schedule 18.03.2011
comment
Это одиночные символы: Job.EXECUTING == 'E'. Запрос в строке 5 действительно исключает задания со статусом «E» (фактически, он принимает только те, которые имеют статус «W» или «N»).   -  person ralphje    schedule 24.03.2011


Ответы (1)


Проблема устранена обновлением транзакции вручную. Похоже, QuerySet не обновлялся с момента начала транзакции. Когда два QuerySet каким-то образом запускаются одновременно и одно задание выполняется в обоих QuerySet, это разбивает бегунов.

Прочитав этот ответ, я нашел решение: непосредственно перед return True транзакция фиксируется.

person ralphje    schedule 30.06.2011