[DB] mongoDB에서 트랜잭션기능을 활용할 수 없을때...(feat. 보상트랜잭션)

여리·2025년 6월 2일

해당 내용은 NestJS + mongoDB(mongoose) 으로 작업한 내용.

✅ 문제의 직면: 진행하고 있는 프로젝트에서는 RDBMS(mysql), NoSQL(mongoDB)를 사용하고 있다. 특정 기능에서는 mongoDB의 데이터를 활용하고있는데 mongoDB에서는 트랜잭션을 기본적으로 지원하지 않는다는 것을 알게 되었고, 진행하는 프로젝트에서는 트랜잭션을 환경을 구성하지 않았다. 구성하게 되면 추가적인 리소스 및 비용이 발생할 수 있는데, 현재상황에서는 필요하지 않은 상황이었다. 다만 트랜잭션과 같은 개념을 기능을 사용해야 하는 상황이었다.

그래서, 어떻게 해결했어야 했는가?
👉 실패 또는 에러가 발생할 수 있는 영역에서 트랜잭션 기능이 되지 않는다면 데이터에 대한 오염과 데이터 신뢰 보장이 되지 않기때문에 기존의 롤백(원복)을 위한 추가적인 처리가 필요했다. 그래서 가장 원시적으로 보상 트랜잭션을 선택했다. 게다가, 이번문제는 외부 API 호출까지 포함된 내용(애플리케이션 레벨 트랜잭션)으로 이런 개념은 애플리케이션 레벨의 보상 트랜잭션의 개념으로 접근해야한다.

💡 애플리케이션 레벨 보상 트랜잭션이란?

트랜잭션이 지원되지 않는 환경에서 데이터 일관성을 어떻게 지킬 것인가?


📌 1. 왜 이 주제가 중요한가?

일반적으로 우리는 데이터베이스 트랜잭션 기능(ACID)을 통해 작업의 원자성을 보장합니다. 하지만 현실에서는 다음과 같은 상황에 자주 부딪힙니다:

  • MongoDB 단일 노드 환경 (replica set 아님) 또는 트랜잭션을 지원하지 않는 버전 이하의 상황텍스트
  • 외부 API나 다른 DB와의 복합 작업
  • NestJS + Mongoose 환경 등에서의 제한
  • 복잡한 분산 시스템

이럴 때는 DB 트랜잭션을 쓸 수 없기 때문에, 대신 "애플리케이션 레벨 보상 트랜잭션"을 구현하여 시스템의 일관성과 신뢰성을 유지해야 합니다.


⚙️ 2. 애플리케이션 레벨 보상 트랜잭션이란?

실제로 DB 트랜잭션 기능을 쓰지 않지만, 트랜잭션처럼 작동하는 구조를 코드 수준에서 구현하는 것을 말합니다.

✅ 개념

  • 복수의 작업 중 하나라도 실패하면, 이미 수행한 작업들을 명시적으로 원래 상태로 되돌리는(보상하는) 방식.
  • 트랜잭션처럼 보장하려는 속성은 원자성(Atomicity)일관성(Consistency)입니다.

🧪 3. 실전 예제: 학습 이력 저장 시 유사 트랜잭션 적용

let backup = null;

try {
  // 1. 기존 상태 백업 (lean()으로 순수 객체 확보)
  backup = await this.DocumentModel.findOne({ _id }).lean();

  // 2. 업데이트 시도
  const updated = await this.DocumentModel.findOneAndUpdate(
    { _id },
    { $set: { targetField: JSON.parse(Dto.field) } },
    { new: true }
  );

  if (!updated) {
    throw new Error("업데이트 실패");
  }

  // 3. 외부 보상/리워드 서비스 호출 또는 외부 API 호출
  await this.rewardService.createReward(); // 여기서 실패 가능

} catch (err) {
  // 4. 실패 시 수동 롤백 또는 별도의 util 함수 생성
  if (learnHistoryBackup) {
    await this.LearnHistoryModel.updateOne(
      { _id: learnHistoryBackup._id },
      { $set: { targetField: backup.field } }
    );
  }

  throw err;
}

🔍 설명

  • lean()을 통해 원래 데이터를 백업
  • 실패 발생 시, 이전 상태로 복원
  • 전체 작업을 트랜잭션처럼 보이게 만드는 구조

🧠 4. 그럼 이걸 '트랜잭션'이라고 부르는 게 맞을까?

✔️ 결론: "보상 트랜잭션"은 트랜잭션 기능은 없지만, 트랜잭션 개념에는 포함됩니다.

✅ 이유

  • 기능적으로는 트랜잭션 아님 (ACID 보장 X)
  • 그러나 설계 목적은 트랜잭션과 동일 (All-or-Nothing 보장)
  • 실제로 “보상 트랜잭션(Compensating Transaction)”은 마이크로서비스, Saga 패턴에서 널리 쓰이는 정식 아키텍처 용어

🧩 5. 핵심 정리표

항목설명
트랜잭션 기능 사용 여부❌ 없음
트랜잭션 개념 포함 여부✅ 포함됨
실패 시 롤백 가능 여부✅ 명시적 복원
설계 목적작업의 원자성과 데이터 일관성 유지
용어 적절성✅ “보상 트랜잭션”이라는 용어는 업계에서 통용됨

📝 6. 실무 적용 시 유의사항

  • 백업 데이터를 신뢰할 수 있어야 함 (lean() 또는 별도 Snapshot)
  • 보상 로직은 작업 단위별로 명확히 정의되어야 함
  • 외부 API나 DB와 함께 작동할 경우 에러 케이스를 잘 정의해야 함
  • 보상 트랜잭션 자체가 실패할 수 있으므로 이중 체크 로직이 필요할 수도 있음

✍️ 마무리 한 줄 요약

"보상 트랜잭션은, 트랜잭션이 기능이 아니라 책임이라는 사실을 기억하게 만든다. 기능보다는 개념에 주안점을 둘 수 있도록 한다."


profile
beckend developer

0개의 댓글