- 이번 포스팅으로는 RDBMS 중 MySQL 을 Master-Slave 구조로 구현하는 방식을 정리하겠습니다.
- DBMS 분산
DBMS 는 소프트웨어의 가장 중요한 자원이라 할 수 있는 데이터를 직접 저장하고 관리하는 부분이니만큼 매우 중요하면서, 여러 응용 소프트웨어의 요청을 받아들이기에 전체 서비스 내에서 가장 부하가 많이 발생할 수 있는 곳이기도 합니다.
서버-클라이언트 구조에서 서버가 N개의 클라이언트로부터 요청을 받아 처리를 하듯, DBMS 는 N개의 서버로부터 요청을 받아 처리를 한다고 생각하시면 됩니다.
고로 데이터 요청의 부하를 분산하는 방식을 사용하여 전체 서비스의 처리 효율을 높일 수 있을 것입니다.
- Master-Slave 구조

데이터베이스 분산을 통한 부하 분산 기술은 여러 방식으로 구현이 가능할 것입니다.
예를들어 요청을 받아들이는 허브 노드를 둔 뒤 이곳에 등록된 데이터베이스 노드들의 정보를 판단하여 요청에 따라 올바른 위치를 찾아가는 방식도 고려할 수 있고,
모든 노드가 연결되어 실시간 동기화 되는 방식도 고려할 수 있을 것입니다.
마치 네트워크 토폴로지를 설명하는 것 같은데, 프로그래밍에 익숙하신 분이라면, 스스로, 각자 다른 위치에서 동작중인 별도의 DB 들을 연동하여 분산 시스템을 구축한다고 상상해본다면 이외에도 많은 아이디어들이 떠오를 것입니다.
Master-Slave 구조는, 데이터베이스 분산 방식 중 하나로, 하나의 Master 가 데이터를 관리하고, Slave 가 이를 복제하여 유지하는 방식입니다.
Master 서버는 하나로, 데이터베이스의 INSERT, UPDATE, DELETE 연산을 담당하고, 변경사항 발생시 연결된 모든 Slave 서버에 변경사항을 복제합니다.
Slave 서버는 Master 서버에 연결된 N개의 서버로, SELECT 연산만을 담당합니다.
만약 Master 서버가 다운되면 Slave 서버 중 한개를 Master 로 승격시킵니다.
위와 같은 구조로 인하여 분산된 서버간의 연동을 비교적 단순하고 안전하게 구축할 수 있고,
DB 연산 중 가장 많은 부분을 차지하는 SELECT 를 담당하는 서버를 나누어서 요청 부하를 분산시킬 수 있으며,
중요 데이터를 백업하는 효과도 얻을 수 있을 것입니다.
물론 단일 서버에 비해 단점이 존재하는데,
1. Master 서버의 변경 사항을 Slave 서버들로 전파하는 데 까지의 지연 문제
2. 쓰기 부하는 여전히 분산 불가
3. Master 서버 장애시 Slave 가 자동으로 승격되지 않을 가능성도 존재
위와 같습니다.
또한, Master 서버의 변경사항을 Slave 서버들로 전파하는 방식도 여러가지가 존재하는데,
Master 서버 내의 데이터를 변경하고 비동기적으로 Slave 에 전파하는 비동기 방식은,
반영 시간동안 데이터 불일치가 일어날 가능성이 있고,
데이터 동기화까지 Master 가 대기해야 하는 동기 방식은, Master 서버의 읽기 연산 성능을 크게 저하시킬 수 있습니다.
이러한 장단점을 잘 파악해서 구성해야만 하며,
일반적으로는 비동기 방식을 사용하는데, 이렇게 된다면 금전적인 데이터와 같이 민감한 데이터의 경우는 조회 시에도 무조건 Master 서버에서만 데이터를 가져오도록 처리를 해야 할 것입니다.
- 이제 MySQL 을 Master-Slave 구조로 구현하는 방법을 알아보겠습니다.
실습을 위하여 Docker 를 사용하여 별도의 2개의 데이터베이스를 구성한 후 mysql 스크립트로 dba 클러스터 설정을 통하여 만들 것입니다.
Docker 컴포즈 파일은,
services:
mysql1:
container_name: mysql1
image: mysql:latest
restart: always
ports:
- 33061:3306
environment:
# 초기 생성 데이터베이스
MYSQL_DATABASE: first_schema
# root 계정 초기 비밀번호
MYSQL_ROOT_PASSWORD: todo1234!
# 타임존 설정
TZ: Asia/Seoul
volumes:
- C:/Users/raill/Downloads/tmp/mysql_cluster/mysql1/var/lib/mysql:/var/lib/mysql
- C:/Users/raill/Downloads/tmp/mysql_cluster/mysql1/etc/mysql/conf.d:/etc/mysql/conf.d
- ./conf/setup.sql:/docker-entrypoint-initdb.d/setup.sql
mysql2:
container_name: mysql2
image: mysql:latest
restart: always
ports:
- 33062:3306
environment:
# 초기 생성 데이터베이스
MYSQL_DATABASE: first_schema
# root 계정 초기 비밀번호
MYSQL_ROOT_PASSWORD: todo1234!
# 타임존 설정
TZ: Asia/Seoul
volumes:
- C:/Users/raill/Downloads/tmp/mysql_cluster/mysql2/var/lib/mysql:/var/lib/mysql
- C:/Users/raill/Downloads/tmp/mysql_cluster/mysql2/etc/mysql/conf.d:/etc/mysql/conf.d
- ./conf/setup.sql:/docker-entrypoint-initdb.d/setup.sql
위와 같이 설정하였습니다.
보시다시피 단순히 2개의 mysql 서버를 생성한 것 뿐입니다.
설정을 위한 setup.sql 파일은,
CREATE USER 'clusteradmin'@'%' IDENTIFIED BY 'todo1234!';
GRANT ALL privileges ON *.* TO 'clusteradmin'@'%' with grant option;
reset master;
이렇게 유저를 생성하였습니다.
위와 같은 도커 파일을
>> docker-compose -f mysql-cluster-compose.yml up -d
이렇게 실행시키면, Mysql 서버 2개에 대한 컨테이너가 생성됩니다.
컨테이너가 생성되었다면 먼저 master 서버 설정을 해야 합니다.
mysql1 의 컨테이너의 exec 에 접속해서,
>> mysqlsh -uclusteradmin -p'todo1234!'
>> \js
>> dba.checkInstanceConfiguration("clusteradmin@host.docker.internal:33061")
>> {clusteradmin 비밀번호 입력}
>> y
>> dba.configureInstance("clusteradmin@host.docker.internal:33061")
>> {all y and waiting restart - 자동 재시작이 안되면 수동 재시작}
위와 같이 설정합니다.
dba 에 본 서버의 인스턴스를 등록하는 것입니다.(configureInstance 의 인자값은, 본 서버에서 해당 mysql 서버 인스턴스로 접속하는 정보입니다. 접속자 id 와 주소를 결합하는 것으로, host.docker.internal 는 도커 컨테이너 내부에서 localhost 로 접근하는 주소라 생각하면 됩니다.)
위와 같이 하여 컨테이너가 재시작시키고,
이번엔 mysql2 의 컨테이너의 exec 에 접속해서,
>> mysqlsh -uclusteradmin -p'todo1234!'
>> \js
>> dba.checkInstanceConfiguration("clusteradmin@host.docker.internal:33062")
>> {clusteradmin 비밀번호 입력}
>> y
>> dba.configureInstance("clusteradmin@host.docker.internal:33062")
>> {all y and waiting restart - 자동 재시작이 안되면 수동 재시작}
mysql2 서버의 인스턴스를 등록합니다.
또 재시작하고,
다시 mysql1 의 컨테이너의 exec 에 접속해서,
- mysql1 exec 명령어창 mysql1 을 main 으로 dbcluster 클러스터 생성
>> mysqlsh -uclusteradmin -p'todo1234!'
>> \js
>> var cluster = dba.createCluster("dbcluster")
>> cluster.status()
- mysql1 exec 명령어창 mysql2 를 클러스터 서브(Read Only)로 등록
>> cluster.addInstance("clusteradmin@host.docker.internal:33062")
>> c
>> cluster.status()
위와 같이 클러스터를 생성한 후 slave 의 인스턴스를 클러스터 내에 등록하면 이것으로 끝입니다.
위와 같은 처리로 인하여 master 서버로 mysql1 컨테이너가 존재하고, 이곳에 등록된 slave 서버인 mysql2 컨테이너가 하나 연결된 상태라고 보시면 됩니다.
테스트를 하기 위해서는,
master 서버에 변경사항을 입력하여, 이에 대한 복제가 slave 서버에 반영되었는지를 확인하면 됩니다.
- mysql1 exec 명령어창에서 mysql 로 접속
>> mysql -u clusteradmin -p'todo1234!'
- mysql1 에서 데이터베이스 조작 후 mysql2 에 반영이 되는지 확인하기
>> CREATE DATABASE developer;
>> use developer;
>> CREATE TABLE dev(name VARCHAR(20) NOT NULL, SLNO INT NOT NULL,
>> PRIMARY KEY(SLNO));
>> INSERT INTO dev VALUE("dev1",1);
위와 같이 master 서버에 데이터를 입력하면 slave 서버에 정상적으로 반영될 것이며,
만약 slave 서버에서 insert 등의 변경 요청을 한다면 read only 에러가 발생하는 것이 정상입니다.
또한, 재부팅 후에도 클러스터가 정상적으로 동작하는지를 알아보기 위하여 컨테이너를 재시작 했다면,
- 재부팅 후 클러스터 설정 확인
>> var cluster = dba.getCluster('dbcluster');
>> cluster.status();
위와 같이 클러스터 현황을 확인할 수 있고,
만약 클러스터 설정이 끊겨있다면,
- 재부팅 후 클러스터 설정이 안 되었을 경우
>> dba.rebootClusterFromCompleteOutage();
이렇게 입력하면 됩니다.