몽고DB 8장 대체 Transaction

  • 몽고 디비 단일 문서에 대해 Atomic 처리가능 → 데이터 일관성 보장
  • 관련 데이터를 하나의 문서에 저장함으로써 복잡한 트랜잭션 처리 없이도 데이터의 일관성과 무결성을 유지

여러 문서라면?

  • 여러 문서에 걸쳐 일관성을 유지해야 하는 상황에서는 MongoDB의 분산 트랜잭션 기능을 사용하여 여러 작업을 하나의 트랜잭션으로 묶어 처리

transactions API

  • 트랜잭션을 시작합니다.
  • 지정된 작업을 실행합니다.
  • 결과를 커밋하거나(또는 에러 시 중단합니다).
  • 콜백 API는 특정 에러에 대해 재시도 로직을 포함
    • TransientTransactionError 또는 UnknownTransactionCommitResult 커밋 에러 후에 트랜잭션을 다시 시도
  • MongoDB 6.2부터, 서버는 TransactionTooLargeForCache 에러를 받을 경우 트랜잭션을 재시도 X

MongoDB 버전에 맞는 MongoDB 드라이버를 사용해야함

트랜잭션은 크게 다음 3가지를 사용한다.

  • transaction-level read concern
  • transaction-level write concern
  • transaction-level read preference

그리고 트랜잭션에 컬렉션을 암시적으로 생성할 수 있다.

  • create Collections and indexes in a transaction

Transaction and Read Concern

트랜잭션 수준의 읽기 관심도가 설정되지 않은 경우

  • 트랜잭션 수준의 읽기 관심도는 세션 수준의 읽기 관심도를 기본값으로 사용 → Local

트랜잭션은 다음 읽기 관심도 수준을 지원

  • "local": 읽기 관심도 "local"은 노드에서 가용한 가장 최근 데이터를 반환하지만 롤백 가능성 있음
  • "majority": 트랜잭션이 Majority 쓰기 관심사로 커밋이 되면 해당 데이터가 복제 세트 내 대다수의 노드에 성공적으로 기록 → 롤백될 수 없다.
  • "snapshot": 트랜잭션이 "majority" 쓰기 관심도로 커밋된 경우, 읽기 관심도 "snapshot"은 대다수 커밋된 데이터의 스냅샷으로부터 데이터를 반환

snapshot은 majority보다 더 높은 수준의 데이터 일관성과 격리를 제공합니다. 트랜잭션의 모든 읽기가 동일한 시점의 데이터 스냅샷을 기준으로 수행

트랜잭션에서 컬렉션과 인덱스를 내부적으로 생성할 수 있습니다.

  • 명시적으로 컬렉션이나 인덱스를 생성하는 경우
    • 트랜잭션은 읽기 관심도 "local"을 사용
    • → 왜 이렇게 해줘야하는지 .
  • 암시적으로 컬렉션을 생성하는 경우
    • 트랜잭션에서 사용 가능한 모든 읽기 관심도를 사용

분산된 클러스터에서의 트랜잭션에서는, "snapshot" 읽기 관심도를 사용하면 데이터의 스냅샷 뷰가 샤드 간에 동기화됩니다.

Transactions and Write Concern

  • 트랜잭션 내의 쓰기 연산은 명시적인 쓰기 관심도 지정 없이 실행
  • 기본 쓰기 관심도를 사용합니다. 커밋 시점에, 트랜잭션 수준의 쓰기 관심도를 사용하여 쓰기가 커밋됩니다.
트랜잭션 내의 개별 쓰기 연산에 대한 쓰기 관심도를 설정하면 오류가 반환

트랜잭션 수준의 쓰기 관심도가 설정되지 않았을 경우, MongoDB는 다음과 같은 순서로 쓰기 관심도의 기본값을 결정합니다:

  1. 트랜잭션 수준의 쓰기 관심도: 트랜잭션을 시작할 때 개발자가 명시적으로 설정
    • 이 설정이 있으면, 트랜잭션 내의 모든 쓰기 작업은 이 쓰기 관심도를 따릅니다.
  2. 세션 수준의 쓰기 관심도: 트랜잭션 수준의 쓰기 관심도가 설정되지 않은 경우, MongoDB는 해당 세션에 대해 설정된 쓰기 관심도를 사용
    • 세션은 클라이언트와 서버 간의 상호작용을 유지하는 연결의 일종입니다. 세션 수준에서 쓰기 관심도가 설정되면, 그 설정이 트랜잭션의 기본 쓰기 관심도가 됌
  3. 클라이언트 수준의 쓰기 관심도: 만약 트랜잭션 수준 또는 세션 수준에서 쓰기 관심도가 설정되지 않은 경우, MongoDB 클라이언트(애플리케이션)에서 설정된 기본 쓰기 관심도를 사용합니다.
    • 예를 들어, MongoDB 5.0 이후 버전에서는 클라이언트 수준의 기본 쓰기 관심도가 "majority"로 설정됩니다. 이는 쓰기 작업이 데이터베이스의 대다수 노드에 복제되고 인정되어야만 성공으로 간주됨을 의미합니다.

트랜잭션은 모든 쓰기 관심도 w 값을 지원합니다, 여기에는 포함됩니다:

  • w: 1: 쓰기 관심도 w: 1은 커밋이 프라이머리에 적용된 후에 승인을 반환합니다. w: 1로 커밋할 때, 장애 이전에 트랜잭션이 롤백될 수 있습니다.
  • w: "majority": 쓰기 관심도 w: "majority"는 커밋이 투표 멤버의 대다수에 적용된 후에 승인을 반환합니다. w: "majority" 쓰기 관심도로 커밋할 때, 트랜잭션 수준의 "majority" 읽기 관심도는 작업이 대다수에 의해 커밋된 데이터를 읽었음을 보장합니다. 샤딩된 클러스터에서의 트랜잭션에 대해서는, 이 대다수 커밋된 데이터의 뷰는 샤드 간에 동기화되지 않음.

샤딩된 클러스터 트랜잭션의 커밋 연산은 {w: "majority", j: true}

서버 매개변수인 coordinateCommitReturnImmediatelyAfterPersistingDecision은 트랜잭션 커밋 결정이 클라이언트에 반환되는 시점을 제어

  • true (MongoDB 5.0의 기본값): 이 설정이 활성화되어 있으면, 샤드 트랜잭션 조정자(shard transaction coordinator)는 트랜잭션 커밋 결정이 영구적으로 저장되는 즉시 커밋 결정을 클라이언트에 반환
  • false (MongoDB 6.1의 기본값 변경): 이 설정이 false로 설정되면, 샤드 트랜잭션 조정자는 모든 샤드 멤버들로부터 트랜잭션 커밋에 대한 승인을 받기 전까지는 커밋 결정을 클라이언트에 반환하지 않는다.

transaction-level read preference

트랜잭션 내의 연산들은 트랜잭션 수준의 읽기 선호도를 사용합니다.

드라이버를 사용하여 트랜잭션 시작 시 트랜잭션 수준의 읽기 선호도를 설정할 수 있습니다:

  • 트랜잭션 수준의 읽기 선호도가 설정되지 않은 경우, 트랜잭션은 세션 수준의 읽기 선호도를 사용합니다.
  • 트랜잭션 수준과 세션 수준의 읽기 선호도 모두 설정되지 않은 경우, 트랜잭션은 클라이언트 수준의 읽기 선호도를 사용합니다. 기본적으로 클라이언트 수준의 읽기 선호도는 primary(기본 복제본)입니다.

분산 트랜잭션에서 읽기 연산을 포함하는 경우 읽기 선호도를 사용해야 합니다. 이는 트랜잭션이 여러 샤드에 걸쳐 있는 데이터를 일관되게 읽을 수 있도록 하는데 중요합니다. 읽기 선호도를 통해 애플리케이션은 데이터를 읽을 복제본을 선호도에 따라 선택할 수 있으며, 이는 데이터의 가용성과 읽기 작업의 성능 최적화에 영향을 줄 수 있습니다.

읽기 선호도 옵션:
Primary: 모든 읽기 연산이 주 복제본(Primary)에서 수행됩니다. 이는 데이터의 일관성을 보장하지만, 모든 읽기 부하가 주 복제본에 집중됩니다.

PrimaryPreferred: 가능한 경우 주 복제본에서 읽기를 수행하지만, 주 복제본이 사용 불가능한 경우에는 보조 복제본(Secondary)에서 읽기를 수행합니다.

Secondary: 모든 읽기 연산이 보조 복제본에서 수행됩니다. 이를 통해 주 복제본의 읽기 부하를 줄일 수 있지만, 보조 복제본에서 읽은 데이터는 약간 지연된 데이터일 수 있습니다(즉, 최신 데이터가 아닐 수 있음).

SecondaryPreferred: 가능한 경우 보조 복제본에서 읽기를 수행하지만, 사용 가능한 보조 복제본이 없는 경우 주 복제본에서 읽기를 수행합니다.

Nearest: 네트워크 지연 시간이 가장 짧은 복제본(주 복제본 또는 보조 복제본)에서 읽기를 수행합니다. 이 옵션은 읽기 성능 최적화에 유용하지만, 읽은 데이터의 일관성 수준은 보장되지 않습니다.
/*
  For a replica set, include the replica set name and a seedlist of the members in the URI string; e.g.
  String uri = "mongodb://mongodb0.example.com:27017,mongodb1.example.com:27017/admin?replicaSet=myRepl";
  For a sharded cluster, connect to the mongos instances.
  For example:
  String uri = "mongodb://mongos0.example.com:27017,mongos1.example.com:27017:27017/admin";
 */
final MongoClient client = MongoClients.create(uri);
/*
    Create collections.
 */
client.getDatabase("mydb1").getCollection("foo")
        .withWriteConcern(WriteConcern.MAJORITY).insertOne(new Document("abc", 0));
client.getDatabase("mydb2").getCollection("bar")
        .withWriteConcern(WriteConcern.MAJORITY).insertOne(new Document("xyz", 0));
/* Step 1: Start a client session. */
final ClientSession clientSession = client.startSession();

/* Step 2: Optional. Define options to use for the transaction. */
TransactionOptions txnOptions = TransactionOptions.builder()
        .readPreference(ReadPreference.primary())
        .readConcern(ReadConcern.LOCAL)
        .writeConcern(WriteConcern.MAJORITY)
        .build();
/* Step 3: Define the sequence of operations to perform inside the transactions. */
TransactionBody txnBody = new TransactionBody<String>() {
    public String execute() {
        MongoCollection<Document> coll1 = client.getDatabase("mydb1").getCollection("foo");
        MongoCollection<Document> coll2 = client.getDatabase("mydb2").getCollection("bar");
        /*
           Important:: You must pass the session to the operations.
         */
        coll1.insertOne(clientSession, new Document("abc", 1));
        coll2.insertOne(clientSession, new Document("xyz", 999));
        return "Inserted into collections in different databases";
    }
};
try {
    /*
       Step 4: Use .withTransaction() to start a transaction,
       execute the callback, and commit (or abort on error).
    */
    clientSession.withTransaction(txnBody, txnOptions);
} catch (RuntimeException e) {
    // some error handling
} finally {
    clientSession.close();
}

//jpa + queydsl mongodb @transaction 
//mongodb 에서 transaction 

Transactions and Atomicity

분산 트랜잭션은 원자적

  • 트랜잭션은 모든 데이터 변경사항을 적용하거나 변경사항을 롤백
  • 트랜잭션이 커밋되면, 트랜잭션 내에서 이루어진 모든 데이터 변경사항이 저장되며 트랜잭션 외부에서도 보이게 됩니다.
  • 트랜잭션이 커밋될 때까지, 트랜잭션 내에서 이루어진 데이터 변경사항은 트랜잭션 외부에서 보이지 않습니다.

트랜잭션이 여러 샤드에 쓰기를 할 때, 모든 외부 읽기 연산이 커밋된 트랜잭션의 결과가 샤드 간에 보이기를 기다릴 필요는 없습니다.

  • 예를 들어, 트랜잭션이 커밋되어 샤드 A에서는 쓰기 1이 보이지만 샤드 B에서는 쓰기 2가 아직 보이지 않는 경우, 읽기 관심도 "local"을 사용하는 외부 읽기는 쓰기 2를 보지 않고 쓰기 1의 결과를 읽을 수 있습니다.

트랜잭션이 중단되면, 트랜잭션 내에서 이루어진 모든 데이터 변경사항이 폐기되며 결코 외부에서 보이지 않게 됩니다.

  • 예를 들어, 트랜잭션 내의 어떤 연산이 실패하면 트랜잭션이 중단되고 트랜잭션 내에서 이루어진 모든 데이터 변경사항이 폐기되어 결코 외부에서 보이지 않습니다.

중요) 분산 트랜잭션은 단일 문서 쓰기보다 더 큰 성능 비용을 발생시킨다.


Transactions and Operations

분산 트랜잭션은 여러 연산, 컬렉션, 데이터베이스, 문서 및 샤드에 걸쳐 사용

  • 샤드 간 쓰기 트랜잭션에서는 새로운 컬렉션을 생성할 수 없습니다.
    • 예를 들어, 한 샤드의 기존 컬렉션에 쓰기를 하면서 다른 샤드에서 컬렉션을 암시적으로 생성하는 경우, MongoDB는 같은 트랜잭션에서 두 연산을 수행할 수 없다.

트랜잭션을 시작하기 바로 전에 컬렉션을 생성하거나 삭제하는 경우

  • 트랜잭션 내에서 해당 컬렉션에 접근하려면, 트랜잭션이 필요한 잠금을 획득할 수 있도록 쓰기 관심도 "majority"를 사용하여 생성 또는 삭제 연산을 발행

 

'DataBase' 카테고리의 다른 글

Database01 :: TOPIC01  (0) 2018.12.31