MongoDB 트랜잭션 활성화를 위한 Replica Set 구성 (feat: Docker)

개발하는 구황작물·2024년 3월 19일
1

개인 프로젝트 중 Mongodb로 트랜잭션을 사용하려면 Replica Set를 구성해야 한다고 해서 작성하게 되었다.

굳이 MongoDB에 트랜잭션을 도입한 이유

선착순 퀴즈 참여 서비스를 주제로 하다보니 여러 유형의 문제(객관식, 단답형, 주관식...) 를 만들 수 있어야 했고 나중에 유형이 더 추가될 수도 있었다. 그렇기 때문에 스키마 변경이 자유로운 MongoDB를 채택하게 되었다.

하지만 프로젝트가 복잡해지면서 단일 문서에 대한 작업보단 여러 문서에 걸친 작업이 많아져 트랜잭션을 도입하게 되었다. (MongoDB의 경우 기본적으로 단일 문서에 대한 작업은 원자적으로 이루어지지만 여러 문서에서는 보장되지 않는다.)

Replica Set

Mongodb Replica Set는 동일한 데이터 세트를 유지관리하는 mongodb 프로세스 그룹으로 중복성과 고가용성을 제공한다.

DB에 장애가 발생하는 경우 빠르게 복구할 수 있는 장점이 있다.

MongoDB에 트랜잭션 사용시 Replica Set가 필요한 이유

공식 답변에 따르면 트랜잭션은 논리적 세션의 개념으로 만들어졌기 때문에, 레플리카 셋 환경에서만 가능한 oplog 와 같은 기술이 필요하다고 한다.

여기서 oplog는 레플리카 셋의 데이터 동기화를 위해 내부에서 발생한 로그를 기록한 것을 뜻한다.

출처

Replica Set 구성

레플리카 셋은 세가지 역할로 나눌 수 있다.

  • Primary : 클라이언트에서 DB Read/Write
  • Secondary : Primary로부터 동기화를 한다. oplog를 복제하여 데이터를 동기화 한다.
  • Arbiter(Optional) : 데이터를 동기화하지는 않으나 Primary 장애시 Secondary 중 누구를 Primary로 선출할지 결정하는 투표권자

여기서 HeartBeat는 Replica Set내의 node 간 정해진 초마다 서로에게 ping를 보낸다. 만약 Heartbeat가 특정 초마다 수신되지 않으면 해당 DB 가 죽었다 판단하고 다른 node끼리 Election을 준비한다.

Replica Set의 구성에는 두가지의 방법이 있다

  • PSS(Primary + Secondary + Secondary)
  • PSA(Primary + Secondary + Arbiter)

PSS는 하나의 Primary + 2개의 Secondary 로 구성되어 있으며, Secondary가 2개나 있어 높은 안정성을 보장한다.

PSA는 Primary, Secondary, Arbiter 이 각각 하나씩 있는 구성이다.
Arbiter는 데이터를 저장하지는 않고 Primary 장애시 Secondary 중 누구를 Primary로 대체할지 Election 하는 기능을 가지고 있다.
PSS 보단 안정성이 낮으나 서버 리소스를 덜 잡아 먹는 장점이 있다.

Docker + MongoDB ReplicaSet 구성방법

  1. docker-compose.yml

먼저 도커 컨테이너 끼리 통신할 외부 네트워크를 생성한다.

> docker network create mongoCluster

docker-compose.yml

services:
  mongodb1:
    image: mongo
    hostname: mongodb1
    container_name: mongodb1
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: <username>
      MONGO_INITDB_ROOT_PASSWORD: <password>
    volumes:
      - ./mongo/mongo1/mongod.conf:/etc/mongod.conf
      - ./key/mongodb.key:/etc/mongodb.key
      - ./data/mongodb1:/data/db
    command: mongod --replSet rs0 --port 27017 --keyFile /etc/mongodb.key --bind_ip_all
    ports:
      - 27017:27017
    networks:
      - mongoCluster
  mongodb2:
    image: mongo
    hostname: mongodb2
    container_name: mongodb2
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: <username>
      MONGO_INITDB_ROOT_PASSWORD: <password>
    volumes:
      - ./mongo/mongo2/mongod.conf:/etc/mongod.conf
      - ./key/mongodb.key:/etc/mongodb.key
      - ./data/mongodb2:/data/db
    command: mongod --replSet rs0 --port 27018  --keyFile /etc/mongodb.key --bind_ip_all
    ports:
      - 27018:27018
    networks:
      - mongoCluster
    depends_on:
      - mongodb1
  mongodb3:
    image: mongo
    hostname: mongodb3
    container_name: mongodb3
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: <username>
      MONGO_INITDB_ROOT_PASSWORD: <password>
    volumes:
      - ./mongo/mongo3/mongod.conf:/etc/mongod.conf
      - ./key/mongodb.key:/etc/mongodb.key
      - ./data/mongodb3:/data/db
    command: mongod --replSet rs0 --port 27019  --keyFile /etc/mongodb.key --bind_ip_all
    ports:
      - 27019:27019
    networks:
      - mongoCluster
    depends_on:
      - mongodb1



networks:
  mongoCluster:
    external: true
  1. config 파일 작성

MongoDB의 conf 파일은 etc/mongod.conf에 저장된다.

mongoDB가 총 3개이고 port 가 각기 다르므로 3개를 만들어야 했다.

# mongod.conf

# Where and how to store data.
storage:
  dbPath: /var/lib/mongodb

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log

# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1


# how the process runs
processManagement:
  timeZoneInfo: /usr/share/zoneinfo

# Keyfile 위치 설정
security:
  authorization: enabled
  clusterAuthMode: keyFile
  keyFile: /etc/mongodb.key

# Replica Set 이름 설정
replication:
  replSetName: rs0
  1. 키 설정
mkdir key

cd key

openssl rand -base64 756 > mongodb.key
chmod 400 mongodb.key
chown 999:999 mongodb.key

mac 유저라면 위와 같이 간단히 키 설정을 할 수 있으나
윈도우 유저라면 위와 같은 방식으로는 키 설정이 되지 않을 것이다.(아마도)

필자 노트북은 Windows라 위의 방법을 사용하지 못했다. (맥북 갖고 싶다)

결국 이 블로그 글을 통해 Docker의 volume mount 기능을 활용하여 권한 설정을 할 수 있었다.

3-1. nginx 생성을 위한 docker-compose.yml 생성

cd nginx

vim docker-compose.yml

docker-compose.yml

version: "3"

services: 
  nginx: 
    image: nginx
    container_name: nginx
    ports: 
      - "3001:80"
    volumes:
      - ./key:/key

3-2. nginx 실행

docker-compose up -d

3-3. docker desktop나 docker exec -it nginx bash로 컨테이너 접속 후 키 파일 생성

cd /key
openssl rand -base64 756 > mongodb.key
chmod 400 mongodb.key
chown 999:999 mongodb.key

이러면 volume 마운트로 인해 권한 설정이 된 키 파일이 로컬에도 생성 될 것이다.

(참고로 docker-compose는 해당 파일 기준으로 파일위치를 설정해줘야 한다. 만약 docker-compose.yml이 /filename에 위치하고 key file이 /filename/key에 위치한다면 volume에는 키 위치를 ./key/mongodb.key로 설정해주어야 한다. )

  1. mongodb docker-compose.yml 실행
docker-compose up -d

이후 docker desktop의 mongodb1의 exec에 들어가서 replication set 초기화를 해주면 된다.

# mongosh 접속
mongosh -u <username> -p <password>

# 사용자 전환
test> use admin

# 초기화
admin> rs.initiate({_id:"rs0", members:[{_id: 0, host:"mongodb1:27017"},{_id: 1, host: "mongodb2:27018"},{_id: 2, host: "mongodb3:27019"}]})
  1. 확인

rs.status() 로 확인

Primary DB shell에 테스트로 DB와 collection 을 생성 한 후 데이터를 insert 한 후, Secondary에서 확인해보자

# Primary DB exec
rs0 [direct: primary] admin> use testdb
switched to db testdb
rs0 [direct: primary] testdb> db.createCollection("testCollection")
{ ok: 1 }
rs0 [direct: primary] testdb> show dbs
admin   140.00 KiB
config  192.00 KiB
local   452.00 KiB
testdb    8.00 KiB
rs0 [direct: primary] testdb> db.testcollection.insert({"name": "test"})
DeprecationWarning: Collection.insert() is deprecated. Use insertOne, insertMany, or bulkWrite.
{
  acknowledged: true,
  insertedIds: { '0': ObjectId('65fa2e223456c76da7db83b0') }
}
rs0 [direct: primary] testdb> db.testcollection.find()
[ { _id: ObjectId('65fa2e223456c76da7db83b0'), name: 'test' } ]

############# Secondary DB exec ###############
rs0 [direct: secondary] test> rs.secondaryOk()

rs0 [direct: secondary] test> use admin
switched to db admin
rs0 [direct: secondary] admin> db.auth("root", "password1!")
{ ok: 1 }

rs0 [direct: secondary] admin> show dbs
admin   140.00 KiB
config  244.00 KiB
local   468.00 KiB
testdb   48.00 KiB

rs0 [direct: secondary] admin> use testdb
switched to db testdb

rs0 [direct: secondary] testdb> db.testcollection.find()
[ { _id: ObjectId('65fa2e223456c76da7db83b0'), name: 'test' } ]

보다시피 Primary에서 insert한 데이터가 Secondary에도 저장되있는 것을 확인할 수 있다.


+) 추가적으로 windows에서 C:\Windows\System32\drivers\etc\hosts 에 아래와 같은 내용을 추가하여 도커의 호스트 이름과 로컬 IP 주소를 매핑 시켜줘야 한다.

127.0.0.1 mongodb1
127.0.0.1 mongodb2
127.0.0.1 mongodb3

(mac의 경우 /etc/hosts에 추가)

profile
어쩌다보니 개발하게 된 구황작물

0개의 댓글