MongoDB는 트랜잭션을 어떻게 보장할까
개요
지난 글 의 연장선임을 밝히기는 하지만 읽지 않아도 무방합니다.
NoSQL이라는 용어때문에 헷갈릴 수 있는데 Non-SQL, Not Only SQL 이 두 정의로 나눠져서 구체적인 정의는 없지만, 후자의 설명을 따르자면 데이터 저장 시 SQL 외에도 다른 방법으로 저장할 수 있다는 의미를 나타내기도 합니다.
다소 폭력적인(?) 어감때문에 처음에는 MongoDB가 트랜잭션을 지원하지 않는 줄 알았는데 Document 형태로 저장될 뿐 트랜잭션을 지원한다는 사실을 알았습니다.
이제 MongoDB가 어떻게 트랜잭션을 지원하는지 알아볼 것입니다.
잠깐 짚고가는 RDB
글의 메인 주제는 MongoDB지만 후에 등장하는 개념 이해를 위해 RDB에서 보장하는 ACID를 짚고 가겠습니다.
ACID는 데이터베이스 트랜잭션이 안전하게 처리되기 위해 갖춰야 하는 특성을 말합니다.
| 속성 | 의미 | 예시 |
|---|---|---|
| Atomicity(원자성) | 트랜잭션 안의 작업은 모두 성공하거나 모두 실패해야 한다. | ex) 재고 차감은 성공했는데 예약 저장만 실패하는 상태를 남기지 않는다. |
| Consistency(일관성) | 트랜잭션 전후에 데이터는 정해둔 제약과 규칙을 만족해야 한다. | ex) 잔여 객실 수가 음수가 되지 않아야 한다. |
| Isolation(격리성) | 동시에 실행되는 트랜잭션끼리 서로의 상태를 간섭할 수 없다. | ex) 다른 요청이 아직 커밋되지 않은 예약을 확정된 예약처럼 읽지 않는다. |
| Durability(지속성) | 커밋된 결과는 장애가 나도 사라지지 않아야 한다. | ex) 예약 생성 응답을 받은 뒤 DB가 재시작되어도 예약은 남아 있어야 한다. |
예를 들어 RDB에서 예약을 생성한다고 하면:
BEGIN;
UPDATE room_inventory
SET remaining = remaining - 1
WHERE room_type_id = 1
AND stay_date = '2026-05-01'
AND remaining > 0;
INSERT INTO reservations (id, room_type_id, check_in, check_out)
VALUES (100, 1, '2026-05-01', '2026-05-02');
COMMIT;
이 때 트랜잭션의 원자성을 보장하기 위해 UPDATE와 INSERT가 하나의 작업으로 묶여야 합니다.
둘 중 하나의 작업만 반영된다면 예약이라는 완전한 비즈니스 작업이 진행되지 않습니다. 그래서 RDB에 익숙한 개발자는 “예약 생성에는 트랜잭션이 필요하다”는 판단부터 하게 됩니다.
MongoDB에서도 이 문제는 사라지지 않습니다. 다만 MongoDB는 먼저 질문을 바꿉니다.
이 변경을 하나의 도큐먼트 안에서 끝낼 수 있는가?
MongoDB의 단일 도큐먼트 시스템
MongoDB 공식 문서에서는 단일 도큐먼트에 대한 쓰기 작업이 원자적이라고 설명합니다.
한 도큐먼트 안의 여러 필드를 한 번에 바꾸는 작업은 중간 상태를 남기지 않습니다.
db.orders.updateOne(
{ _id: "order-1", status: "PENDING" },
{
$set: { status: "CONFIRMED" },
$inc: { reservedQuantity: 1 }
}
)
위 작업은 status만 바뀌고 reservedQuantity는 안 바뀌는 식으로 끝나지 않습니다.
필터 조건에 맞는 도큐먼트 하나에 대해 변경이 하나의 원자 작업으로 적용됩니다.
여기서 원자성의 기본 경계가 “컬렉션”이나 “여러 도큐먼트”가 아니라 하나의 도큐먼트라는 것입니다.
updateMany()처럼 여러 도큐먼트를 바꾸는 명령도 각 도큐먼트에 대한 변경은 원자적이지만, 여러 도큐먼트 전체가 하나의 원자 단위가 되는 것은 아닙니다. 그 전체를 하나로 묶으려면 멀티 도큐먼트 트랜잭션이 필요합니다.
WiredTiger 스토리지 엔진
MongoDB 3.2+의 기본 스토리지 엔진인 WiredTiger가 도큐먼트 단위 동시성 제어(document-level concurrency control)를 제공합니다.
MySQL과 비교하자면:
| 구분 | MySQL InnoDB | MongoDB WiredTiger |
|---|---|---|
| 잠금 단위 | 행(row) | 도큐먼트(document) |
| MVCC | 있음 | 있음 |
| 저널링 | redo log | WAL (Write-Ahead Log) |
‘도큐먼트 단위 동시성 제어’라는 말은 서로 다른 도큐먼트를 수정하는 write가 같은 컬렉션 안에서도 병렬로 진행될 수 있다는 뜻입니다.
과거의 컬렉션 단위 잠금보다 경합 범위가 훨씬 작습니다.
물론 모든 잠금이 사라지는 것은 아닙니다. MongoDB는 global, database, collection 레벨에 intent lock을 사용하고, 실제 write 경합은 WiredTiger가 도큐먼트 단위에서 조정합니다. 두 요청이 같은 도큐먼트를 동시에 바꾸려고 하면 write conflict가 발생할 수 있고, MongoDB는 일부 단일 write 작업을 내부적으로 재시도합니다. 트랜잭션 안에서 충돌이 나면 애플리케이션이 트랜잭션 자체를 다시 시도해야 할 수도 있습니다.
WiredTiger의 MVCC(Multi-Version Concurrency Control)는 읽기 작업이 쓰기를 차단하지 않도록 돕습니다.
쓰기가 진행 중이더라도, 읽기 요청은 자신이 읽는 시점에 맞는 스냅샷을 볼 수 있습니다. 그래서 MongoDB의 기본 전략은 “읽기와 쓰기를 최대한 덜 막고, 충돌은 작은 단위에서 처리한다”에 가깝습니다.
도큐먼트 설계가 트랜잭션을 줄인다
RDB에서는 정규화로 인해 여러 테이블에 분산되는 데이터를, MongoDB에서는 하나의 도큐먼트에 임베딩할 수 있습니다.
이것이 “트랜잭션 없이도 일관성을 유지”하는 핵심 전략입니다.
RDB (3개 테이블, 트랜잭션 필요):
INSERT INTO orders (...) VALUES (...);
INSERT INTO order_items (...) VALUES (...);
INSERT INTO order_recipients (...) VALUES (...);
COMMIT;
MongoDB (1개 도큐먼트, 트랜잭션 불필요):
db.orders.insertOne({
items: [...],
recipient: {...}
})
예를 들어 주문과 주문 항목은 보통 함께 생성되고 함께 조회됩니다. RDB에서는 orders, order_items, order_recipients를 각각 저장하고 트랜잭션으로 묶습니다. MongoDB에서는 주문 도큐먼트 안에 항목과 수령자 정보를 함께 넣을 수 있습니다.
db.orders.insertOne({
_id: "order-1",
memberId: "member-1",
status: "CREATED",
items: [
{ productId: "product-1", name: "샴푸", quantity: 2, price: 12000 },
{ productId: "product-2", name: "린스", quantity: 1, price: 9000 }
],
recipient: {
name: "홍길동",
phone: "010-0000-0000",
address: "서울시 ..."
}
})
이 경우 주문 생성은 insertOne() 한 번으로 끝나며, 항목 하나만 들어가고 수령자 정보는 누락되는 중간 상태가 생기지 않습니다.
MongoDB 공식 문서도 여러 상황에서 비정규화된 데이터 모델, 즉 embedded documents와 arrays가 멀티 도큐먼트 트랜잭션보다 더 적합할 수 있다고 설명합니다. 여기서 중요한 표현은 “트랜잭션을 안 쓴다”가 아니라 트랜잭션 경계를 도큐먼트 안으로 옮긴다입니다.
하지만 이 방식에는 비용이 있습니다.
| 선택 | 좋아지는 점 | 부담 |
|---|---|---|
| 임베드 | 한 번에 읽기 쉽고, 단일 도큐먼트 원자성을 활용할 수 있습니다. | 데이터 중복, 큰 도큐먼트, 배열 증가 관리가 필요합니다. |
| 레퍼런스 | 중복이 줄고, 독립적으로 자주 바뀌는 데이터를 다루기 쉽습니다. | 조회가 여러 번 필요하거나 $lookup, 애플리케이션 조립, 트랜잭션이 필요할 수 있습니다. |
예를 들어 상품명은 주문 시점의 스냅샷으로 주문 도큐먼트에 복사해도 괜찮습니다. 나중에 상품명이 바뀌어도 과거 주문 내역은 당시 이름을 보여주는 것이 자연스럽습니다.
반대로 회원의 포인트 잔액, 객실 재고, 결제 상태처럼 여러 업무 흐름에서 계속 바뀌는 값은 무작정 임베드하면 중복 갱신 문제가 생깁니다. 이런 데이터는 별도 도큐먼트로 두고, 필요할 때 멀티 도큐먼트 트랜잭션이나 보상 로직을 고려하는 편이 낫습니다.
2층: Read/Write Concern
RDB에서는 격리 수준(Isolation Level)으로 일관성을 조절합니다. MongoDB에서는 Read Concern과 Write Concern이라는 두 축으로 이를 제어합니다.
Write Concern: “몇 개 노드에 쓰여야 성공으로 볼 것인가”
Write Concern: { w: 1 }
Primary에 쓰기 완료 -> 즉시 응답
-> 빠르지만, Primary 장애 시 데이터 유실 가능
Write Concern: { w: "majority" }
Primary + 과반수 Secondary에 복제 완료 -> 응답
-> 느리지만, 장애 시에도 데이터 보존
Write Concern: { w: 0 }
쓰기 요청만 전송, 확인 안 함
-> 가장 빠르지만, 유실 가능성 높음 (로그성 데이터에 적합)
w: 1은 Primary가 write를 받았다는 확인만 받습니다. w: "majority"는 replica set의 과반수 멤버에 기록된 뒤 성공으로 봅니다. 그래서 지연은 늘 수 있지만, Primary 장애 후 롤백될 가능성을 줄입니다.
Read Concern: “어느 시점의 데이터를 읽을 것인가”
Read Concern: local (기본값)
Primary의 최신 데이터 반환
-> 아직 과반수에 복제되지 않은 데이터도 보임 (rollback 가능)
Read Concern: majority
과반수 노드에 복제 확인된 데이터만 반환
-> 롤백되지 않을 데이터만 읽음
Read Concern: snapshot
트랜잭션 시작 시점의 일관된 스냅샷
-> 멀티 도큐먼트 트랜잭션에서 사용
Read Concern은 “무엇을 읽을 것인가”에 대한 설정입니다. local은 현재 노드가 가진 최신 데이터를 읽고, majority는 과반수에 반영된 데이터를 읽습니다. snapshot은 트랜잭션에서 같은 시점의 데이터를 일관되게 읽기 위해 사용합니다.
Write Concern과 Read Concern은 성능 스위치가 아니라 일관성 스위치에 가깝습니다. 빠른 응답을 원하면 더 약한 보장을 선택할 수 있지만, 장애나 롤백 상황에서 어떤 데이터를 읽고 쓸 수 있는지까지 같이 결정됩니다.
멀티 도큐먼트 트랜잭션
도큐먼트 설계로 해결할 수 없는 경우가 있습니다. 서로 다른 컬렉션의 도큐먼트를 함께 수정해야 할 때입니다.
// 예약 생성: inventories 컬렉션 + reservations 컬렉션을 함께 수정
fun createReservation(...) {
inventoryApplication.reserve(...) // inventories 컬렉션 write
reservationRepository.save(reservation) // reservations 컬렉션 write
}
재고 차감은 성공했는데 예약 저장이 실패하면? 재고만 줄어든 채 예약이 없는 상태가 됩니다.
숙소 예약으로 조금 더 구체화해보면 다음 흐름입니다.
1. 5월 1일 재고 1개 차감
2. 5월 2일 재고 1개 차감
3. 5월 3일 재고 1개 차감
4. reservations 컬렉션에 예약 저장
3박 예약이면 재고 도큐먼트 여러 개와 예약 도큐먼트 하나가 함께 바뀝니다. 단일 도큐먼트 원자성만으로는 이 전체를 보장할 수 없습니다.
fun createReservation(command: CreateReservationCommand): Reservation {
command.dateRange.allDates().forEach { date ->
inventoryApplication.reserve(
propertyId = command.propertyId,
roomTypeId = command.roomTypeId,
date = date
)
}
return reservationRepository.save(command.toReservation())
}
위 코드에서 두 번째 날짜 재고 차감 후 세 번째 날짜에서 예외가 나면, 이미 차감한 재고는 남습니다. 예약 저장 전 서버가 죽어도 마찬가지입니다. 이때 필요한 것이 멀티 도큐먼트 트랜잭션입니다.
전제 조건: Replica Set
멀티 도큐먼트 트랜잭션은 Replica Set 또는 Sharded Cluster 환경에서 동작합니다. Standalone MongoDB에서는 사용할 수 없습니다. 그래서 로컬 개발 환경에서도 트랜잭션을 테스트하려면 replica set으로 띄워야 합니다.
# docker-compose.yml
mongodb:
command: ["--replSet", "rs0"]
Spring에서의 사용
@Configuration
class MongoConfig {
@Bean
fun transactionManager(dbFactory: MongoDatabaseFactory): MongoTransactionManager {
return MongoTransactionManager(dbFactory)
}
}
MongoTransactionManager Bean을 등록하면, RDB와 동일하게 @Transactional을 사용할 수 있습니다.
@Transactional
fun createReservation(...): Reservation {
// 이 안의 모든 write가 하나의 트랜잭션
dateRange.allDates().forEach { date ->
inventoryApplication.reserve(...) // write 1, 2, ...
}
reservationRepository.save(reservation) // write N
// 하나라도 실패하면 전부 rollback
}
이제 재고 차감 중 하나라도 실패하거나 예약 저장이 실패하면 전체 트랜잭션이 abort됩니다. 성공하면 commit 시점에 하나의 비즈니스 작업처럼 반영됩니다.
실무에서는 read preference 설정도 같이 봐야 합니다. 올리브영 기술 블로그의 MongoDB 트랜잭션 도입 사례에서도 Spring Boot에서 트랜잭션 매니저를 구성할 때 transaction read preference를 primary로 명시합니다. MongoDB 트랜잭션 내부 작업은 기본적으로 Primary에서 수행된다고 이해하면 됩니다.
@Configuration
class MongoTransactionConfig {
@Bean
fun transactionManager(dbFactory: MongoDatabaseFactory): MongoTransactionManager {
val options = TransactionOptions.builder()
.readPreference(ReadPreference.primary())
.readConcern(ReadConcern.SNAPSHOT)
.writeConcern(WriteConcern.MAJORITY)
.build()
return MongoTransactionManager(dbFactory, options)
}
}
위 설정은 예시입니다. 모든 서비스가 항상 snapshot + majority 조합을 써야 한다는 뜻은 아닙니다. 중요한 것은 트랜잭션 경계에서 읽기 일관성과 쓰기 내구성을 명시적으로 결정할 수 있다는 점입니다.
멀티 도큐먼트 트랜잭션으로 얻는 이점은 명확합니다.
- 여러 컬렉션 write를 commit/rollback 단위로 묶을 수 있습니다.
- 예약, 재고, 결제처럼 하나의 업무 규칙에 묶인 데이터를 함께 보호할 수 있습니다.
- 트랜잭션 commit 이후에만 외부 이벤트를 발행하는 식으로 후속 처리 시점을 제어하기 쉬워집니다.
다만 외부 API 호출까지 DB 트랜잭션으로 rollback할 수 있는 것은 아닙니다. 결제 승인 같은 외부 상태 변경은 outbox, 보상 트랜잭션, 멱등키 같은 별도 장치가 필요합니다.
트랜잭션의 비용
MongoDB 멀티 도큐먼트 트랜잭션은 단일 도큐먼트 write보다 비용이 큽니다.
- 트랜잭션 시작 시 스냅샷 획득
- 트랜잭션 중 write set 추적
- 커밋 시 모든 write를 원자적으로 반영
- oplog 기록 및 복제 비용
- 기본 트랜잭션 제한 시간 60초
- lock 획득 대기와 write conflict 재시도 가능성
단일 도큐먼트 write ████░░░░░░ 빠름
멀티 도큐먼트 트랜잭션 ████████░░ 느림 (스냅샷 + oplog + 커밋 비용)
MongoDB 공식 문서의 권장사항도 이 방향입니다. 많은 경우에는 적절한 데이터 모델링으로 멀티 도큐먼트 트랜잭션 필요성을 줄이고, 정말 필요한 곳에만 트랜잭션을 쓰는 것이 좋습니다.
먼저 도큐먼트 설계로 해결하고, 그래도 안 되면 트랜잭션을 사용하라.
oplog에는 어떻게 기록될까
Replica Set을 쓰면 Primary에서 일어난 write는 oplog에 기록되고, Secondary는 이 oplog를 따라가며 같은 작업을 적용합니다.
oplog는 local 데이터베이스의 oplog.rs 컬렉션에 저장됩니다. 일반적인 애플리케이션 데이터베이스에 있는 컬렉션이 아니라, 각 replica set 멤버가 로컬로 갖는 복제용 capped collection입니다.
use local
db.oplog.rs.find().sort({ $natural: -1 }).limit(3).pretty()
실제로 보면 대략 이런 형태의 문서가 저장됩니다. MongoDB 버전과 작업 종류에 따라 필드는 달라질 수 있습니다.
{
"op": "u",
"ns": "stayops.room_inventory",
"ui": UUID("..."),
"o": {
"$v": 2,
"diff": {
"u": {
"remaining": 2
}
}
},
"o2": {
"_id": ObjectId("...")
},
"ts": Timestamp({ t: 1776560000, i: 1 }),
"t": Long(12),
"v": Long(2),
"wall": ISODate("2026-04-19T12:00:00Z")
}
여기서 자주 보는 필드는 다음과 같습니다.
| 필드 | 의미 |
|---|---|
op | 작업 종류입니다. i는 insert, u는 update, d는 delete, c는 command를 뜻합니다. |
ns | 작업이 적용된 namespace입니다. 보통 database.collection 형태입니다. |
o | 실제 작업 내용입니다. update라면 변경 diff나 update 문서가 들어갑니다. |
o2 | update/delete 대상 식별자처럼 보조 조건이 들어갑니다. |
ts | oplog timestamp입니다. Secondary가 어디까지 따라왔는지 판단하는 기준이 됩니다. |
t | replica set term입니다. Primary 선출과 관련됩니다. |
wall | 사람이 읽기 쉬운 실제 시각입니다. |
멀티 도큐먼트 트랜잭션이라고 해서 반드시 “트랜잭션 전체가 하나의 oplog 문서”로만 저장되는 것은 아닙니다. MongoDB 문서는 트랜잭션의 write 작업이 여러 oplog entry를 만들 수 있고, 각 oplog entry는 BSON 문서 크기 제한인 16MiB를 넘을 수 없다고 설명합니다.
이 점은 운영상 꽤 중요합니다. 트랜잭션 하나가 너무 많은 도큐먼트를 바꾸면 oplog 양이 커지고, Secondary가 따라가야 할 작업도 많아집니다. 복제 지연(replication lag)이 커질 수 있고, 장애 복구 시 oplog catch-up 시간이 늘어날 수 있습니다.
정리하면 oplog는 단순한 로그 파일이 아니라 replica set 복제의 기준선입니다.
Client write
-> Primary가 데이터 변경
-> Primary가 local.oplog.rs에 작업 기록
-> Secondary가 oplog를 읽어 같은 순서로 적용
-> writeConcern에 따라 어느 시점에 클라이언트에 성공 응답
writeConcern: "majority"는 이 흐름에서 “과반수 멤버가 해당 write를 확인했는가”를 성공 기준으로 삼습니다. 그래서 단일 도큐먼트 원자성, 트랜잭션, oplog, write concern은 서로 따로 노는 개념이 아니라 같은 쓰기 경로의 서로 다른 층위입니다.
정리: 세 가지 층위
┌─────────────────────────────────────────────────┐
│ 3층: 멀티 도큐먼트 트랜잭션 │
│ 여러 컬렉션에 걸친 원자적 write │
│ Replica Set 필수, 성능 비용 있음 │
├─────────────────────────────────────────────────┤
│ 2층: Read/Write Concern │
│ 데이터 읽기/쓰기의 일관성 수준 조절 │
│ majority, snapshot 등 조합 │
├─────────────────────────────────────────────────┤
│ 1층: 단일 도큐먼트 원자성 │
│ WiredTiger의 document-level concurrency + MVCC │
│ 도큐먼트 설계로 트랜잭션 필요성을 줄임 │
└─────────────────────────────────────────────────┘
RDB에서 넘어온 개발자가 가장 먼저 할 일은 “트랜잭션을 어떻게 거는가”가 아니라, **“이 데이터를 하나의 도큐먼트로 설계할 수 있는가”**를 먼저 묻는 것입니다. 1층에서 해결되면 가장 좋고, 안 되면 2층으로, 그래도 안 되면 3층으로 올라가는 것이 MongoDB의 트랜잭션 전략입니다.
의문점: NoSQL은 RDB에 비해 빠르다?
흔히 “NoSQL은 RDB보다 빠르다”라는 말을 듣습니다. 반은 맞고 반은 위험한 말입니다.
MongoDB가 빠를 수 있는 대표적인 상황은 접근 패턴이 도큐먼트 모델과 잘 맞을 때입니다.
주문 상세 화면에 필요한 데이터:
- 주문 기본 정보
- 주문 항목
- 배송지
- 결제 요약
이 데이터가 하나의 주문 도큐먼트에 들어 있다면 애플리케이션은 한 번의 조회로 화면에 필요한 대부분의 데이터를 가져올 수 있습니다. RDB였다면 orders, order_items, shipments, payments를 join하거나 여러 번 조회해야 할 수 있습니다. 이 경우 MongoDB의 임베딩 모델은 네트워크 왕복, join 비용, 트랜잭션 경계를 줄일 수 있습니다.
하지만 이것이 “MongoDB 엔진이 항상 RDB 엔진보다 빠르다”는 뜻은 아닙니다.
예를 들어 다음 상황에서는 RDB가 더 자연스러울 수 있습니다.
- 데이터 관계가 복잡하고 ad-hoc join이 자주 필요합니다.
- 강한 외래키 제약과 정규화가 업무 규칙을 단순하게 만듭니다.
- 여러 엔티티가 독립적으로 자주 바뀌어 임베딩하면 중복 갱신이 많아집니다.
- 쿼리 패턴이 자주 바뀌어 특정 도큐먼트 구조에 맞춰 최적화하기 어렵습니다.
AWS의 NoSQL 설명도 NoSQL 데이터베이스를 특정 데이터 모델과 접근 패턴에 맞춰 성능과 확장성을 얻는 선택지로 설명합니다. MongoDB 공식 문서 역시 데이터 모델링을 할 때 애플리케이션의 query pattern과 데이터 관계를 먼저 보라고 말합니다.
즉 질문은 이렇게 바꾸는 편이 정확합니다.
"NoSQL이 RDB보다 빠른가?"
-> "이 서비스의 읽기/쓰기 패턴은 MongoDB 도큐먼트 모델에 맞는가?"
맞으면 MongoDB는 매우 빠르고 단순해질 수 있습니다. 맞지 않으면 트랜잭션, 중복 갱신, $lookup, 애플리케이션 조립 비용이 늘어나면서 기대했던 장점이 사라질 수 있습니다.
그래서 MongoDB를 고를 때는 “NoSQL이라 빠르다”보다 “함께 읽고 함께 바꾸는 데이터를 한 도큐먼트로 모델링할 수 있다”가 더 좋은 근거입니다.
마무리
MongoDB는 트랜잭션이 없는 데이터베이스가 아닙니다. 다만 트랜잭션을 사용하는 순서가 RDB와 다릅니다.
- 단일 도큐먼트 원자성으로 해결할 수 있는지 먼저 봅니다.
- 읽기/쓰기 보장 수준은 Read Concern과 Write Concern으로 조절합니다.
- 여러 도큐먼트를 반드시 하나의 작업으로 묶어야 할 때 멀티 도큐먼트 트랜잭션을 사용합니다.
- Replica Set에서는 oplog가 이 write 흐름을 복제하고 복구하는 기준이 됩니다.
결국 MongoDB의 트랜잭션 전략은 기능 하나의 문제가 아니라 데이터 모델링, 동시성 제어, 복제, 운영 비용이 함께 얽힌 문제입니다. RDB처럼 트랜잭션부터 떠올리기보다, 원자성의 경계를 어디에 둘지 먼저 정하는 것이 MongoDB다운 접근이라고 볼 수 있습니다.
출처
- MongoDB: Transactions
- MongoDB: Transactions Production Considerations
- MongoDB: Atomicity and Transactions
- MongoDB: Read Concern
- MongoDB: Write Concern
- MongoDB: WiredTiger Storage Engine
- MongoDB: Replica Set Oplog
- MongoDB: Embedded Data Models
- MongoDB: Data Modeling
- OliveYoung Tech Blog: Spring Boot MongoDB 트랜잭션 도입 실전 가이드
- AWS: What is NoSQL?
- PostgreSQL: Glossary - ACID
- IBM: ACID properties of transactions
