
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.04. 트랜잭션을 위한 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 mongoshrs.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 구성과 연결 설정에서 시작된다.
'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 |