2010-01-04 1 views
0

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

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

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

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

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

Код: Result = Работа

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) 

ответ

1

Слить # 2705 в вашем Джанго, вам необходимо скачать его первая:

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

затем перемотки Svn до нужной Джанго версии:

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 . 
+0

Спасибо Энтони, это отличная помощь. Я попробую это сразу. – Paddie

0

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

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

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

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

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

+0

Как мы это делали в старой системе, была блокировка ок. 20 строк, соответствующих статусу = '0', выбор самого старого и т. Д., Но это действительно привело к некоторым условиям гонки, которые были нечетными, и мне было поручено их устранить. Кроме того, я бы очень хотел сделать это в стиле Django-ish. Мы используем PostgreSQL в качестве бэкэнда БД, и если нам нужно перейти на пользовательский SQL, пусть будет так, но есть ли способ сделать то, что я хочу сделать, все еще гарантируя атомарность? У нас есть работы в категориях, означающие, что только подмножество работников получает доступ к любому «набору» результатов «ToDo» в любой момент времени. – Paddie

1

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

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

Ха, это мышление вне коробки! Любить это! :) – Paddie

Смежные вопросы