[DB] MongoDB Replication

MongoDB를 사용시, 동일한 데이터의 복제본을 여러 개 만들어서 데이터의 가용성을 높일 수 있다고 한다!

MongoDB Replication을 위한 용어들

  • mongod MongoDB 시스템을 위한 primary daemon process
  • replica set 같은 데이터셋을 유지하는 mongod의 그룹으로 데이터의 중복성을 제공하고, 가용성을 높인다.

위의 그림처럼 클라이언트는 secondary에 데이터를 쓸 수는 없지만, 읽을 수는 있다.
primary의 사용이 불가능해지면, secondary 중 어떤 것이 primary될지 election을 거친다.

  • heartbeat replicat set member끼리는 서로에게 매 2초마다 서로 ping을 보내서 상태를 확인한다.
    10초 후에도 return 이 안 오면 그 member는 접근 불가인 것으로 판단한다.

Replication 설정 방법

  1. 각 mongod 컨테이너가 작동할 docker network 생성

    docker network create mongoCluster

  2. mongod 컨테이너간의 인증, 클라이언트 access control 위해 keyfile 생성

    openssl rand -base64 756 > mongodb.key // openssl 이용하여 공유 암호로 사용하기 위한 complex pseudo-random 1024 character string을 생성
    chmod 400 mongodb.key //사용자에게만 read 권한 설정

  3. docker-compose.yml에 3개의 mongodb 컨테이너 정보 작성

    //docker-compose.yml
    version: '3.8'
    
    services:
      mongo_1:
        image: mongo:latest
        container_name: mongo_1
        restart: always
        environment:
          MONGO_INITDB_ROOT_USERNAME: root
          MONGO_INITDB_ROOT_PASSWORD: root
        ports:
          - 27017:27017
        volumes:
          - ./data/db/replica/mongo_1:/data/db
          - ./mongodb.key:/etc/mongodb.key
        command:
          - '--replSet'
          - 'myReplicaSet'
          - '--keyFile'
          - '/etc/mongodb.key'
          - '--bind_ip_all'
    
        # command 내용
          # replicaSet의 이름은 myReplicaSet으로 한다.
          # keyFile은 /etc/mongodb.key를 사용한다.
          # --bind_ip_all로 외부에서 클라이언트 접속이 가능하게 한다.
        
      mongo_2:
        image: mongo:latest
        container_name: mongo_2
        restart: always
        environment:
          MONGO_INITDB_ROOT_USERNAME: root
          MONGO_INITDB_ROOT_PASSWORD: root
        depends_on:
          - mongo_1
        ports:
          - 27018:27017
        volumes:
          - ./data/db/replica/mongo_2:/data/db
          - ./mongodb.key:/etc/mongodb.key
        command:
          - '--replSet'
          - 'myReplicaSet'
          - '--keyFile'
          - '/etc/mongodb.key'
          - '--bind_ip_all'
    
      mongo_3:
        image: mongo:latest
        container_name: mongo_3
        restart: always
        environment:
          MONGO_INITDB_ROOT_USERNAME: root
          MONGO_INITDB_ROOT_PASSWORD: root
        depends_on:
          - mongo_2
        ports:
          - 27019:27017
        volumes:
          - ./data/db/replica/mongo_3:/data/db
          - ./mongodb.key:/etc/mongodb.key
        command:
          - '--replSet'
          - 'myReplicaSet'
          - '--keyFile'
          - '/etc/mongodb.key'
          - '--bind_ip_all'
    
    networks:
      default:
        name: mongoCluster
  4. 컨테이너 실행

    docker-compose up -d

  5. 띄운 3개의 컨테이너 중 하나에 들어가서 config 적용하여 초기화 설정
➜  mongo_replication docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                      NAMES
0640cfad95e0   mongo:latest   "docker-entrypoint.s…"   6 minutes ago   Up 6 minutes   0.0.0.0:27019->27017/tcp   mongo_3
3dc08fc9a3a5   mongo:latest   "docker-entrypoint.s…"   6 minutes ago   Up 6 minutes   0.0.0.0:27018->27017/tcp   mongo_2
34092fe360a8   mongo:latest   "docker-entrypoint.s…"   6 minutes ago   Up 6 minutes   0.0.0.0:27017->27017/tcp   mongo_1

docker exec -it 34092fe360a8 /bin/shmongosh -u root -p root
현재처럼 초기화를 거치지 않은 상태에서 rs.status()를 입력시 no replset config has been received가 나오면서 별도의 config이 설정되지 않은 것을 확인할 수 있다.

rs.initiate({_id: "myReplicaSet", members: [{ _id: 0, host: "mongo_1" }, { _id: 1, host: "mongo_2" }, { _id: 2, host: "mongo_3" }]});를 입력하면
결과가 {ok:1}이 나오며 설정이 잘 완료된 것을 볼 수 있다.

3개의 컨테이너에 각각 들어가보면 primary, secondary, secondary로 설정된 것을 확인할 수 있다.

또한 rs.status()로 replica set의 config, 상태 등을 확인해볼 수도 있다.

primary와 secondary간에 같은 데이터를 가지는지 확인

insert, find

primary에서 replicaTest라는 db를 생성하고, 하나의 document를 insert한다.
(insertOne이나 insertMany가 아닌 insert 자체는 deprecated되었다고 나오지만 작동은 가능했다.)

secondary에서 find 시도하면 primary가 아니라는 메세지가 나온다.

이 때 rs.secondaryOk()를 입력하면 조회가 가능하다.

기존에 사용하던 rs.slaveOk()와 마찬가지로 rs.secondaryOk()역시 deprecated라고 나왔다.
작동 자체는 가능하였기 때문에 여기서는 rs.slaveOk()로 find 결과를 얻을 수 있었고,
다른 secondary에서는db.getMongo().setReadPref('secondary') 로 같은 결과를 얻을 수 있었다.

당연하지만 이 때 꼭 어떠한 db를 사용할 것인지 명시한 후 (여기서는 use replicaTest) find를 해야 결과가 제대로 나온다.
use <db name> 없이 db.<db name>.find()를 하면 빈 결과가 나온다.

delete, find

secondary에서 document를 삭제하려고 하면 not primary라고 에러가 나오는 것을 볼 수 있다.

primary(아래 이미지에서 왼쪽 터미널)에서 삭제 후 secondary(오른쪽 터미널)에서 확인하니 document가 없어진 것을 확인할 수 있다.

todo

  • election이 되는 과정을 확인해볼 수 있을까?
    사용자가 의도적으로 primary에서 데이터를 삭제하고, 그것이 동일하게 secondary에서도 확인가능한 것과 달리 primary의 사용이 불가해졌을 때 기존의 secondary가 어떻게 그 역할을 하는지 궁금하다.
  • docker network에 대해 좀 더 자세하게 알아보자.

출처

https://www.mongodb.com/docs/manual/replication/
https://www.mongodb.com/compatibility/deploying-a-mongodb-cluster-with-docker
https://www.mongodb.com/docs/manual/tutorial/deploy-replica-set-with-keyfile-access-control/


Written by
Sunmin
어제보다 나은 오늘을 만들기 위해 배우고, 기록하고, 회고합니다. Maker. Reader. Realistic optimist.