source

MongoDB의 트랜잭션 부족 문제를 어떻게 해결할 것인가?

ittop 2023. 4. 2. 11:54
반응형

MongoDB의 트랜잭션 부족 문제를 어떻게 해결할 것인가?

비슷한 질문이 있는 것은 알지만 일반 RDB로 다시 전환하라는 메시지가 있습니다.트랜잭션이 필요한 경우 또는 원자적인 조작 또는 2상 커밋을 사용하는 경우 MS 시스템.두 번째 솔루션이 최선의 선택인 것 같습니다.세 번째는 많은 것이 잘못될 수 있고 모든 면에서 테스트할 수 없기 때문에 따르지 않았으면 한다.나는 원자력 작전을 수행하기 위한 내 프로젝트를 리팩터링하는 데 어려움을 겪고 있다.이것이 저의 제한된 관점(지금까지 SQL 데이터베이스로만 작업해 왔습니다)에서 비롯된 것인지, 아니면 실제로 불가능한 것인지 알 수 없습니다.

저희 회사에서 MongoDB를 시범 테스트하고 싶습니다.비교적 간단한 프로젝트인 SMS 게이트웨이를 선택했습니다.이를 통해 소프트웨어에서 셀룰러 네트워크에 SMS 메시지를 보낼 수 있고 게이트웨이는 다른 통신 프로토콜을 통해 공급자와 통신하는 등 지저분한 작업을 수행할 수 있습니다.게이트웨이는 메시지의 과금도 관리합니다.서비스를 신청하는 모든 고객은 신용카드를 구입해야 합니다.시스템은 메시지를 보낼 때 사용자의 균형을 자동으로 줄이고 균형이 부족할 경우 액세스를 거부합니다.또, 서드파티 SMS 프로바이더의 고객이기 때문에, 그들과의 밸런스도 있을 수 있습니다.우리는 그것들도 추적해야 한다.

복잡성(외부 과금, SMS 전송 대기)을 줄이면 MongoDB에 필요한 데이터를 어떻게 저장할 수 있을까 고민하기 시작했습니다.SQL 월드에서 온 사용자용 테이블과 SMS 메시지용 테이블, 사용자 잔액에 대한 트랜잭션 저장 테이블을 별도로 만듭니다.예를 들어 MongoDB에 있는 모든 컬렉션에 대해 별도의 컬렉션을 만듭니다.

이 간단한 시스템에서 다음 단계를 사용하여 SMS를 전송하는 작업을 상상해 보십시오.

  1. 사용자의 잔액이 충분한지 확인합니다.크레딧이 충분하지 않은 경우 접근 거부

  2. 과 함께 에서는 메시지에 됩니다).status속성과 태스크는 배송을 위해 그것을 픽업하고 현재 상태에 따라 SMS의 가격을 설정합니다.)

  3. 발송된 메시지 비용만큼 사용자의 잔액을 줄이다

  4. 트랜잭션을 트랜잭션 컬렉션에 기록

그게 뭐가 문제죠?MongoDB는 하나의 문서에서만 원자 업데이트를 수행할 수 있습니다.이전 흐름에서는 어떤 오류가 슬금슬금 발생하여 메시지가 데이터베이스에 저장되지만 사용자의 밸런스가 업데이트되지 않거나 트랜잭션이 기록되지 않는 경우가 있습니다.

두 가지 아이디어가 떠올랐어요

  • 사용자에 대한 단일 컬렉션을 작성하고 잔액을 필드로, 사용자 관련 트랜잭션 및 메시지를 사용자 문서에 하위 문서로 저장합니다.문서를 원자적으로 갱신할 수 있기 때문에 실제로 트랜잭션 문제를 해결할 수 있습니다.단점: 사용자가 많은 SMS 메시지를 보낼 경우 문서의 크기가 커지고 4MB 문서 제한에 도달할 수 있습니다.그런 경우라면 역사 문서를 만들 수 있을지도 모르지만, 좋은 생각은 아닌 것 같습니다.또한 동일한 빅 문서에 점점 더 많은 데이터를 푸시하면 시스템이 얼마나 빨라질지 모릅니다.

  • 사용자를 위한 컬렉션을 하나 만들고 트랜잭션을 위한 컬렉션을 하나 만듭니다.거래에는 두 가지 종류가 있습니다: 의 잔액이 변경되는 신용 구매와 음의 잔액이 변경되는 메시지입니다.트랜잭션에는 하위 문서가 있을 수 있습니다. 예를 들어, 전송된 메시지에 SMS의 세부 정보가 트랜잭션에 포함될 수 있습니다.단점:현재 사용자 잔액은 저장되지 않으므로 사용자가 메시지를 보낼 때마다 계산하여 메시지를 통과할 수 있는지 확인해야 합니다.유감스럽게도 이 계산은 저장된 거래 건수가 증가함에 따라 느려질 수 있습니다.

어떤 방법을 선택해야 할지 좀 헷갈리네요.다른 해결책이 있나요?이러한 문제를 해결하는 방법에 대한 모범 사례를 온라인에서 찾을 수 없었습니다.NoSQL의 세계에 익숙해지려고 하는 많은 프로그래머들도 처음에는 비슷한 문제에 직면하게 될 것입니다.

4 트랜잭션을 됩니다.4 . 0 o MongoDB 중중 、 ACID 랜 as as 。이 계획에서는 먼저 복제 세트 배포에 있는 것을 사용하도록 설정한 후 샤드 클러스터를 사용하도록 설정합니다.데이터베이스에서 MongoDB의 트랜잭션은 MongoDB와 같은)을 가진 다양한 스테이트먼트이며, 시멘틱스나 구문이 유사합니다(예:start_transaction ★★★★★★★★★★★★★★★★★」commit_transaction중요한 것은 트랜잭션을 활성화하는 MongoDB 변경은 트랜잭션을 필요로 하지 않는 워크로드의 성능에 영향을 주지 않는다는 것입니다.

자세한 것은, 여기를 참조해 주세요.

분산 트랜잭션이 있다고 해서 표 형식의 관계형 데이터베이스와 같이 데이터를 모델링해야 하는 것은 아닙니다.문서 모델의 성능을 수용하고 데이터 모델링의 우수하고 권장되는 관행을 따르십시오.

이것 좀 봐, Tokutek의 작품.그들은 거래뿐만 아니라 성능 향상을 약속하는 몽고용 플러그인을 개발한다.

요점은 트랜잭션 무결성이 필수인 경우 MongoDB를 사용하지 말고 트랜잭션을 지원하는 시스템 구성 요소만 사용하십시오.ACID를 준수하지 않는 컴포넌트에 ACID와 유사한 기능을 제공하기 위해 컴포넌트 위에 무언가를 구축하는 것은 매우 어렵습니다.개별 사용 사례에 따라 액션을 트랜잭션액션과 비트랜잭션액션으로 구분하는 것이 적절할 수 있습니다.

그게 뭐가 문제죠?MongoDB는 하나의 문서에서만 원자 업데이트를 수행할 수 있습니다.이전 흐름에서는 어떤 오류가 슬금슬금 발생하여 메시지는 데이터베이스에 저장되지만 사용자의 밸런스는 줄어들지 않고 트랜잭션이 기록되지 않는 경우가 있습니다.

이건 별로 문제가 되지 않아요.말씀하신 오류는 논리적 오류(버그) 또는 IO 오류(네트워크, 디스크 장애)입니다.이러한 종류의 오류는 트랜잭션 없는 저장소와 트랜잭션 저장소가 모두 일관성 없는 상태가 될 수 있습니다.예를 들어, 이미 SMS를 발송했지만 메시지 저장 중 오류가 발생한 경우, SMS 발송을 롤백할 수 없으므로 로그가 되지 않으며, 사용자 밸런스가 저하되지 않습니다.

여기서 진짜 문제는 사용자가 레이스 조건을 이용하여 자신의 밸런스가 허용하는 것보다 더 많은 메시지를 보낼 수 있다는 것입니다.RDBMS 」 、 「 RDBMS 」 、 「 」( 「 RDBMS 」 ) 、 「 SMS 」 、 「 MongDB 」의 으로서 MongDB합니다.findAndModify먼저 잔액을 줄여서 확인하고, 마이너스일 경우 송금을 불허하고 환불한다(원자 증액).양이면 발송을 계속하고 금액이 환불되지 않을 경우 환불해 드립니다.밸런스 이력 컬렉션은 밸런스 필드의 수정/검증에도 도움이 되도록 유지관리할 수 있습니다.

프로젝트는 간단하지만, 지불을 위해 거래를 지원해야 하기 때문에 모든 것이 어렵습니다.예를 들어, 수백 개의 컬렉션(포럼, 채팅, 광고 등)이 있는 복잡한 포털 시스템은 어떤 점에서는 더 단순합니다. 포럼이나 채팅 엔트리를 잃으면 아무도 신경 쓰지 않기 때문입니다.반면, 당신이 지불 거래를 잃어버리면 그것은 심각한 문제입니다.

따라서 MongoDB의 파일럿 프로젝트를 정말 원한다면 그 점에서 간단한 것을 선택하세요.

MongoDB에는 타당한 이유로 트랜잭션이 존재하지 않습니다.이것이 MongoDB의 속도를 높이는 요인 중 하나입니다.

당신의 경우 거래가 필수라면 mongo는 적합하지 않은 것 같습니다.

RDMBS + MongoDB일 수 있지만 복잡성이 증가하여 애플리케이션 관리와 지원이 어려워집니다.

mongodb의 트랜잭션과 같은 기능을 실장하는 것에 대해 제가 발견한 최고의 블로그입니다!

동기 플래그: 마스터 문서에서 데이터를 복사하는 데 최적

작업 큐: 매우 일반적인 목적으로, 사례의 95%를 해결합니다.대부분의 시스템에서는 적어도1개의 작업 큐가 필요합니다.

2단계 커밋: 이 기술을 통해 각 엔티티가 항상 일관된 상태를 유지하는 데 필요한 모든 정보를 얻을 수 있습니다.

로그 조정: 금융 시스템에 이상적인 가장 강력한 기술

버전 관리: 격리 및 복잡한 구조 지원

상세한 것에 대하여는, https://dzone.com/articles/how-implement-robust-and 를 참조해 주세요.

늦었지만 나중에 도움이 될 것 같아요.저는 이 문제를 해결하기 위해 를 만들 때 Redis를 사용합니다.

  • ★★★★
    아래 그림에서는 2개의 액션을 동시에 실행해야 하지만 액션1의 단계2와 단계3은 액션2의 시작 단계2 또는 그 이전에 완료해야 합니다(단계는 요청 REST API, 데이터베이스 요청 또는 Javascript 코드 실행...).여기에 이미지 설명 입력

  • 이 되는
    는 " " " 사이의 가 " " 합니다.lock() ★★★★★★★★★★★★★★★★★」release()많은 기능이 동시에 실행되지 않을 경우 분리해야 합니다.

    function action1() {
      phase1();
      queue.lock("action_domain");
      phase2();
      phase3();
      queue.release("action_domain");
    }
    
    function action2() {
      phase1();
      queue.lock("action_domain");
      phase2();
      queue.release("action_domain");
    }
    

  • 백엔드 사이트에 큐를 작성할 때 레이스 컨디턴 부분을 회피하는 방법만 중점적으로 다루겠습니다.큐의 기본을 모르면 여기로 오세요.
    아래 코드는 개념만을 나타내며, 올바른 방법으로 구현해야 합니다.

    function lock() {
      if(isRunning()) {
        addIsolateCodeToQueue(); //use callback, delegate, function pointer... depend on your language
      } else {
        setStateToRunning();
        pickOneAndExecute();
      }
    }
    
    function release() {
      setStateToRelease();
      pickOneAndExecute();
    }
    

하지만 당신은 필요하다.isRunning() setStateToRelease() setStateToRunning()자신을 고립시키지 않으면 다시 인종적 상황에 직면하게 됩니다.그러기 위해서는 ACID 용도와 확장성이 뛰어난 Redis를 선택합니다.
Redis 문서에서는 거래에 대해 설명합니다.

트랜잭션의 모든 명령어는 순차적으로 직렬화되어 실행됩니다.Redis 트랜잭션 실행 중에 다른 클라이언트에 의해 발행된 요구가 처리되는 일은 없습니다.이렇게 하면 명령어가 단일 격리된 동작으로 실행될 수 있습니다.

P/s:
레디스레디스다른 방법으로 지원을 분리할 수 있습니다.
action_domain위의 코드는 사용자 A의 액션1 콜만 필요한 경우 A의 액션2를 차단하고 다른 사용자를 차단하지 마십시오.이 아이디어는 각 사용자의 잠금을 위한 고유한 키를 배치하는 것입니다.

현재 MongoDB 4.0에서 트랜잭션을 사용할 수 있습니다. 샘플은 이쪽입니다.

// Runs the txnFunc and retries if TransientTransactionError encountered

function runTransactionWithRetry(txnFunc, session) {
    while (true) {
        try {
            txnFunc(session);  // performs transaction
            break;
        } catch (error) {
            // If transient error, retry the whole transaction
            if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes("TransientTransactionError")  ) {
                print("TransientTransactionError, retrying transaction ...");
                continue;
            } else {
                throw error;
            }
        }
    }
}

// Retries commit if UnknownTransactionCommitResult encountered

function commitWithRetry(session) {
    while (true) {
        try {
            session.commitTransaction(); // Uses write concern set at transaction start.
            print("Transaction committed.");
            break;
        } catch (error) {
            // Can retry commit
            if (error.hasOwnProperty("errorLabels") && error.errorLabels.includes("UnknownTransactionCommitResult") ) {
                print("UnknownTransactionCommitResult, retrying commit operation ...");
                continue;
            } else {
                print("Error during commit ...");
                throw error;
            }
       }
    }
}

// Updates two collections in a transactions

function updateEmployeeInfo(session) {
    employeesCollection = session.getDatabase("hr").employees;
    eventsCollection = session.getDatabase("reporting").events;

    session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } );

    try{
        employeesCollection.updateOne( { employee: 3 }, { $set: { status: "Inactive" } } );
        eventsCollection.insertOne( { employee: 3, status: { new: "Inactive", old: "Active" } } );
    } catch (error) {
        print("Caught exception during transaction, aborting.");
        session.abortTransaction();
        throw error;
    }

    commitWithRetry(session);
}

// Start a session.
session = db.getMongo().startSession( { mode: "primary" } );

try{
   runTransactionWithRetry(updateEmployeeInfo, session);
} catch (error) {
   // Do something with error
} finally {
   session.endSession();
}

언급URL : https://stackoverflow.com/questions/6635718/how-to-work-around-the-lack-of-transactions-in-mongodb

반응형