진행하고 있던 프로젝트에서 다음과 같은 이유로 CQRS를 도입하게 되었다.
Command와 Query용 DB를 분리하여 2개를 관리함으로써, 쓰기 작업으로 인해 장애가 발생하더라도 읽기를 통해 기존 데이터에 대한 접근이 가능해짐
Command/Query DB를 독립적으로 확장이 가능하다.
여기서 언급하는 유지 보수는 실제로 DB에 대한 유지보수가 아닌, 애플리케이션의 서비스 계층에서의 유지 보수를 말한다.
읽기, 쓰기 작업은 서로 목적이 다르기 때문에 복잡도가 다르게 나타난다.
쓰기는 CUD에 대한 작업으로 주로 요청 데이터에 대한 검증일 실시하는 반면, 읽기는 캐시, 검색 엔진 사용, 복잡한 쿼리 사용 등 다양한 성능최적화를 위해 복잡도가 증가한다.
따라서 각각의 책임을 분리하여 관리하면 유지 보수가 편해질 것으로 기대한다.
현재 프로젝트의 배포 서버에서는 AWS의 RDS-MySQL을 활용하고 있기 때문에 복제본을 생성하면 쉽게 CQRS를 설정할 수 있다.
이 포스팅에서는 개발 환경에서도 CQRS를 도입 시 생길 수 있는 문제를 사전에 식별하기 위해, 로컬 환경에서도 CQRS를 도입할 수 있는 방법을 같이 소개하려 한다.
먼저 MySQL은 어떻게 Replication을 관리하는 지 알아보았다.
역시 가장 중요한 것은 Master DB와 Replica DB간의 데이터 동기화일 것이다
MySQL의 데이터 복제 방식은 크게 동기/비동기를 모두 지원하고, Semi-Sync Replication이라는 플러그인을 통해 Semi-Sync 방식도 사용할 수 있다고 한다.
MySQL의 Replication은 기본적으로 비동기 복제 방식을 사용한다
Semi-Synck란?
기존 비동기 복제는 SLAVE DB에 데이터가 성공적으로 반영됐는 지 모른다는 단점이 존재한다.
이를 보완하기 위해 마스터가 트랜잭션 커밋 시, 최소 한 슬레이브가 binlog를 수신했다는 확인(ACK)을 받아야 클라이언트에 응답하는 복제 방식이다.
Master 노드에서 변경되는 데이터에 대한 이력을 로그(Binary Log)에 기록하면, Replication Master Thread 가 (비동기적으로) 이를 읽어서 Slave 쪽으로 전송하는 방식이다.
전체 흐름을 살펴보자

배포 서버에서 사용하는 RDS-MySQL과 최대한 유사한 환경을 구성하기 위해 로컬에서도 MySQL을 활용하여 읽기용 Replica를 만들고, 데이터 동기화가 이루어지도록 만들어 보았다.
먼저 docker-compose를 활용하여 2개의 mysql을 3306, 3307 포트로 인바운드 포트를 열어주었다.
생성 시 mysql.cnf에 대한 설정도 추가했다.
docker-compose.yml
version: '3'
services:
master_database:
container_name: pitchain_master_db
image: mysql:8.0
environment:
MYSQL_DATABASE: pitchain_db
MYSQL_ROOT_HOST: '%'
MYSQL_ROOT_PASSWORD: 1234
ports:
- "3306:3306"
volumes:
- ./mysql/master_data_source.cnf:/etc/mysql/conf.d/my.cnf
networks:
- pitchain-network
replica_database:
container_name: pitchain_replica_db
image: mysql:8.0
environment:
MYSQL_DATABASE: pitchain_db
MYSQL_ROOT_HOST: '%'
MYSQL_ROOT_PASSWORD: 1234
ports:
- "3307:3306"
volumes:
- ./mysql/replica_data_source.cnf:/etc/mysql/conf.d/my.cnf
networks:
- pitchain-network
networks:
pitchain-network:
# ./mysql/master_data_source.cnf
[mysqld]
server-id=1
log-bin=mysql-bin
binlog-do-db=pitchain_db
# ./mysql/replica_data_source.cnf
[mysqld]
server-id=2
log-bin=mysql-bin
read_only=1
log-bin을 통해 binary-log를 활성화 시키고, prefix로 mysql-bin 이름을 지정해준다
binlog-do-db를 통해 binary-log에 기록을 남길 db를 명시적으로 설정해준다
이제 master db와 replica db를 연동해줘야 한다.
먼저 master db에 접속하여 replica db로 복제해주기 위한 권한이 생성된 유저를 만들고, 연동하기 위한 데이터를 조회한다.
docker exec -it pitchain_master_db bash
mysql -u root -p
# Enter the password: 1234
CREATE USER 'replica'@'%' IDENTIFIED WITH mysql_native_password BY '1234';
GRANT REPLICATION SLAVE ON *.* TO 'replica'@'%';
FLUSH PRIVILEGES;
show master status; # 8.4 이후에는 SHOW BINARY LOG STATUS를 통해 조회 가능하다

여기서 File과 Position값이 Replica DB와 연동하기 위해 필요하다.
이제 replica db로 접속하여 연동을 마무리 해줘야 한다.
docker exec -it pitchain_replica_db bash
mysql -u root -p
# Enter the password: 1234
위에서 복제 권한을 부여한 user에 대한 정보와 master의 정보(File, Position)을 통해 연동을 요청한다.
STOP SLAVE;
CHANGE MASTER TO
MASTER_HOST='pitchain_master_db',
MASTER_PORT=3306,
MASTER_USER='{user_name}',
MASTER_PASSWORD='{user_password}',
MASTER_LOG_FILE='{master_file}',
MASTER_LOG_POS={position};
START SLAVE;
잘 연동 됐는지 테스트 해보자
SHOW SLAVE STATUS \G #\G는 보기 편하게 출력해줌
이렇게 이벤트 요청을 기다리고 있다고 뜨면 성공이다

이제 master db로 데이터를 생성해주면, replica db에서도 데이터를 확인할 수 있다.
