CouchDB에서 트랜잭션과 잠금을 할 수 있습니까?


81

트랜잭션 (시작, 커밋 또는 롤백), 잠금 (업데이트 선택)을 수행해야합니다. 문서 모델 DB에서 어떻게 할 수 있습니까?

편집하다:

사례는 다음과 같습니다.

  • 경매 사이트를 운영하고 싶습니다.
  • 그리고 직접 구매하는 방법도 생각합니다.
  • 직접 구매에서는 품목 레코드의 수량 필드를 줄여야하지만 수량이 0보다 큰 경우에만 가능합니다. 이것이 내가 잠금과 트랜잭션이 필요한 이유입니다.
  • 잠금 및 / 또는 트랜잭션없이이 문제를 해결하는 방법을 모르겠습니다.

CouchDB로이 문제를 해결할 수 있습니까?

답변:


145

아니요. CouchDB는 "낙관적 동시성"모델을 사용합니다. 가장 간단한 용어로 이것은 업데이트와 함께 문서 버전을 보내고 현재 문서 버전이 보낸 것과 일치하지 않으면 CouchDB가 변경을 거부한다는 것을 의미합니다.

정말 믿을 수 없을 정도로 간단합니다. CouchDB에 대한 많은 일반 트랜잭션 기반 시나리오를 재구성 할 수 있습니다. 하지만 CouchDB를 배울 때 RDBMS 도메인 지식을 버릴 필요가 있습니다. Couch를 SQL 기반 세계로 만들려고 시도하는 것보다 더 높은 수준에서 문제에 접근하는 것이 도움이됩니다.

재고 추적

설명하신 문제는 주로 재고 문제입니다. 항목을 설명하는 문서가 있고 "사용 가능한 수량"필드가 포함 된 경우 다음과 같은 동시성 문제를 처리 할 수 ​​있습니다.

  1. 문서를 검색하고 _revCouchDB가 보내는 속성을 기록해 둡니다.
  2. 수량 필드가 0보다 큰 경우 감소
  3. _rev속성을 사용하여 업데이트 된 문서를 다시 보내기
  4. (가) 경우 _rev현재 저장된 번호와 일치, 할!
  5. 충돌이있는 경우 ( _rev일치하지 않는 경우 ) 최신 문서 버전을 검색합니다.

이 경우 고려해야 할 두 가지 가능한 실패 시나리오가 있습니다. 최신 문서 버전의 수량이 0 인 경우 RDBMS에서와 마찬가지로 처리하고 사용자에게 구매하고 싶은 것을 실제로 구매할 수 없다고 경고합니다. 최신 문서 버전의 수량이 0보다 큰 경우 업데이트 된 데이터로 작업을 반복하고 처음부터 다시 시작하면됩니다. 이로 인해 RDBMS보다 약간 더 많은 작업을 수행해야하며 충돌하는 업데이트가 자주 발생하면 약간 짜증이 날 수 있습니다.

이제 제가 방금 제시 한 대답은 RDBMS에서와 거의 같은 방식으로 CouchDB에서 작업을 수행 할 것이라는 전제를 전제로합니다. 이 문제에 조금 다르게 접근 할 수 있습니다.

모든 설명자 데이터 (이름, 사진, 설명, 가격 등)가 포함 된 "마스터 제품"문서로 시작하겠습니다. 그런 다음 product_key및에 대한 필드가있는 각 특정 인스턴스에 대한 "인벤토리 티켓"문서를 추가합니다 claimed_by. 당신이 망치의 모델을 판매하고, 판매에 20을 가지고 있다면, 당신은 같은 키를 사용하여 문서에있을 수 있습니다 hammer-1, hammer-2사용 가능한 각 망치를 표현하기 위해, 등.

그런 다음 "총계"를 볼 수있는 축소 기능과 함께 사용 가능한 해머 목록을 제공하는 뷰를 만듭니다. 이것들은 커프에서 완전히 벗어 났지만 작업 뷰가 어떻게 생겼는지에 대한 아이디어를 제공해야합니다.

지도

function(doc) 
{ 
    if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) { 
        emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev }); 
    } 
}

이것은 제품 키별로 사용 ​​가능한 "티켓"목록을 제공합니다. 누군가 망치를 사고 싶을 때이 그룹을 잡고 업데이트를 보낼 때까지 반복 할 수 있습니다 ( id및 사용 _rev).

줄이다

function (keys, values, combine) {
    return values.length;
}

이 축소 기능은 청구되지 않은 총 inventory_ticket항목 수를 반환 하므로 구매할 수있는 "망치"수를 알 수 있습니다.

주의 사항

이 솔루션은 귀하가 제시 한 특정 문제에 대해 대략 3.5 분의 총 사고를 나타냅니다. 더 나은 방법이있을 수 있습니다! 즉, 충돌하는 업데이트를 크게 줄이고 새로운 업데이트로 충돌에 대응할 필요성을 줄입니다. 이 모델에서는 기본 제품 항목의 데이터를 변경하려는 여러 사용자가 없습니다. 최악의 경우 여러 명의 사용자가 단일 티켓을 요청하게되며,보기에서 여러 사용자를 확보 한 경우 다음 티켓으로 이동하여 다시 시도하면됩니다.

참조 : https://wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F


4
순서대로 청구하려는 '티켓'을 갖는 것이 단순히 읽기 / 수정 / 쓰기를 다시 시도하여 마스터 엔터티를 업데이트하는 것보다 중요한 개선 사항인지는 확실하지 않습니다. 특히 재고가 많은 경우 추가 오버 헤드의 가치가없는 것 같습니다.
Nick Johnson

4
내 관점에서 티켓 컨벤션은 구축하기가 "더 간단"합니다. 마스터 항목에서 실패한 업데이트는 문서를 다시로드하고 작업을 다시 수행 한 다음 저장해야합니다. 티켓을 사용하면 더 많은 데이터를 요청하지 않고도 무언가를 시도하고 "청구"할 수 있습니다.
MrKurt

또한 어떤 종류의 오버 헤드를 걱정하는지에 따라 다릅니다. 경쟁이 치열 해 지거나 추가 스토리지 요구 사항이 있습니다. 티켓이 구매 기록으로도 두 배가 될 수 있다는 점을 감안할 때 당신이 생각하는 것만 큼 많은 저장 문제가 있을지 모르겠습니다.
MrKurt

2
제품 문서의 수량 필드를 편집하고 있습니다. 예를 들어 수량이 2K이면 수천 개의 "티켓"을 만들어야합니다. 그런 다음 수량을 줄이고 티켓을 삭제해야합니다. 나를 위해 완전히 긴장되지 않은 것 같습니다. 기본 사용 사례에서 많은 골칫거리. 뭔가 빠졌을 수도 있지만 이전에 제거 된 트랜잭션 동작을 다시 가져 오지 않는 이유는 _bulk_docs? reject_on_conflict = true와 같이 선택 사항으로 만드십시오. 단일 마스터 구성에서 매우 유용합니다.
Sam

3
@mehaase : 이것을 읽으십시오 : guide.couchdb.org/draft/recipes.html , 대답은 "데이터를 변경하지 않고 새로 추가하기 만하면됩니다"라는 couchdb의 내부 데이터 구조로 귀결됩니다. 귀하의 시나리오에서는 차변을 위해 계정에서 미착 계정으로 하나의 (원자 적) 트랜잭션을 생성하고 미착용 계정에서 두 번째 (원자 적) 트랜잭션을 앞 (또는 뒤로) 생성하는 것을 의미합니다. 그것이 실제 은행이하는 방법입니다. 모든 단계는 항상 문서화됩니다.
Fabian Zeindl

26

MrKurt의 대답을 확장합니다. 많은 시나리오의 경우 재고 티켓을 순서대로 교환 할 필요가 없습니다. 첫 번째 티켓을 선택하는 대신 나머지 티켓에서 무작위로 선택할 수 있습니다. 많은 수의 티켓과 많은 수의 동시 요청이 주어지면 첫 번째 티켓을 얻으려는 모든 사람에 비해 해당 티켓에 대한 경합이 훨씬 줄어 듭니다.


21

restfull 트랜잭션을위한 디자인 패턴은 시스템에 "긴장"을 만드는 것입니다. 은행 계좌 거래의 인기있는 사용 사례의 경우 관련된 두 계정의 합계를 업데이트해야합니다.

  • "계정 11223에서 계정 88733로 USD 10 이전"거래 문서를 생성합니다. 이것은 시스템에 긴장을 만듭니다.
  • 모든 거래 문서에 대한 긴장 스캔을 해결하고
    • 소스 계정이 아직 업데이트되지 않은 경우 소스 계정을 업데이트합니다 (-10 USD)
    • 출처 계정이 업데이트되었지만 거래 문서에이를 표시하지 않는 경우 거래 문서를 업데이트합니다 (예 : 문서에서 "sourcedone"플래그 설정).
    • 대상 계정이 아직 업데이트되지 않은 경우 대상 계정을 업데이트합니다 (+10 USD)
    • 대상 계정이 갱신되었지만 거래 문서에이를 표시하지 않는 경우 거래 문서를 갱신하십시오.
    • 두 계정이 모두 업데이트 된 경우 거래 문서를 삭제하거나 감사를 위해 보관할 수 있습니다.

장력 스캔은 시스템의 장력 시간을 짧게 유지하기 위해 모든 "장력 문서"에 대한 백엔드 프로세스에서 수행되어야합니다. 위의 예에서 첫 번째 계정이 업데이트되었지만 두 번째 계정이 아직 업데이트되지 않은 경우 짧은 시간 동안 불일치가 예상됩니다. 이것은 Couchdb가 배포 된 경우 최종 일관성을 처리하는 것과 동일한 방식으로 고려해야합니다.

또 다른 가능한 구현은 트랜잭션의 필요성을 완전히 피하는 것입니다. 긴장 문서를 저장하고 관련된 모든 긴장 문서를 평가하여 시스템 상태를 평가하기 만하면됩니다. 위의 예에서 이것은 계정의 합계가이 계정이 관련된 거래 문서의 합계 값으로 만 결정된다는 것을 의미합니다. Couchdb에서는이를 맵 / 축소 뷰로 매우 멋지게 모델링 할 수 있습니다.


5
그러나 계좌가 인출되었지만 긴장 문서가 변경되지 않은 경우는 어떻습니까? 이 두 지점 사이의 실패 시나리오는 원자가 아닌 경우 영구적 인 불일치를 유발할 것입니다. 프로세스에 대한 어떤 것은 원자 적이어야합니다. 그것이 트랜잭션의 요점입니다.
Ian Varley

예, 맞습니다.이 경우 긴장이 해결되지는 않지만 불일치가있을 것입니다. 그러나 불일치는 장력 문서에 대한 다음 스캔에서이를 감지 할 때까지 일시적입니다. 이것은 시간에 관한 일종의 최종 일관성의 거래입니다. 먼저 소스 계정을 decrent하고 나중에 대상 계정을 증가시키는 한 허용 될 수 있습니다. 그러나주의하십시오 : 긴장 문서는 REST 위에 ACID 트랜잭션을 제공하지 않습니다. 그러나 순수 REST와 ACID 사이의 좋은 절충안이 될 수 있습니다.
ordnungswidrig

4
모든 장력 문서에 타임 스탬프가 있고 계정 문서에 '마지막 장력 적용'필드 또는 적용된 장력 목록이 있다고 상상해보십시오. 소스 계정에서 인출 할 때 'last-tension-applied'필드도 업데이트됩니다. 이 두 작업은 동일한 문서에 있기 때문에 원자 적입니다. 대상 계정에도 유사한 필드가 있습니다. 이렇게하면 시스템은 항상 어떤 장력 문서가 어떤 계정에 적용되었는지 알 수 있습니다.
Jesse Hallett

1
원본 / 대상 문서가 이미 업데이트되었는지 여부를 감지하는 방법은 무엇입니까? 1 단계 이후에 실패한 다음 다시 실행되고 다시 실패하는 경우, 계속해서 소스 계정을 공제하게됩니까?
wump

1
@wump : 장력 문서가 계정에 적용되었음을 기록해야합니다. 예를 들어 장력 문서 ID를 계정의 목록 속성에 추가합니다. 장력 문서가 건드린 모든 계정이 업데이트되면 장력 문서를 "완료"로 표시하거나 삭제합니다. 이후 모든 계정의 목록에서 문서 ID를 삭제할 수 있습니다.
ordnungswidrig 2010

6

아니요, CouchDB는 클러스터 / 복제 환경에서 원자 적 작업을 지원하지 않기 때문에 일반적으로 트랜잭션 애플리케이션에 적합하지 않습니다.

CouchDB는 확장 성을 위해 트랜잭션 기능을 희생했습니다. 원자 적 작업을 수행하려면 확장 성을 제한하는 중앙 조정 시스템이 필요합니다.

CouchDB 인스턴스가 하나만 있거나 특정 문서를 수정하는 모든 사람이 동일한 CouchDB 인스턴스에 연결되도록 보장 할 수 있다면 충돌 감지 시스템을 사용하여 위에서 설명한 방법을 사용하여 일종의 원 자성을 생성 할 수 있지만 나중에 클러스터로 확장하는 경우 또는 Cloudant와 같은 호스팅 된 서비스를 사용하면 고장이 나고 시스템의 해당 부분을 다시 실행해야합니다.

따라서 계정 잔액에 CouchDB가 아닌 다른 것을 사용하는 것이 좋습니다. 그렇게하는 것이 훨씬 쉬울 것입니다.


5

OP 문제에 대한 응답으로 Couch는 아마도 여기에서 최선의 선택이 아닐 것입니다. 뷰를 사용하는 것은 재고를 추적하는 좋은 방법이지만 0으로 고정하는 것은 다소 불가능합니다. 문제는 뷰의 결과를 읽고 "hammer-1"항목을 사용해도된다고 결정한 다음이를 사용하기위한 문서를 작성할 때 경쟁 조건입니다. 문제는보기의 결과가 망치 -1이 0 개를 초과하는 경우 망치를 사용하도록 문서를 작성하는 원자 적 방법이 없다는 것입니다. 100 명의 사용자가 모두 동시에 뷰를 쿼리하고 1 개의 hammer-1을 보면, 모두 망치 1을 사용하는 문서를 작성할 수 있으며 결과적으로 -99 hammer-1이 생성됩니다. 실제로 경쟁 조건은 상당히 작을 것입니다. DB가 localhost를 실행하고 있다면 정말 작습니다. 그러나 일단 확장하고 오프 사이트 DB 서버 또는 클러스터가 있으면 문제가 훨씬 더 두드러집니다.

MrKurt의 응답에 대한 업데이트 (날짜 일 수도 있고 일부 CouchDB 기능을 인식하지 못했을 수도 있음)

뷰는 CouchDB에서 잔액 / 재고와 같은 것을 처리하는 좋은 방법입니다.

뷰에서 docid 및 rev를 내보낼 필요가 없습니다. 보기 결과를 검색 할 때 둘 다 무료로 얻을 수 있습니다. 특히 사전과 같은 장황한 형식으로 내 보내면 뷰가 불필요하게 커집니다.

재고 잔고를 추적하기위한 간단한보기는 다음과 같이 보일 것입니다.

function( doc )
{
    if( doc.InventoryChange != undefined ) {
        for( product_key in doc.InventoryChange ) {
            emit( product_key, 1 );
        }
    }
}

감소 기능은 훨씬 더 간단합니다.

_sum

이것은 일치하는 키로 모든 행의 값을 합산 하는 내장 된 감소 함수 를 사용 합니다.

이보기에서 모든 문서에는 product_key를 총 인벤토리의 변경 사항에 매핑하는 "InventoryChange"멤버가있을 수 있습니다. 즉.

{
    "_id": "abc123",
    "InventoryChange": {
         "hammer_1234": 10,
         "saw_4321": 25
     }
}

hammer_1234 10 개와 saw_4321 25 개를 추가합니다.

{
    "_id": "def456",
    "InventoryChange": {
        "hammer_1234": -5
    }
}

인벤토리에서 망치 5 개를 태울 것입니다.

이 모델을 사용하면 데이터를 업데이트하지 않고 추가 만 할 수 있습니다. 이는 업데이트 충돌의 기회가 없음을 의미합니다. 데이터 업데이트의 모든 트랜잭션 문제가 사라집니다. :)

이 모델의 또 다른 좋은 점은 DB에있는 모든 문서가 인벤토리에서 항목을 추가하거나 뺄 수 있다는 것입니다. 이러한 문서에는 모든 종류의 다른 데이터가 포함될 수 있습니다. 입고 날짜 및 시간, 창고, 입고 직원 등에 대한 데이터가 포함 된 "Shipment"문서가있을 수 있으며 해당 문서가 InventoryChange를 정의하는 한 재고를 업데이트합니다. "Sale"문서, "DamagedItem"문서 등이 될 수 있습니다. 각 문서를 보면 매우 명확하게 읽습니다. 그리고 뷰는 모든 노력을 처리합니다.


흥미로운 전략. CouchDB newb로서 현재 해머 수를 계산하려면 해머에 대한 회사의 전체 재고 변경 내역 에 대해지도 / 감소를 수행해야합니다 . 이것은 수년간의 변화가 될 수 있습니다. 이 성능을 향상시키는 CouchDB의 내장 기능이 있습니까?
chadrik

예, CouchDB의 뷰는 연속적이고 지속적인 맵 / 축소와 같습니다. 큰 데이터 세트에서 처음부터 시작하려면 오래 걸리지 만 새 문서가 추가되면 기존보기 만 업데이트되고 전체보기를 다시 계산할 필요가 없다는 것이 맞습니다. 뷰에 대한 공간 및 CPU 요구 사항이 모두 있음을 명심하십시오. 또한 적어도 CouchDB를 전문적으로 작업했을 때 (몇 년이 지났습니다) 내장 된 reduce 기능 만 사용하는 것이 매우 중요했습니다. _합집합. 사용자 정의 Javascript 감소 기능이 매우 느 렸습니다
wallacer

3

실제로 어떤 방식 으로든 할 수 있습니다. HTTP 문서 API를 살펴보고 "단일 요청으로 여러 문서 수정"이라는 제목으로 스크롤하십시오.

기본적으로 URI / {dbname} / _ bulk_docs 에 대한 단일 게시 요청으로 여러 문서를 생성 / 업데이트 / 삭제할 수 있으며 모두 성공하거나 모두 실패합니다. 이 문서는이 동작이 향후 변경 될 수 있음을 경고합니다.

편집 : 예상대로 버전 0.9부터 대량 문서는 더 이상 이런 식으로 작동하지 않습니다.


이는 논의중인 상황, 즉 여러 사용자의 단일 문서에 대한 경합에서 실제로 도움이되지 않습니다.
Kerr

3
CouchDB 0.9부터 대량 업데이트의 의미가 변경되었습니다.
Barry Wark

0

트랜잭션에 SQlite 종류의 경량 솔루션을 사용하고 트랜잭션이 성공적으로 완료되면 복제하고 SQLite에 복제 된 것으로 표시합니다.

SQLite 테이블

txn_id    , txn_attribute1, txn_attribute2,......,txn_status
dhwdhwu$sg1   x                    y               added/replicated

성공적으로 복제 된 트랜잭션을 삭제할 수도 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.