개인 프로젝트 중 Mongodb로 트랜잭션을 사용하려면 Replica Set를 구성해야 한다고 해서 작성하게 되었다.
Mongodb Replica Set는 동일한 데이터 세트를 유지관리하는 mongodb 프로세스 그룹으로 중복성과 고가용성을 제공한다.
DB에 장애가 발생하는 경우 빠르게 복구할 수 있는 장점이 있다.
공식 답변에 따르면 트랜잭션은 논리적 세션의 개념으로 만들어졌기 때문에, 레플리카 셋 환경에서만 가능한 oplog 와 같은 기술이 필요하다고 한다.
여기서 oplog는 레플리카 셋의 데이터 동기화를 위해 내부에서 발생한 로그를 기록한 것을 뜻한다.
레플리카 셋은 세가지 역할로 나눌 수 있다.
여기서 HeartBeat는 Replica Set내의 node 간 정해진 초마다 서로에게 ping를 보낸다. 만약 Heartbeat가 특정 초마다 수신되지 않으면 해당 DB 가 죽었다 판단하고 다른 node끼리 Election을 준비한다.
Replica Set의 구성에는 두가지의 방법이 있다
PSS는 하나의 Primary + 2개의 Secondary 로 구성되어 있으며, Secondary가 2개나 있어 높은 안정성을 보장한다.
PSA는 Primary, Secondary, Arbiter 이 각각 하나씩 있는 구성이다.
Arbiter는 데이터를 저장하지는 않고 Primary 장애시 Secondary 중 누구를 Primary로 대체할지 Election 하는 기능을 가지고 있다.
PSS 보단 안정성이 낮으나 서버 리소스를 덜 잡아 먹는 장점이 있다.
먼저 도커 컨테이너 끼리 통신할 외부 네트워크를 생성한다.
> docker network create mongoCluster
docker-compose.yml
version: "3"
services:
mongodb1:
image: mongo
hostname: mongodb1
container_name: mongodb1
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: password1!
volumes:
- ./mongod.conf:/etc/mongod.conf
- ./key/mongodb.key:/etc/mongodb.key
- ./data/mongodb1:/data/db
command: mongod --replSet rs0 --port 27018 --keyFile /etc/mongodb.key --bind_ip_all
ports:
- 27018:27018
networks:
- mongoCluster
mongodb2:
image: mongo
hostname: mongodb2
container_name: mongodb2
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: password1!
volumes:
- ./mongod.conf:/etc/mongod.conf
- ./key/mongodb.key:/etc/mongodb.key
- ./data/mongodb2:/data/db
command: mongod --replSet rs0 --port 27019 --keyFile /etc/mongodb.key --bind_ip_all
ports:
- 27019:27019
networks:
- mongoCluster
depends_on:
- mongodb1
mongodb3:
image: mongo
hostname: mongodb3
container_name: mongodb3
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: password1!
volumes:
- ./mongod.conf:/etc/mongod.conf
- ./key/mongodb.key:/etc/mongodb.key
- ./data/mongodb3:/data/db
command: mongod --replSet rs0 --port 27020 --keyFile /etc/mongodb.key --bind_ip_all
ports:
- 27020:27020
networks:
- mongoCluster
depends_on:
- mongodb1
networks:
mongoCluster:
external: true
# 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:
keyFile: /etc/mongodb.key
# Replica Set 이름 설정
replication:
replSetName: rs0
security 와 replication 설정을 해주어야 한다.
mkdir key
cd key
openssl rand -base64 756 > mongodb.key
chmod 400 mongodb.key
chown 999:999 mongodb.key
mac 유저라면 위와 같이 간단히 키 설정을 할 수 있으나
윈도우 유저라면 위와 같은 방식으로는 키 설정이 되지 않을 것이다.(아마도)
필자는 git bash 를 사용해서 chmod, chown 등등 해보았으나 권한 설정이 되지 않았다.
결국 이 블로그 글을 통해 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 마운트로 인해 권한 설정이 된 키 파일이 로컬에도 생성 될 것이다.
이제 생성된 올바른 키 파일을 mongodb 생성을 위한 docker-compose.yml에 설정하면 된다.
(volumes 변경)
docker-compose.yml
version: "3"
services:
mongodb1:
image: mongo
hostname: mongodb1
container_name: mongodb1
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: password1!
volumes:
- ./mongod.conf:/etc/mongod.conf
- ./key/mongodb.key:/etc/mongodb.key
- ./data/mongodb1:/data/db
command: mongod --replSet rs0 --port 27018 --keyFile /etc/mongodb.key --bind_ip_all
ports:
- 27018:27018
networks:
- mongoCluster
mongodb2:
image: mongo
hostname: mongodb2
container_name: mongodb2
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: password1!
volumes:
- ./mongod.conf:/etc/mongod.conf
- ./key/mongodb.key:/etc/mongodb.key
- ./data/mongodb2:/data/db
command: mongod --replSet rs0 --port 27019 --keyFile /etc/mongodb.key --bind_ip_all
ports:
- 27019:27019
networks:
- mongoCluster
depends_on:
- mongodb1
mongodb3:
image: mongo
hostname: mongodb3
container_name: mongodb3
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: password1!
volumes:
- ./mongod.conf:/etc/mongod.conf
- ./key/mongodb.key:/etc/mongodb.key
- ./data/mongodb3:/data/db
command: mongod --replSet rs0 --port 27020 --keyFile /etc/mongodb.key --bind_ip_all
ports:
- 27020:27020
networks:
- mongoCluster
depends_on:
- mongodb1
networks:
mongoCluster:
external: true
(참고로 docker-compose는 해당 파일 기준으로 파일위치를 설정해줘야 한다. 만약 docker-compose.yml이 /filename
에 위치하고 key file이 /filename/key
에 위치한다면 volume에는 키 위치를 ./key/mongodb.key
로 설정해주어야 한다. )
docker-compose up -d
이후 docker desktop의 mongodb1의 exec에 들어가서 replication set 초기화를 해주면 된다.
# mongosh 접속
mongosh --port 27018
# 사용자 전환
use admin
# 로그인
db.auth("[docker-compose에 적은 username],[docker-compose에 적은 password]")
# 초기화
rs.initiate({
_id: "rs0",
members: [
{_id: 0, host: "mongodb1:27018"},
{_id: 1, host: "mongodb2:27019"},
{_id: 2, host: "mongodb3:27020"}
]
});
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
에 추가)
++ )
참고로 mongodb의 트랜잭션 수준은 Read uncommited, Read commited, Repeatable Read(snapshot)를 제공한다