문제

바이 어떤 과오에서 용어에 기초하고 있습니다.특히,내가 사용하고 관계형 데이터베이스 용어입니다.

의 숫자가 지속적인 키 값을 저장함 CouchDB카산드라, 과 함께,다른 많은 프로젝트입니다.

일반적인 논쟁에 대한 그들은하지 않는 것이 일반적으로 허용 원자 트랜잭션에서 여러 행 또는 테이블이 있습니다.내가 궁금해 있는 경우의 일반적인 접근을 해결할 것이 이 문제를 해결합니다.

예를 들어의 상황을 설정의 은행 계좌입니다.우리는 어떻게 이동 하나의 은행에서 돈을 계정에 또 다른?을 경우 각각 은행 계좌,행 우리는 업데이트하고 싶은 두 행 일부분으로 동일한 트랜잭션의 감소,값에서 하나 증가하는 값이다.

하나 분명한 방법은 별도의 테이블을 설명하는 트랜잭션이 있습니다.그런 다음,이동하는 하나의 은행에서 돈을 계정에 또 다른로 구성되어 있 간단히 삽입하는 새로운 행로입니다.우리는 저장하지 않음 현재의 균형을 두 개의 은행 계좌와 대신에 의존 합산하는 모든 적절한 행 거래에서 테이블.그것은 쉽게 상상하는 것이 너무 많은 일,그러나;은행할 수 있는 비용과 개인이 은행 계좌 수 있 신속하게 여러 가지 천'트랜잭션'연결되어 있습니다.

숫자(모든?) 의 키-값 저장에'목록을 다시'동작하는 경우 기본 데이터가 변경되기 때문 당신이 마지막으로 그것을 잡았다.가능성이 될 수 있는 시뮬레이션하는 데 사용되는 트랜잭션 처리,다음할 수 있음을 나타내는 특정한 분야이 잠겨 있습니다.거기에 몇 가지 명백한 문제와 이와 같은 방법을 적용했다.

다른 어떤 아이디어가?그것은 전적으로 가능한 나 접근은 단순히 잘못되었고 나는 아직 싸서 제 두뇌의 주위에 새로운 스스로의 생각을 가지고 있습니다.

도움이 되었습니까?

해결책

예를 들어, 값을 원자 적으로 업데이트하려면 하나의 문서 (관계 용어로 행) CouchDB에서 그렇게 할 수 있습니다. 다른 경쟁 클라이언트가 읽은 후 동일한 문서를 업데이트 한 경우 변경 사항을 커밋하려고 할 때 충돌 오류가 발생합니다. 그러면 새 값을 읽고 커밋을 업데이트하고 재지해야합니다. 불확실한 것이 있습니다 (아마도 많은 경합))이 프로세스를 반복해야 할 수도 있지만 커밋이 성공하면 원자 적으로 업데이트 된 잔액이있는 데이터베이스에 문서가 보장됩니다.

두 잔액을 업데이트 해야하는 경우 (예 : 한 계정에서 다른 계정으로 전송)를 사용하면 별도의 트랜잭션 문서 (효과적으로 행이 트랜잭션 인 다른 테이블)를 사용해야합니다. . 그건 그렇고 이것은 일반적인 부기 관행입니다. CouchDB는 필요에 따라보기를 계산하기 때문에 실제로 해당 계정을 나열하는 트랜잭션에서 계정의 현재 금액을 계산하는 것이 여전히 매우 효율적입니다. CouchDB에서는 계정 번호를 키로 방출 한 맵 함수와 트랜잭션 금액 (들어오는 데 긍정적, 나가는 경우 음수)을 사용합니다. 감소 기능은 단순히 각 키의 값을 합하여 동일한 키와 총 합을 방출합니다. 그런 다음 Group = true와 함께보기를 사용하여 계정 번호로 계정 잔액을 얻을 수 있습니다.

다른 팁

CouchDB는 잠금 및 원자 운영을 지원하지 않기 때문에 트랜잭션 시스템에 적합하지 않습니다.

은행 이체를 완료하려면 몇 가지 작업을 수행해야합니다.

  1. 거래를 검증하고 소스 계정에 충분한 자금이 있는지 확인하고, 두 계정이 열려 있고, 잠금되지 않았는지, 양호한 상태 등을 보장합니다.
  2. 소스 계정의 잔액을 줄입니다
  3. 대상 계정의 잔액을 늘리십시오

이러한 단계 중 하나가 계정의 잔액 또는 상태 사이에서 변경이 이루어지면 계정의 잔액 또는 상태가 제출 된 후에 무효화 될 수 있으며, 이는 이런 종류의 시스템에서 큰 문제입니다.

"전송"레코드를 삽입하고 맵/감소보기를 사용하여 최종 계정 잔액을 계산하는 곳에서 위에서 제안한 접근 방식을 사용하더라도, 여전히 소스 계정을 인출하지 않도록 보장 할 방법이 없습니다. 소스 계정 잔액 확인과 잔액을 확인한 후 두 개의 트랜잭션을 동시에 추가 할 수있는 트랜잭션 삽입 사이의 레이스 조건.

그래서 ... 직업에 잘못된 도구입니다. CouchDB는 아마도 많은 일에 능숙 할 것입니다. 그러나 이것은 실제로 할 수없는 일입니다.

편집 : 실제 세계의 실제 은행은 최종 일관성을 사용한다는 점에 주목할 가치가 있습니다. 은행 계좌를 오랫동안 인출하면 초과 인출 수수료를받습니다. 당신이 매우 좋았다면, 거의 동시에 두 개의 다른 ATM에서 돈을 인출하고 잔액을 확인하고 돈을 발급하고 거래를 기록하기위한 경주 조건이 있기 때문에 계정을 인출 할 수도 있습니다. 귀하가 귀하의 계정에 수표를 입금하면 잔액을 충돌 시키지만 실제로 소스 계정에 실제로 돈이 충분하지 않은 경우 "만약"한 기간 동안 그 자금을 보유합니다.

구체적인 예제를 제공하려면 (온라인으로 올바른 예제가 놀라운 것이 없기 때문에) : 다음은 다음과 같습니다. "원자 은행 잔고 이체"CouchDB에서 (동일한 주제에 대한 내 블로그 게시물에서 크게 복사 : : http://blog.codekills.net/2014/03/13/atomic-bank-balance-transfer-with-couchdb/)

첫째, 문제에 대한 간단한 요약 : 계정간에 돈을 이체 할 수있는 은행 시스템을 어떻게 유효하지 않거나 무의미한 잔액을 남길 수있는 레이스 조건이 없도록 설계 될 수 있습니까?

이 문제에는 몇 가지 부분이 있습니다.

첫째 : 트랜잭션 로그. 계정 잔액을 단일 레코드 또는 문서로 저장하는 대신 - {"account": "Dave", "balance": 100} - 계정의 잔액은 해당 계정에 대한 모든 크레딧과 차감을 요약하여 계산됩니다. 이러한 크레딧 및 사원은 거래 로그에 저장되며 다음과 같은 것처럼 보일 수 있습니다.

{"from": "Dave", "to": "Alex", "amount": 50}
{"from": "Alex", "to": "Jane", "amount": 25}

그리고 균형을 계산하기위한 CouchDB 맵-레디스 기능은 다음과 같은 것처럼 보일 수 있습니다.

POST /transactions/balances
{
    "map": function(txn) {
        emit(txn.from, txn.amount * -1);
        emit(txn.to, txn.amount);
    },
    "reduce": function(keys, values) {
        return sum(values);
    }
}

완전성을 위해 다음은 잔액 목록입니다.

GET /transactions/balances
{
    "rows": [
        {
            "key" : "Alex",
            "value" : 25
        },
        {
            "key" : "Dave",
            "value" : -50
        },
        {
            "key" : "Jane",
            "value" : 25
        }
    ],
    ...
}

그러나 이것은 명백한 질문을 남깁니다. 오류는 어떻게 처리됩니까? 누군가가 균형보다 더 큰 이적을 만들려고한다면 어떻게됩니까?

COUCHDB (및 유사한 데이터베이스)를 사용하면 이러한 종류의 비즈니스 로직 및 오류 처리가 응용 프로그램 수준에서 구현되어야합니다. 순진하게, 그러한 함수는 다음과 같을 수 있습니다.

def transfer(from_acct, to_acct, amount):
    txn_id = db.post("transactions", {"from": from_acct, "to": to_acct, "amount": amount})
    if db.get("transactions/balances") < 0:
        db.delete("transactions/" + txn_id)
        raise InsufficientFunds()

그러나 트랜잭션 삽입과 업데이트 된 잔액 확인간에 응용 프로그램이 충돌하면 데이터베이스가 일관되지 않은 상태에 남아있을 것입니다. 발신자는 부정적인 잔액이 남아있을 수 있으며 이전에는 존재하지 않은 돈으로 수신자가 남을 수 있습니다.

// Initial balances: Alex: 25, Jane: 25
db.post("transactions", {"from": "Alex", "To": "Jane", "amount": 50}
// Current balances: Alex: -25, Jane: 75

어떻게 해결할 수 있습니까?

시스템이 일관되지 않은 상태가 아닌지 확인하려면 각 거래에 두 가지 정보를 추가해야합니다.

  1. 거래가 생성 된 시간 ( 엄격한 총 주문 거래) 및

  2. 상태 - 거래가 성공했는지 여부.

또한 계정의 가용 잔액을 반환하는 두 가지 조회 (즉, 모든 "성공적인"거래의 합) 및 가장 오래된 "보류중인"트랜잭션을 반환하는 두 가지 조회가 필요합니다.

POST /transactions/balance-available
{
    "map": function(txn) {
        if (txn.status == "successful") {
            emit(txn.from, txn.amount * -1);
            emit(txn.to, txn.amount);
        }
    },
    "reduce": function(keys, values) {
        return sum(values);
    }
}

POST /transactions/oldest-pending
{
    "map": function(txn) {
        if (txn.status == "pending") {
            emit(txn._id, txn);
        }
    },
    "reduce": function(keys, values) {
        var oldest = values[0];
        values.forEach(function(txn) {
            if (txn.timestamp < oldest) {
                oldest = txn;
            }
        });
        return oldest;
    }

}

전송 목록은 이제 다음과 같습니다.

{"from": "Alex", "to": "Dave", "amount": 100, "timestamp": 50, "status": "successful"}
{"from": "Dave", "to": "Jane", "amount": 200, "timestamp": 60, "status": "pending"}

다음으로, 응용 프로그램은 유효한지 확인하기 위해 각 보류중인 거래를 확인하여 거래를 해결 한 다음 "PENDENT"에서 "성공"또는 "거부"로의 상태를 업데이트하여 거래를 해결할 수있는 기능이 있어야합니다.

def resolve_transactions(target_timestamp):
    """ Resolves all transactions up to and including the transaction
        with timestamp `target_timestamp`. """
    while True:
        # Get the oldest transaction which is still pending
        txn = db.get("transactions/oldest-pending")
        if txn.timestamp > target_timestamp:
            # Stop once all of the transactions up until the one we're
            # interested in have been resolved.
            break

        # Then check to see if that transaction is valid
        if db.get("transactions/available-balance", id=txn.from) >= txn.amount:
            status = "successful"
        else:
            status = "rejected"

        # Then update the status of that transaction. Note that CouchDB
        # will check the "_rev" field, only performing the update if the
        # transaction hasn't already been updated.
        txn.status = status
        couch.put(txn)

마지막으로, 응용 프로그램 코드 바르게 전송 수행 :

def transfer(from_acct, to_acct, amount):
    timestamp = time.time()
    txn = db.post("transactions", {
        "from": from_acct,
        "to": to_acct,
        "amount": amount,
        "status": "pending",
        "timestamp": timestamp,
    })
    resolve_transactions(timestamp)
    txn = couch.get("transactions/" + txn._id)
    if txn_status == "rejected":
        raise InsufficientFunds()

몇 가지 메모 :

  • 간결성을 위해,이 특정 구현은 CouchDB의지도 레디스에서 어느 정도의 원자력을 가정합니다. 코드를 업데이트하여 해당 가정에 의존하지 않도록 독자에게 연습으로 남아 있습니다.

  • 마스터/마스터 복제 또는 CouchDB의 문서 동기화는 고려되지 않았습니다. 마스터/마스터 복제 및 동기화는이 문제를 훨씬 더 어렵게 만듭니다.

  • 실제 시스템에서 사용합니다 time() 충돌이 발생할 수 있으므로 좀 더 엔트로피를 사용하는 것이 좋은 생각 일 수 있습니다. 아마도 "%s-%s" %(time(), uuid()), 또는 문서 사용 _id 순서대로. 시간을 포함시킬 필요는 없지만 여러 요청이 거의 동시에 들어 오면 논리를 유지하는 데 도움이됩니다.

BerkeleyDB와 LMDB는 모두 산 거래를 지원하는 주요 가치 저장소입니다. BDB에서 TXN은 선택 사항이며 LMDB는 트랜잭션으로 만 작동합니다.

일반적인 논쟁에 대한 그들은하지 않는 것이 일반적으로 허용 원자 트랜잭션에서 여러 행 또는 테이블이 있습니다.내가 궁금해 있는 경우의 일반적인 접근을 해결할 것이 이 문제를 해결합니다.

많은 현대적인 데이터를 저장하지 않 원자 지원 멀티-키 업데이트(트랜잭션이)상자의 그러나 그들의 대부분을 제공하는 기본 형식을 구축 할 수 있도록 산 클라이언트 측의 트랜잭션이 있습니다.

는 경우에 데이터 저장소 지원당 키 linearizability 및 compare-and-swap 또 테스트 및 설정 작업에 다음 그것을 구현하기에 충분 serializable 트랜잭션이 있습니다.예를 들어,이 접근법은 사용 구글의 여과기 고서 CockroachDB 데이터베이스입니다.

에 나 블로그를 만들었습니다 단계별 시각화의 serializable 크로 분할 클라이언트 측 거래, 을 설명하고,주요 사용하는 경우 제공된 링크 개의 알고리즘이 있습니다.나는 그것을 이해하는 데 도움이 될 것입을 구현하는 방법들을 위해 당신에게 데이터를 저장합니다.

중 데이터를 저장하는 지원 당 키 linearizability 및 CAS 습니다:

  • 카산드라진 거래량
  • Riak 으로 일관하는 버킷
  • RethinkDB
  • 사육사
  • Etdc
  • DynamoDB
  • MongoDB

방법으로,당신은 잘 읽 Committed 격리 수준이 다음 그것을 살펴보 경사로 거래 Peter Bailis.할 수 있도 구현에 대해 동일한 세트의 데이터를 저장합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top