Атомное состояние хранения в Python? - PullRequest
0 голосов
/ 19 ноября 2010

Я работаю над проектом на ненадежной системе, которая, как я предполагаю, может в любой момент провалиться. Что я хочу гарантировать, так это то, что если я запишу write_state, и машина потерпит неудачу в середине операции, read_state будет либо читать действительное состояние, либо вообще не будет состояния. Я реализовал что-то, что, я думаю, будет работать ниже - меня интересует критика этого или альтернативных решений, если кто-то знает об этом.

Моя идея:

import hashlib, cPickle, os

def write_state(logname, state):
    state_string = cPickle.dumps(state, cPickle.HIGHEST_PROTOCOL)
    state_string += hashlib.sha224(state_string).hexdigest()

    handle = open('%s.1' % logname, 'wb')
    handle.write(state_string)
    handle.close()

    handle = open('%s.2' % logname, 'wb')
    handle.write(state_string)
    handle.close()

def get_state(logname):
    def read_file(name):
        try:
            f = open(name,'rb')
            data = f.read()
            f.close()
            return data
        except IOError:
            return ''
    def parse(data):
        if len(data) < 56:
            return (None, '', False)
        hash = data[-56:]
        data = data[:-56]
        valid = hashlib.sha224(data).hexdigest() == hash
        try:
            parsed = cPickle.loads(data)
        except cPickle.UnpicklingError:
            parsed = None
        return (parsed, valid)

    data1,valid1 = parse(read_file('%s.1'%logname))
    data2,valid2 = parse(read_file('%s.2'%logname))

    if valid1 and valid2:
        return data1
    elif valid1 and not valid2:
        return data1
    elif valid2 and not valid1:
        return data2
    elif not valid1 and not valid2:
        raise Exception('Theoretically, this never happens...')

например:.

write_state('test_log', {'x': 5})
print get_state('test_log')

Ответы [ 5 ]

3 голосов
/ 19 ноября 2010

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

Существует несколько операций с файловой системой, которые гарантированно являются атомарными: переименование файла поверх другого - это одно, поскольку файл будет находиться в том или ином месте. Однако, что касается POSIX, он не гарантирует, что перемещение будет выполнено до того, как содержимое файла попадет на диск, а это означает, что оно только дает вам блокировку.

Файловые системы Linux принудили, чтобы содержимое файла попадало на диск до атомарного перемещения (но не синхронно), так что это делает то, что вы хотите. ext4 на короткое время нарушил это предположение, что делает эти файлы с большей вероятностью пустыми. Это было широко расценено как движение члена , и с тех пор исправлено.

В любом случае, правильный способ сделать это: создать временный файл в том же каталоге (так, чтобы он находился в той же файловой системе); написать новые данные; fsync временный файл; переименуйте его поверх предыдущей версии. Это настолько атомарно, насколько может гарантировать ОС. Это также дает вам долговечность за счет раскручивания дисков, поэтому разработчики приложений предпочитают не использовать fsync и не вносить в черный список версии ext4, вызывающие проблемы.

2 голосов
/ 19 ноября 2010

Я добавлю еретический ответ: как насчет использования sqlite? Или, возможно, bsddb, однако это кажется устаревшим и вам придется использовать сторонний модуль.

1 голос
/ 19 ноября 2010

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

По этой схеме ваши читатели не видят файл, пока состояние не станет нормальным.Операция связи является атомарной.Вы можете отменить связь временного имени после того, как успешно связались с «готовым» именем.Есть несколько дополнительных складок, которые нужно обработать, если вам нужно гарантировать семантику для старых версий NFS без зависимости от демонов блокировки.

1 голос
/ 19 ноября 2010

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

Контрольный файл имеет глобальный счетчик транзакций и хэш-код или другую контрольную сумму.Это небольшой файл размером в один физический блок.Одна запись на уровне ОС.

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

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

  1. Записать все ожидающие транзакции в простой журнал.Там есть порядковый номер и содержание изменения.

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

  3. Выполните ожидаемое обновление для целевого файла.Найдите начало и обновите счетчик и контрольную сумму.Если это не удается, контрольный файл имеет счетчик на один больше, чем целевой файл.Целевой файл поврежден.Когда это работает, последняя зарегистрированная транзакция, контрольный файл и целевой файл все согласовывают порядковый номер.

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

0 голосов
/ 19 ноября 2010

Я думаю, вы можете упростить несколько вещей

def read_file(name):
    try:
        with open(name,'rb') as f
            return f.read()
    except IOError:
        return ''

if valid1:
    return data1
elif valid2:
    return data2
else:
    raise Exception('Theoretically, this never happens...')

Возможно, вам не нужно все время записывать оба файла, просто напишите file2 и переименуйте его в file1.

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

...