트랜잭션
개요
이 가이드를 읽고 Node.js 드라이버를 사용하여 MongoDB에서 트랜잭션 을 수행하는 방법을 알아보세요. 트랜잭션은 작업 단위이며, 하나 이상의 작업이 실패할 경우 함께 성공하거나 함께 실패하려는 일련의 작업으로 구성됩니다. 이 동작을 원자성 이라고 합니다. 원자성은 하나 이상의 작업으로 구성된 트랜잭션이 한 번에 모두 발생하여 다른 클라이언트가 이를 별도의 작업으로 관찰할 수 없으며 작업 중 하나가 실패해도 변경 사항을 남기지 않는 속성입니다.
MongoDB의 단일 문서에 대한 모든 쓰기 작업은 원자적이므로, 여러 문서를 수정하는 원자적 변경(다중 문서 트랜잭션이라고 함)을 수행해야 하는 경우 트랜잭션을 최대한 활용할 수 있습니다. 단일 문서에 대한 쓰기 작업과 마찬가지로 다중 문서 트랜잭션은 ACID 를 준수 하므로 MongoDB는 예기치 않은 오류가 발생하더라도 트랜잭션 작업과 관련된 데이터의 일관성을 보장합니다. ACID 트랜잭션에 대한 이 MongoDB 문서에서 자세히 알아보세요.
드라이버를 사용하여 다중 문서 트랜잭션을 수행할 수 있습니다.
참고
다중 문서 트랜잭션을 실행하려면 MongoDB 버전 4.0 이상을 사용해야 합니다.
자세한 제한 사항 목록은 Server 매뉴얼의 트랜잭션 및 운영 섹션을 참조하세요.
MongoDB에서 다중 문서 트랜잭션은 클라이언트 세션 내에서 실행됩니다. 클라이언트 세션은 순차적으로 실행하려는 관련 읽기 또는 쓰기 작업을 그룹화한 것입니다. 매번 새 클라이언트를 인스턴스화하는 대신 여러 세션 및 트랜잭션에 클라이언트를 재사용할 것을 권장합니다.
대부분의 읽기 및 쓰기 문제와 결합되면 드라이버는 작업 간의 인과적 일관성을 보장할 수 있습니다. 자세한 내용은 클라이언트 세션 및 인과적 일관성 보장 에 대한 서버 매뉴얼 가이드를 참조하세요.
이 가이드의 다음 섹션에서 드라이버를 사용하여 MongoDB에서 다중 문서 트랜잭션을 실행하는 방법에 대해 자세히 알아보세요.
트랜잭션 API
Core API 를 사용하여 드라이버와의 트랜잭션을 구현합니다. Core API를 사용하려면 트랜잭션의 시작 점과 커밋 점을 선언해야 합니다.
Core API
Core API에는 트랜잭션을 시작, 취소 또는 커밋하는 메서드가 있습니다. 트랜잭션을 커밋할 때 작업을 원자적으로 변경하라는 요청을 서버에 보냅니다. 이 API를 사용할 때는 서버에서 반환되는 특정 트랜잭션 오류를 수동으로 처리해야 합니다.
이러한 오류에 대한 자세한 내용은 TransientTransactionError 및 UnknownTransactionCommitResult 를 참조하세요.
트랜잭션을 시작, 취소 또는 커밋하려면 Session
객체에서 해당 메서드를 호출하면 됩니다.
startTransaction()
commitTransaction()
abortTransaction()
샘플 트랜잭션 구현 은 Core API 예시 를 참조하세요.
트랜잭션 설정
트랜잭션을 인스턴스화할 때 다음 옵션을 지정하여 해당 트랜잭션의 기본 동작을 설정할 수 있습니다.
설정 | 설명 |
---|---|
readConcern | 읽기 작업이 복제본 세트에서 검색하는 데이터의 일관성을 확인하는 방법을 지정합니다. 자세한 내용은 서버 매뉴얼의 읽기 고려 를 참조하세요. |
writeConcern | 쓰기를 승인할 조건을 지정합니다. 자세한 내용은 서버 매뉴얼의 쓰기 고려 를 참조하세요. |
readPreference | 자세한 내용은 서버 매뉴얼의 읽기 설정 을 참조하세요. |
maxCommitTimeMS | 트랜잭션의 조치가 실행될 수 있는 최대 시간(밀리초)을 지정합니다. |
값을 제공하지 않으면 드라이버는 클라이언트 설정을 사용합니다.
다음과 유사한 코드를 사용하여 Core API에서 트랜잭션 옵션을 지정할 수 있습니다.
const transactionOptions = { readPreference: 'primary', readConcern: { level: 'local' }, writeConcern: { w: 'majority' }, maxCommitTimeMS: 1000 }; session.startTransaction(transactionOptions);
예시
고객이 온라인 스토어에서 품목을 구매하는 시나리오를 가정해 보겠습니다. 구매를 기록하려면 애플리케이션에서 재고, 고객 주문 및 주문 세부 정보 등록과 관련된 정보를 업데이트해야 합니다. 데이터 업데이트를 다음과 같이 구성한다고 가정해 보겠습니다.
컬렉션 | 작업 | 변경 사항 설명 |
---|---|---|
orders | insert | 구매 정보 기록 |
customers | update | 주문 ID를 추가하여 고객과 연결 |
inventory | update | 주문한 항목의 수량 빼기 |
재고에 있는 품목의 수량이 충분하지 않은 경우, 주문이 완료되지 않은 경우, 결제 시스템이 오프라인 상태인 경우 등 여러 가지 이유로 구매가 실패할 수 있습니다.
결제에 실패할 경우 트랜잭션을 사용하여 해당 데이터에 의존하는 다른 작업에서 데이터 일관성 문제를 일으킬 수 있는 부분 업데이트가 노출되지 않도록 할 수 있습니다.
샘플 데이터
코드 예제에서는 다중 문서 결제 트랜잭션을 실행하기 위해 testdb
데이터베이스에 다음 샘플 데이터가 필요합니다.
고객과 해당 주문을 설명하는
customers
컬렉션의 문서입니다.각
inventory
항목의 수량 및 설명을 추적하는 collection의 문서입니다.
이 예제의 customers
collection에 있는 문서에는 다음이 포함되어 있습니다.
{ _id: 98765, orders: [] }
이 예제의 inventory
collection에 있는 문서에는 다음이 포함되어 있습니다.
[ { name: "sunblock", sku: 5432, qty: 85 }, { name: "beach towel", sku: 7865, qty: 41 } ]
코드 예제는 orders
collection에 대한 작업도 수행하지만 이전 샘플 문서는 필요하지 않습니다.
코드 예제에서는 cart
및 payment
변수를 사용하여 다음과 같이 구매한 품목의 샘플 목록과 주문 결제 세부 정보를 나타냅니다.
const cart = [ { name: 'sunblock', sku: 5432, qty: 1, price: 5.19 }, { name: 'beach towel', sku: 7865, qty: 2, price: 15.99 } ]; const payment = { customer: 98765, total: 37.17 };
중요
다음 섹션의 예제에서는 트랜잭션 외부에서 컬렉션을 생성하거나 MongoDB 4.4 이상을 사용해야 합니다. 트랜잭션 내에서 컬렉션을 만드는 방법에 대한 자세한 내용은 트랜잭션 서버 가이드 에서 컬렉션 및 인덱스 만들기 를 참조하세요.
Core API 구현
이 섹션의 코드 예제는 Core API를 사용하여 세션에서 다중 문서 결제 트랜잭션을 실행하는 방법을 보여줍니다. 이 함수는 다음을 수행하는 방법을 보여줍니다.
세션 시작
트랜잭션 시작, 트랜잭션 옵션 지정
동일한 세션에서 데이터 작업 수행
트랜잭션을 커밋하거나 드라이버에 오류가 발생하면 취소하세요.
세션 종료
1 async function placeOrder(client, cart, payment) { 2 const transactionOptions = { 3 readConcern: { level: 'snapshot' }, 4 writeConcern: { w: 'majority' }, 5 readPreference: 'primary' 6 }; 7 8 const session = client.startSession(); 9 try { 10 session.startTransaction(transactionOptions); 11 12 const ordersCollection = client.db('testdb').collection('orders'); 13 const orderResult = await ordersCollection.insertOne( 14 { 15 customer: payment.customer, 16 items: cart, 17 total: payment.total, 18 }, 19 { session } 20 ); 21 22 const inventoryCollection = client.db('testdb').collection('inventory'); 23 for (let i=0; i<cart.length; i++) { 24 const item = cart[i]; 25 26 // Cancel the transaction when you have insufficient inventory 27 const checkInventory = await inventoryCollection.findOne( 28 { 29 sku: item.sku, 30 qty: { $gte: item.qty } 31 }, 32 { session } 33 ) 34 if (checkInventory === null) { 35 throw new Error('Insufficient quantity or SKU not found.'); 36 } 37 38 await inventoryCollection.updateOne( 39 { sku: item.sku }, 40 { $inc: { 'qty': -item.qty }}, 41 { session } 42 ); 43 } 44 45 const customerCollection = client.db('testdb').collection('customers'); 46 await customerCollection.updateOne( 47 { _id: payment.customer }, 48 { $push: { orders: orderResult.insertedId }}, 49 { session } 50 ); 51 await session.commitTransaction(); 52 console.log('Transaction successfully committed.'); 53 54 } catch (error) { 55 if (error instanceof MongoError && error.hasErrorLabel('UnknownTransactionCommitResult')) { 56 // add your logic to retry or handle the error 57 } 58 else if (error instanceof MongoError && error.hasErrorLabel('TransientTransactionError')) { 59 // add your logic to retry or handle the error 60 } else { 61 console.log('An error occured in the transaction, performing a data rollback:' + error); 62 } 63 await session.abortTransaction(); 64 } finally { 65 await session.endSession(); 66 } 67 }
참고로 해당 세션에서 실행하려는 각 CRUD 작업에 세션 객체를 전달해야 합니다.
catch
블록의 코드와 주석은 서버 트랜잭션 오류를 식별하는 방법과 오류를 처리하는 로직을 배치할 수 있는 위치를 보여줍니다. 다음 샘플 가져오기 문에 표시된 대로 코드에 드라이버의 MongoError
유형을 포함해야 합니다.
const { MongoError, MongoClient } = require('mongodb');
트랜잭션 을 실행 한 후 컬렉션에 포함되어야 하는 내용을 확인하려면 결제 트랜잭션 결과 섹션을 참조하세요.
결제 트랜잭션 결과
애플리케이션이 결제 트랜잭션을 완료하면 데이터베이스에 모든 업데이트가 포함되어야 하며, 예외로 인해 트랜잭션이 중단된 경우 데이터베이스에 변경 사항이 없어야 합니다.
customers
collection에는 주문 필드에 주문 id가 추가된 고객 문서가 포함되어 있어야 합니다.
{ "_id": 98765, "orders": [ "61dc..." ] }
inventory
collection에는 '선블록' 및 '해변 타월' 항목에 대한 업데이트된 수량이 포함되어야 합니다.
[ { "_id": ..., "name": "sunblock", "sku": 5432, "qty": 84 }, { "_id": ..., "name": "beach towel", "sku": 7865, "qty": 39 } ]
orders
collection에는 주문 및 결제 정보가 포함되어야 합니다.
[ { "_id": "...", "customer": 98765, "items": [ { "name": "sunblock", "sku": 5432, "qty": 1, "price": 5.19 }, { "name": "beach towel", "sku": 7865, "qty": 2, "price": 15.99 } ], "total": 37.17 } ]