코딩하는 털보

Mongodb Replica set 본문

IT Study/Database

Mongodb Replica set

이정인 2023. 4. 27. 13:07

Mongodb Replica set

Mongodb의 replication 방식

Mongodb에서 Replica set을 구성하려면 Failover를 위해 최소 3개의 노드가 필요함.

이는 데이터 복제본이 3개여야 하는건 아니고 장애 복구 시 어떤 노드를 Primary로 승급하는지 투표를 하기 위해 3개의 노드가 필요한 것.

그렇기 때문에 복제 노드를 2개 추가로 구성(PSS)하거나 또는 복제 노드 구성에 대한 부담을 줄이기 위해 복제 노드 하나와 오직 투표만 수행하는 Arbiter를 구성(PSA)하는 두가지 방식을 통해 Replica set을 구성할 수 있다.

동기화

secondary node는 primary node의 oplog(데이터 작업 로그)를 사용하여 데이터를 비동기적으로 적용한다.

이로써 node가 실패하더라도 복제본 세트로 계속 동작할 수 있도록 한다.

Replication Lag and Flow Control

만약 데이터 복제 시간이 커진다면 (building cache pressure 등) 심각한 문제가 발생할 수 있다. MongoDB 4.2부터 flowControlTargetLagSeconds 로 primary node에서 발생하는 쓰기 적용 속도를 제한할 수 있다.

Auto Failover

electionTimeoutMillis 이상으로 Primary node가 통신이 되지 않는다면 새로운 Primary node를 선출하기 위한 선거를 시작한다.

클러스터는 적합한 새로운 Primary node를 선출하며, 선거 완료 전까지는 데이터 쓰기 작업을 허용하지 않는다.

Read Operation

secondary node에서도 읽기 작업이 가능하다. 클라이언트에서 read preference를 통해 읽기 요청을 Primary 대신 secondary node로 보낼 수 있다.

Docker Compose 활용 테스트 (server 218)

Primary node 이미지 생성

setReplication.js

config = {
  _id : "rs",
  members: [
    {_id:0,host : "mongodb1:27017"},
    {_id:1,host : "mongodb2:27017"},
    {_id:2,host : "mongodb3:27017"},
  ]
}
rs.initiate(config);
rs.conf();

setup.sh

sleep 5 | echo "Waiting for the servers to start..."
mongo mongodb://localhost:27017 /usr/src/configs/init/setReplication.js

Dockerfile

FROM mongo
​
WORKDIR /usr/src
RUN mkdir configs
WORKDIR /usr/src/configs
​
COPY init/* init/
​
RUN chmod +x init/setup.sh
​
CMD ["/usr/src/configs/init/setup.sh"]

build

docker build -t mongo-rep:latest .

서비스 구성

Security key 생성

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

keyFile은 접근 권한 설정이 필요하여 400으로 변경하였으며

mongodb 사용자가 읽을 수 있어야하기 때문에 mongo docker image의 기본 mongodb의 ID인 999번 사용자가 파일을 읽을 수 있도록 변경.

docker network 생성

docker network create mongodb-network 

docker-compose.yml

version: '3'
​
services:
  mongodb1:
    container_name: mongodb-node-1
    image: mongo
    restart: always
    volumes:
      - ./key:/etc/key
    environment:
      - MONGO_INITDB_ROOT_USERNAME=mongoadmin
      - MONGO_INITDB_ROOT_PASSWORD=mongoadmin
      - TZ=${TZ}
    ports:
      - 57018:27017
    command:
      - '--replSet'
      - 'rs'
      - '--keyFile'
      - '/etc/key'
      - '--bind_ip_all'
​
  mongodb2:
    container_name: mongodb-node-2
    image: mongo
    restart: always
    volumes:
      - ./mongod.conf:/etc/mongod.conf
    environment:
      - MONGO_INITDB_ROOT_USERNAME=mongoadmin
      - MONGO_INITDB_ROOT_PASSWORD=mongoadmin
      - TZ=${TZ}
    ports:
      - 57019:27017
    depends_on:
      - mongodb1
    command:
      - '--replSet'
      - 'rs'
      - '--keyFile'
      - '/etc/key'
      - '--bind_ip_all'
​
  mongodb3:
    container_name: mongodb-node-3
    image: mongo
    restart: always
    volumes:
      - ./mongod.conf:/etc/mongod.conf
    environment:
      - MONGO_INITDB_ROOT_USERNAME=mongoadmin
      - MONGO_INITDB_ROOT_PASSWORD=mongoadmin
      - TZ=${TZ}
    ports:
      - 57020:27017
    depends_on:
      - mongodb1
    command:
      - '--replSet'
      - 'rs'
      - '--keyFile'
      - '/etc/key'
      - '--bind_ip_all'
​
networks:
  default:
    name: mongodb-network
    external: true

primary로 구성할 mongodb-node-1만 실행하여 replica set 구성을 시작한다.

docker-compose up -d mongodb-node-1
rs.initiate();

데이터 복제 테스트를 위해 primary node로 사용할 mongodb-node-1에 미리 데이터를 집어넣는다.

db.test.insertOne({"name" : "rockintuna", "team" : "dev"});

아직은 복제 구성이 아니므로 당연히 2,3번 노드에는 데이터가 없다. (replSet 옵션으로 replication mode로 실행되었기 때문에 사실 아직 조회도 불가능.)

Replica-set에 2,3번 노드를 추가한다.

rs.add({ host: "mongodb2:27017" })
rs.add({ host: "mongodb3:27017" })

2,3번 노드 컨테이너를 실행한다.

docker-compose up -d

2,3번 노드에 데이터가 복제되어 있는지 확인한다.

db.test.find();

Replica-set 상태를 확인한다.

rs.status();
[
  {
    "$clusterTime": {
      "clusterTime": {"$timestamp": {"t": 1682563018, "i": 1}},
      "signature": {
        "hash": {"$binary": {"base64": "aejeu8pYhT/egfIUJpsYNuzXfGE=", "subType": "00"}},
        "keyId": 7226547681160593413
      }
    },
    "date": {"$date": "2023-04-27T02:37:04.211Z"},
    "electionCandidateMetrics": {
      "lastElectionReason": "electionTimeout",
      "lastElectionDate": {"$date": "2023-04-27T02:15:48.309Z"},
      "electionTerm": 1,
      "lastCommittedOpTimeAtElection": {
        "ts": {"$timestamp": {"t": 1682561747, "i": 1}},
        "t": -1
      },
      "lastSeenOpTimeAtElection": {
        "ts": {"$timestamp": {"t": 1682561747, "i": 1}},
        "t": -1
      },
      "numVotesNeeded": 1,
      "priorityAtElection": 1,
      "electionTimeoutMillis": 10000,
      "newTermStartDate": {"$date": "2023-04-27T02:15:48.595Z"},
      "wMajorityWriteAvailabilityDate": {"$date": "2023-04-27T02:15:48.790Z"}
    },
    "heartbeatIntervalMillis": 2000,
    "lastStableRecoveryTimestamp": {"$timestamp": {"t": 1682563008, "i": 1}},
    "majorityVoteCount": 2,
    "members": [
      {
        "_id": 0,
        "name": "d0eb51ac6e5c:27017",
        "health": 1,
        "state": 1,
        "stateStr": "PRIMARY",
        "uptime": 1318,
        "optime": {
          "ts": {"$timestamp": {"t": 1682563018, "i": 1}},
          "t": 1
        },
        "optimeDate": {"$date": "2023-04-27T02:36:58.000Z"},
        "lastAppliedWallTime": {"$date": "2023-04-27T02:36:58.815Z"},
        "lastDurableWallTime": {"$date": "2023-04-27T02:36:58.815Z"},
        "syncSourceHost": "",
        "syncSourceId": -1,
        "infoMessage": "",
        "electionTime": {"$timestamp": {"t": 1682561748, "i": 1}},
        "electionDate": {"$date": "2023-04-27T02:15:48.000Z"},
        "configVersion": 5,
        "configTerm": 1,
        "self": true,
        "lastHeartbeatMessage": ""
      },
      {
        "_id": 1,
        "name": "mongodb2:27017",
        "health": 1,
        "state": 2,
        "stateStr": "SECONDARY",
        "uptime": 461,
        "optime": {
          "ts": {"$timestamp": {"t": 1682563018, "i": 1}},
          "t": 1
        },
        "optimeDurable": {
          "ts": {"$timestamp": {"t": 1682563018, "i": 1}},
          "t": 1
        },
        "optimeDate": {"$date": "2023-04-27T02:36:58.000Z"},
        "optimeDurableDate": {"$date": "2023-04-27T02:36:58.000Z"},
        "lastAppliedWallTime": {"$date": "2023-04-27T02:36:58.815Z"},
        "lastDurableWallTime": {"$date": "2023-04-27T02:36:58.815Z"},
        "lastHeartbeat": {"$date": "2023-04-27T02:37:02.361Z"},
        "lastHeartbeatRecv": {"$date": "2023-04-27T02:37:02.549Z"},
        "pingMs": 0,
        "lastHeartbeatMessage": "",
        "syncSourceHost": "mongodb3:27017",
        "syncSourceId": 2,
        "infoMessage": "",
        "configVersion": 5,
        "configTerm": 1
      },
      {
        "_id": 2,
        "name": "mongodb3:27017",
        "health": 1,
        "state": 2,
        "stateStr": "SECONDARY",
        "uptime": 459,
        "optime": {
          "ts": {"$timestamp": {"t": 1682563018, "i": 1}},
          "t": 1
        },
        "optimeDurable": {
          "ts": {"$timestamp": {"t": 1682563018, "i": 1}},
          "t": 1
        },
        "optimeDate": {"$date": "2023-04-27T02:36:58.000Z"},
        "optimeDurableDate": {"$date": "2023-04-27T02:36:58.000Z"},
        "lastAppliedWallTime": {"$date": "2023-04-27T02:36:58.815Z"},
        "lastDurableWallTime": {"$date": "2023-04-27T02:36:58.815Z"},
        "lastHeartbeat": {"$date": "2023-04-27T02:37:03.233Z"},
        "lastHeartbeatRecv": {"$date": "2023-04-27T02:37:03.583Z"},
        "pingMs": 0,
        "lastHeartbeatMessage": "",
        "syncSourceHost": "d0eb51ac6e5c:27017",
        "syncSourceId": 0,
        "infoMessage": "",
        "configVersion": 5,
        "configTerm": 1
      }
    ],
    "myState": 1,
    "ok": 1,
    "operationTime": {"$timestamp": {"t": 1682563018, "i": 1}},
    "optimes": {
      "lastCommittedOpTime": {
        "ts": {"$timestamp": {"t": 1682563018, "i": 1}},
        "t": 1
      },
      "lastCommittedWallTime": {"$date": "2023-04-27T02:36:58.815Z"},
      "readConcernMajorityOpTime": {
        "ts": {"$timestamp": {"t": 1682563018, "i": 1}},
        "t": 1
      },
      "appliedOpTime": {
        "ts": {"$timestamp": {"t": 1682563018, "i": 1}},
        "t": 1
      },
      "durableOpTime": {
        "ts": {"$timestamp": {"t": 1682563018, "i": 1}},
        "t": 1
      },
      "lastAppliedWallTime": {"$date": "2023-04-27T02:36:58.815Z"},
      "lastDurableWallTime": {"$date": "2023-04-27T02:36:58.815Z"}
    },
    "set": "rs",
    "syncSourceHost": "",
    "syncSourceId": -1,
    "term": 1,
    "votingMembersCount": 3,
    "writableVotingMembersCount": 3,
    "writeMajorityCount": 2
  }
]

데이터 일관성 확인

Primary node에서 데이터를 입력해본다.

db.test.insertOne({"name" : "cyr-pig", "team" : "dev"});

secondary node에서 조회해보면 입력되어 있는 것을 확인할 수 있다.

Auto Failover 테스트

Primary stop

docker-compose stop mongodb1

Replica-set 상태를 확인한다.

rs.status();

기존에 Primary 였던 노드는 (not reachable/healthy) 상태로 바뀌었고 node 2번이 새로운 Primary 노드로 승격되었다. 2번이 새로운 Primary이므로 이제 R/W가 가능하다.

[
  {
    "$clusterTime": {
      "clusterTime": {"$timestamp": {"t": 1682568066, "i": 1}},
      "signature": {
        "hash": {"$binary": {"base64": "wjaS9Sx7VhrNVqSlEjFPfE7VGpc=", "subType": "00"}},
        "keyId": 7226547681160593413
      }
    },
    "date": {"$date": "2023-04-27T04:01:10.013Z"},
    "electionCandidateMetrics": {
      "lastElectionReason": "stepUpRequestSkipDryRun",
      "lastElectionDate": {"$date": "2023-04-27T03:59:26.135Z"},
      "electionTerm": 2,
      "lastCommittedOpTimeAtElection": {
        "ts": {"$timestamp": {"t": 1682567959, "i": 1}},
        "t": 1
      },
      "lastSeenOpTimeAtElection": {
        "ts": {"$timestamp": {"t": 1682567959, "i": 1}},
        "t": 1
      },
      "numVotesNeeded": 2,
      "priorityAtElection": 1,
      "electionTimeoutMillis": 10000,
      "priorPrimaryMemberId": 0,
      "numCatchUpOps": 0,
      "newTermStartDate": {"$date": "2023-04-27T03:59:26.181Z"},
      "wMajorityWriteAvailabilityDate": {"$date": "2023-04-27T03:59:27.161Z"}
    },
    "heartbeatIntervalMillis": 2000,
    "lastStableRecoveryTimestamp": {"$timestamp": {"t": 1682568026, "i": 1}},
    "majorityVoteCount": 2,
    "members": [
      {
        "_id": 0,
        "name": "d0eb51ac6e5c:27017",
        "health": 0,
        "state": 8,
        "stateStr": "(not reachable/healthy)",
        "uptime": 0,
        "optime": {
          "ts": {"$timestamp": {"t": 0, "i": 0}},
          "t": -1
        },
        "optimeDurable": {
          "ts": {"$timestamp": {"t": 0, "i": 0}},
          "t": -1
        },
        "optimeDate": {"$date": "1970-01-01T00:00:00.000Z"},
        "optimeDurableDate": {"$date": "1970-01-01T00:00:00.000Z"},
        "lastAppliedWallTime": {"$date": "2023-04-27T03:59:36.183Z"},
        "lastDurableWallTime": {"$date": "2023-04-27T03:59:26.181Z"},
        "lastHeartbeat": {"$date": "2023-04-27T04:01:00.186Z"},
        "lastHeartbeatRecv": {"$date": "2023-04-27T03:59:35.189Z"},
        "pingMs": 0,
        "lastHeartbeatMessage": "Couldn't get a connection within the time limit",
        "syncSourceHost": "",
        "syncSourceId": -1,
        "infoMessage": "",
        "configVersion": 5,
        "configTerm": 2
      },
      {
        "_id": 1,
        "name": "mongodb2:27017",
        "health": 1,
        "state": 1,
        "stateStr": "PRIMARY",
        "uptime": 5342,
        "optime": {
          "ts": {"$timestamp": {"t": 1682568066, "i": 1}},
          "t": 2
        },
        "optimeDate": {"$date": "2023-04-27T04:01:06.000Z"},
        "lastAppliedWallTime": {"$date": "2023-04-27T04:01:06.186Z"},
        "lastDurableWallTime": {"$date": "2023-04-27T04:01:06.186Z"},
        "syncSourceHost": "",
        "syncSourceId": -1,
        "infoMessage": "",
        "electionTime": {"$timestamp": {"t": 1682567966, "i": 1}},
        "electionDate": {"$date": "2023-04-27T03:59:26.000Z"},
        "configVersion": 5,
        "configTerm": 2,
        "self": true,
        "lastHeartbeatMessage": ""
      },
      {
        "_id": 2,
        "name": "mongodb3:27017",
        "health": 1,
        "state": 2,
        "stateStr": "SECONDARY",
        "uptime": 5340,
        "optime": {
          "ts": {"$timestamp": {"t": 1682568066, "i": 1}},
          "t": 2
        },
        "optimeDurable": {
          "ts": {"$timestamp": {"t": 1682568066, "i": 1}},
          "t": 2
        },
        "optimeDate": {"$date": "2023-04-27T04:01:06.000Z"},
        "optimeDurableDate": {"$date": "2023-04-27T04:01:06.000Z"},
        "lastAppliedWallTime": {"$date": "2023-04-27T04:01:06.186Z"},
        "lastDurableWallTime": {"$date": "2023-04-27T04:01:06.186Z"},
        "lastHeartbeat": {"$date": "2023-04-27T04:01:08.217Z"},
        "lastHeartbeatRecv": {"$date": "2023-04-27T04:01:08.220Z"},
        "pingMs": 0,
        "lastHeartbeatMessage": "",
        "syncSourceHost": "mongodb2:27017",
        "syncSourceId": 1,
        "infoMessage": "",
        "configVersion": 5,
        "configTerm": 2
      }
    ],
    "myState": 1,
    "ok": 1,
    "operationTime": {"$timestamp": {"t": 1682568066, "i": 1}},
    "optimes": {
      "lastCommittedOpTime": {
        "ts": {"$timestamp": {"t": 1682568066, "i": 1}},
        "t": 2
      },
      "lastCommittedWallTime": {"$date": "2023-04-27T04:01:06.186Z"},
      "readConcernMajorityOpTime": {
        "ts": {"$timestamp": {"t": 1682568066, "i": 1}},
        "t": 2
      },
      "appliedOpTime": {
        "ts": {"$timestamp": {"t": 1682568066, "i": 1}},
        "t": 2
      },
      "durableOpTime": {
        "ts": {"$timestamp": {"t": 1682568066, "i": 1}},
        "t": 2
      },
      "lastAppliedWallTime": {"$date": "2023-04-27T04:01:06.186Z"},
      "lastDurableWallTime": {"$date": "2023-04-27T04:01:06.186Z"}
    },
    "set": "rs",
    "syncSourceHost": "",
    "syncSourceId": -1,
    "term": 2,
    "votingMembersCount": 3,
    "writableVotingMembersCount": 3,
    "writeMajorityCount": 2
  }
]
Comments