Выполнение атомарного обновления первого экземпляра в QuerySet.

StackOverflow https://stackoverflow.com/questions/1999494

Вопрос

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

Клиенты будут запрашивать в системе задания со статусом = '0' (ToDo), затем атомарным способом обновлять «самую старую» строку со статусом = '1' (Заблокировано) и получать идентификатор этой строки (для обновления задание с информацией о работнике, например, какая машина над ним работает и т. д.).

Основная проблема здесь заключается в том, что одновременно может обновляться любое количество клиентов.Решением было бы заблокировать около 20 строк со статусом = '0', обновить самую старую и впоследствии снова снять все блокировки.Я изучал TransactionMiddleware, но не понимаю, как это предотвратит обновление самого старого из них после того, как я его запрошу.

Я изучил QuerySet.update(), и это выглядит многообещающе, но в случае, если два клиента получают одну и ту же запись, статус просто обновится, и у нас будет два работника, работающих над одной и той же работой. ..Я действительно в растерянности.

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

Код:Результат = Работа

class Result(models.Model):
"""
Result: completed- and pending runs

'ToDo': job hasn't been acquired by a client
'Locked': job has been acquired
'Paused'
"""
# relations
run = models.ForeignKey(Run)
input = models.ForeignKey(Input)

PROOF_CHOICES = (
    (1, 'Maybe'),
    (2, 'No'),
    (3, 'Yes'),
    (4, 'Killed'),
    (5, 'Error'),
    (6, 'NA'),
)
proof_status = models.IntegerField(
    choices=PROOF_CHOICES,
    default=6,
    editable=False)

STATUS_CHOICES = (
    (0, 'ToDo'),
    (1, 'Locked'),
    (2, 'Done'),
)
result_status = models.IntegerField(choices=STATUS_CHOICES, editable=False, default=0)

# != 'None' => status = 'Done'
proof_data = models.FileField(upload_to='results/',
    null=True, blank=True)
# part of the proof_data
stderr = models.TextField(editable=False,
    null=True, blank=True)

realtime = models.TimeField(editable=False,
    null=True, blank=True)
usertime = models.TimeField(editable=False,
    null=True, blank=True)
systemtime = models.TimeField(editable=False,
    null=True, blank=True)

# updated when client sets status to locked
start_time = models.DateTimeField(editable=False)

worker = models.ForeignKey('Worker', related_name='solved',
    null=True, blank=True)
Это было полезно?

Решение

Чтобы объединить #2705 с вашим django, вам необходимо сначала загрузить его:

cd <django-dir>
wget http://code.djangoproject.com/attachment/ticket/2705/for_update_11366_cdestigter.diff?format=raw

затем перемотайте svn на необходимую версию django:

svn update -r11366

затем примените его:

patch -p1 for_update_11366_cdestigter.diff

Он сообщит вам, какие файлы были успешно исправлены, а какие нет.В маловероятном случае конфликтов вы можете исправить их вручную, просмотрев http://code.djangoproject.com/attachment/ticket/2705/for_update_11366_cdestigter.diff

Чтобы отменить патч, просто напишите

svn revert --recursive . 

Другие советы

Если ваш django работает на одной машине, есть гораздо более простой способ сделать это...Извините за псевдокод, поскольку детали вашей реализации не ясны.

from threading import Lock

workers_lock = Lock()

def get_work(request):
    workers_lock.acquire()
    try:
        # Imagine this method exists for brevity
        work_item = WorkItem.get_oldest()
        work_item.result_status = 1
        work_item.save()
    finally:
        workers_lock.release()

    return work_item

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

Другой вариант — вернуть строки, открытые на момент запроса, но затем проверять их снова всякий раз, когда клиент пытается получить задание для работы.Когда клиент пытается обновить задание для работы над ним, сначала будет выполнена проверка, доступно ли оно еще.Если кто-то другой уже захватил его, клиенту будет отправлено уведомление.Это позволяет всем клиентам видеть все задания в виде снимков, но если они постоянно получают последнюю версию, клиенты могут постоянно получать уведомления о том, что задание уже используется.Может быть, вы имеете в виду именно состояние гонки?

Один из способов обойти это — вернуть клиентам задания в определенных группах, чтобы они не всегда получали одни и те же списки.Например, разбить их по географическим регионам или даже просто случайным образом.Например, каждый клиент может иметь идентификатор от 0 до 9.Возьмите модификацию идентификатора заданий и отправьте клиенту те задания с той же конечной цифрой.Однако не ограничивайте это только этими работами, поскольку вы не хотите, чтобы были рабочие места, которых вы не можете достичь.Например, если у вас есть клиенты 1, 2 и 3 и работа 104, то никто не сможет до нее добраться.Таким образом, если вакансий с правильными конечными цифрами недостаточно, задания начнут возвращаться с другими цифрами, просто чтобы заполнить список.Возможно, вам придется поиграть с точным алгоритмом, но, надеюсь, это даст вам представление.

То, как вы блокируете строки в своей базе данных для их обновления и/или отправки уведомлений, во многом зависит от вашей СУБД.В MS SQL Server вы можете красиво обернуть всю эту работу в хранимую процедуру, если вмешательство пользователя в ее середине не требуется.

Надеюсь, это поможет.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top