Одновременно модифицируйте разные ключи в 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