[NestJS - 트러블 슈팅] 몽고 DB replicaSet 없이는 트랜잭션 불가능

2025. 6. 19. 18:13·Nest.js
728x90
반응형

1. MongoDB 트랜잭션을 NestJS에서 써보자!

뜨도록 프로젝트 진행 중 사용자 프로젝트와 차트를 동시에 생성하는 기능을 만들고 있었다.
여러 컬렉션(chart_parts, user_projects)에 데이터를 쓰는 구조였기 때문에, 당연히 트랜잭션이 필요했다.

const session = await this.chartPartModel.db.startSession();
session.startTransaction();
try {
  await this.chartPartModel.create([{ ... }], { session });
  await this.projectModel.create([{ ... }], { session });
  await session.commitTransaction();
} catch (e) {
  await session.abortTransaction();
  throw e;
} finally {
  session.endSession();
}

하지만 실행하자마자 아래와 같은 에러가 발생했다:

MongoError: Transaction numbers are only allowed on a replica set member or mongos


2. MongoDB에서 트랜잭션은 replicaSet 전용이다

MongoDB 트랜잭션은 복수 문서의 변경 사항을 원자적으로 처리할 수 있도록 설계돼 있다.
하지만 이 기능은 단일 인스턴스에선 동작하지 않고, 반드시 replicaSet 구성이 되어 있어야 한다.

replicaSet?

eplicaSet은 MongoDB의 고가용성(High Availability) 구성 방식 중 하나다. 기본적으로 아래 구조를 갖는다:

  • ➖ Primary: 쓰기 가능한 노드
  • ➖ Secondary: Primary를 복제하는 노드 (읽기 가능)
  • ➖ Arbiter(선택): 투표용 노드 (데이터 저장 X)
     
    💡 그런데 이게 트랜잭션이랑 무슨 관계냐면?

MongoDB는 복수 문서(document)에 대한 트랜잭션을 지원하기 위해 내부적으로 commit/abort 로그와 타임스탬프 동기화가 필요하다. 이게 바로 replicaSet 환경에서만 제공된다.
즉, 트랜잭션 = 로그 레플리케이션 기반의 복제 기능 필요 라는 뜻이다.
 
단일 인스턴스(MongoDB 단일 컨테이너 실행)에서는 내부적으로 이 기능을 꺼둔다.
그래서 replicaSet 없이 트랜잭션을 시도하면 에러가 발생한다.


3. 기존 Docker 환경에선 트랜잭션 불가

기존에 사용하던 MongoDB 컨테이너는 단일 인스턴스였기 때문에 트랜잭션이 작동하지 않았다

docker run -d \
  --name mongodb \
  --restart unless-stopped \
  --network backend \
  -p 27017:27017 \
  -e MONGO_INITDB_ROOT_USERNAME=... \
  -e MONGO_INITDB_ROOT_PASSWORD=... \
  mongo:6.0

4. 트랜잭션을 위한 replicaSet 구성

MongoDB 트랜잭션을 사용하려면 아래처럼 replicaSet을 명시한 컨테이너로 교체해야 한다

docker run -d \
  --name mongodb \
  --restart unless-stopped \
  --network backend \
  -p 27017:27017 \
  -v "$(pwd)/mongo-keyfile:/etc/mongo-keyfile:ro" \
  -e MONGO_INITDB_ROOT_USERNAME=... \
  -e MONGO_INITDB_ROOT_PASSWORD=... \
  mongo:6.0 \
  mongod --auth --replSet rs0 --bind_ip_all --keyFile /etc/mongo-keyfile
  • --replSet rs0: replicaSet 이름을 rs0으로 지정
  • --keyFile: replica 간 인증용 파일. 단일 노드여도 필요
  • --bind_ip_all: 외부 연결 허용

5. rs.initiate로 replicaSet 초기화

컨테이너 실행으로 끝이 아니라 MongoDB 컨테이너 내부에서 replicaSet 구성을 초기화 해줘야한다.

docker exec -it mongodb mongosh
rs.initiate({
  _id: "rs0",
  members: [{ _id: 0, host: "localhost:27017" }]
});

구성 설명

키 설명
_id replicaSet 이름 (rs0)
members 구성원 배열 (여기선 단일 노드)
_id: 0 멤버 인덱스
host 접속 주소 (localhost:27017)

이렇게 하면 하나의 노드를 replicaSet으로 간주하게 된다.


6. connection URI 설정 (진짜 중요)

replicaSet을 구성한 뒤, 기존 URI로는 연결 자체가 안 됐다:

mongodb://user:password@mongodb:27017/database?authSource=admin

에러 로그:

MongooseServerSelectionError: Server selection timed out after 30000 ms

✅ 수정된 URI

mongodb://dev_dddorok:password@mongodb:27017/dddorok?authSource=admin&replicaSet=rs0&authMechanism=SCRAM-SHA-256&directConnection=true

옵션 설명

옵션 설명
replicaSet=rs0 구성한 replicaSet 이름과 일치해야 함
authMechanism=SCRAM-SHA-256 MongoDB 인증 방식 명시
directConnection=true 단일 노드 환경에서 클러스터 탐색 생략하고 바로 연결

7. directConnection 옵션이 없으면 왜 연결이 안 될까?

MongoDB 클라이언트는 replicaSet이 명시된 경우, 해당 노드를 통해 다른 멤버들을 탐색하려고 한다.
하지만 현재는 노드가 하나뿐이라, 탐색 결과가 비어 있고 연결이 실패한다.

directConnection=true는 클러스터 탐색 없이 해당 노드 하나에 바로 연결하라는 의미다.
테스트/개발 환경에서 replicaSet을 구성한 단일 노드에 연결하려면 반드시 포함되어야 한다.


최종 정리

항목 설명
트랜잭션 동작 replicaSet 환경에서만 가능
Docker 실행 --replSet, --keyFile 포함 필수
초기화 rs.initiate() 직접 실행
URI 설정 replicaSet, authMechanism, directConnection 포함 필수
연결 실패 원인 단일 노드에서 directConnection 없이 클러스터 탐색 실패

마무리

이번 프로젝트에서 MongoDB를 처음 도입했는데 트랜잭션 처리를 위해 이렇게 까지 삽질하게 될줄은 몰랐다...
기존 RDB에서는 트랜잭션 처리를 @Transactional 하나로 끝낼 수 있었던 반면, MongoDB에서는 트랜잭션을 쓸 수 있는 환경 자체를 직접 구성해줘야 했다.

특히 아래 두 가지가 인상 깊었다:

  • ➖ Docker로 실행할 때도 replicaSet 설정이 필수

  • ➖ URI에 옵션(replicaSet, directConnection)이 빠지면 연결조차 안 됨

    코드는 제대로 짰는데, 연결이 안 되는 이유가 URI 때문이라는 걸 알기까지 꽤 오래 걸렸다.

MongoDB에서 트랜잭션을 쓰고 싶다면, 코드를 짜기 전에 먼저 환경을 제대로 세팅해야 한다.

MongoDB 트랜잭션은 애플리케이션 코드가 아니라, replicaSet 구성과 연결 설정에서 시작된다.

728x90
반응형

'Nest.js' 카테고리의 다른 글

[NestJS 테스트코드] Postgres, MongoDB 환경에서 유닛 & E2E 테스트 제대로 해보기  (2) 2025.07.09
NestJS 엔티티, 왜 다 public이야? – Spring 개발자의 궁금증 해결  (1) 2025.05.25
[NestJS 인증 흐름] Jwt 토큰 기반 인증 요청 흐름 정리  (1) 2025.05.22
[NestJS 응답/요청 변환기] 내부 camelCase, 외부 snake_case 통일하기  (0) 2025.05.22
[Nest.js] 제로베이스에서 1주일, 실전까지 반나절  (1) 2025.05.22
'Nest.js' 카테고리의 다른 글
  • [NestJS 테스트코드] Postgres, MongoDB 환경에서 유닛 & E2E 테스트 제대로 해보기
  • NestJS 엔티티, 왜 다 public이야? – Spring 개발자의 궁금증 해결
  • [NestJS 인증 흐름] Jwt 토큰 기반 인증 요청 흐름 정리
  • [NestJS 응답/요청 변환기] 내부 camelCase, 외부 snake_case 통일하기
highgarden
highgarden
커밋 하나하나가 쌓여 커다란 정원이 되는 중입니다. 하루하루 정성껏 심어가는 중 https://github.com/highgarden7
  • highgarden
    커밋심는 정원
    highgarden
  • 전체
    오늘
    어제
    • 분류 전체보기 (37)
      • ai (1)
      • devops (2)
      • Nest.js (14)
      • linux (14)
      • 네트워크 (6)
      • git (0)
      • aws (0)
      • docker (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • github
  • 공지사항

  • 인기 글

  • 태그

    Java
    githib action
    nestjs
    Chat GPT
    IP
    Linux
    네트워크
    E2E
    vercel
    springboot
  • 최근 댓글

  • 최근 글

  • 250x250
  • hELLO· Designed By정상우.v4.10.3
highgarden
[NestJS - 트러블 슈팅] 몽고 DB replicaSet 없이는 트랜잭션 불가능
상단으로

티스토리툴바