2013-08-07 3 views
1

저는 웹 서비스를 통해 수정 될 객체의 영구 저장소로 ZODB를 사용하고 있습니다. 다음은 문제를 줄인 예입니다. increment-function은 여러 스레드에서 호출되는 함수입니다. 내 문제는 두 개의 스레드에서 서로 다른 키에 대해 increment가 동시에 호출 될 때 충돌 오류가 발생한다는 것입니다.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 :-(' 

답변

2

두 가지 옵션이 있습니다. 충돌 해결을 구현하거나 새로운 데이터로 커밋을 다시 시도하십시오.

Conflict resolution은 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 
+0

이 카운터의 충돌 해결이되지 않을해야'[ '_ 횟수'] 저장 + = 새 [ '_ 횟수'] - 옛 [ '_ 횟수']'있도록 increment()가 두 번 이상 호출되는 경우를 처리 할 수 ​​있습니까? – Duncan

+0

@Duncan : hrm, true. 실제로, 그게 바로'BTree.Length'가하는 일입니다. 제가 갱신 할 것입니다. –

+0

실제로, 제 문제는 병렬로 두 개의 다른 키를 변경하는 것에 관한 것이 었습니다. 예를 들어 두 개의 카운터 인스턴스가 있습니다. 이것은 "root"에 일반 숫자를 쓰는 대신 Counter 클래스를 도입하여 해결되었습니다. 사실, (mutabl) 루트가 실제로 변경되지 않는다는 사실 때문에 어느 것입니까? – sebastian