Одновременно модифицируйте разные ключи в ZODB

Я использую ZODB в качестве постоянного хранилища для объектов, которые будут изменены через веб-сервис. Ниже приведен пример, до которого я сократил проблему. Функция приращения - это то, что вызывается из нескольких потоков. Моя проблема в том, что когда приращение вызывается одновременно из двух потоков, для разных ключей, я получаю ошибку конфликта.

Я полагаю, что можно решить эту проблему, по крайней мере, до тех пор, пока различные ключи будут изменены должным образом? Если так, мне не удалось найти пример того, как... (zodb-документация, похоже, несколько разбросана по разным сайтам:/)

Рад любым идеям...

import time
import transaction
from ZODB.FileStorage import FileStorage
from ZODB.DB import DB
from ZODB.POSException import ConflictError

def test_db():
    store = FileStorage('zodb_storage.fs')
    return DB(store)

db_test = test_db()     

# app here is a flask-app
@app.route('/increment/<string:key>')
def increment(key):
    '''increment the value of a certain key'''

    # open connection
    conn = db_test.open()
    # get the current value:
    root = conn.root()
    val = root.get(key,0)    

    # calculate new value 
    # in the real application this might take some seconds
    time.sleep(0.1)
    root[key] = val + 1     

    try:
        transaction.commit()
        return '%s = %g' % (key, val)
    except ConflictError:
        transaction.abort()
        return 'ConflictError :-('

1 ответ

Решение

У вас есть два варианта: реализовать разрешение конфликтов или повторить коммит с новыми данными.

Разрешение конфликтов применимо только к пользовательским типам, которые вы храните в ZODB, и может быть применено только в том случае, если вы знаете, как объединить изменения в недавно измененное состояние.

ZODB ищет _p_resolveConflict() метод пользовательских типов и вызывает этот метод со старым состоянием, сохраненным состоянием, с которым вы находитесь в конфликте, и новым состоянием, которое вы пытались зафиксировать; Вы должны вернуть объединенное состояние. Для простого счетчика, как в вашем примере, это было бы так же просто, как обновить сохраненное состояние с изменением между старым и новым состояниями:

class Counter(Persistent):
    def __init__(self, start=0):
        self._count = start

    def increment(self):
        self._count += 1
        return self._count

    def _p_resolveConflict(self, old, saved, new):
        # default __getstate__ returns a dictionary of instance attributes
        saved['_count'] += new['_count'] - old['_count']
        return saved

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

max_retries = 10
retry = 0

conn = db_test.open()
root = conn.root()

while retry < max_retries:
    val = root.get(key,0)    
    time.sleep(0.1)
    root[key] = val + 1

    try:
        transaction.commit()
        return '%s = %g' % (key, val)
    except ConflictError:
        retry += 1

raise CustomExceptionIndicatingTooManyRetries
Другие вопросы по тегам