master-slave 구조 이해하기

반응형

DB에서 master-slave 형태로 변경하다는 뜻은 읽기 전용과 쓰기 전용을 분리한다는 의미입니다.
최근에는 master-slave라는 용어보다는 primary-replica라는 용어로 더 많이 사용이 됩니다.
이것을 왜 사용하는지부터 이해를 해봅시다.
primary-replica 구조는 읽기 트래픽이 쓰기 안정성을 침범하지 않도록
역할을 분리하기 위한 설계 선택이며, 그 과정에서 읽기 확장성과 운영 유연성을 함께 얻는 구조입니다.

일단 오해부터 잡고 갑시다.

처음 개발을 시작했을 때, 읽기 전용 DB와 쓰기 전용 DB라는 개념 자체는 알고 있었습니다.
하지만 그 구조를 이해하는 과정에서 한 가지 중요한 오해를 하고 있었습니다.

저는 쓰기 전용 DB가 읽기 전용 DB로 데이터를 직접 전달해줘야 한다고 생각했습니다.
즉, 애플리케이션이 중간에서 "이 데이터는 읽기 DB에도 넣어줘야 하나?"를 판단하고 처리해야 한다고 믿고 있었던 것입니다.
이런 생각을 하게 된 이유는 단순합니다. 데이터는 결국 DB가 가지고 있으니, DB가 DB에게 데이터를 넘겨줘야 한다고 생각했기 때문입니다. 하지만 이는 잘못된 이해였습니다.

그렇다면, 어떻게 데이터를 넘겨줄까요?

사실 Primary는 Replica에게 데이터를 직접 전달하지 않습니다.
데이터 자체를 전송하는 것이 아니라, 데이터가 어떻게 변경되었는지에 대한 변경 이력을 전달합니다.
이 변경 이력을 기록하는 것이 바로 binlog(Binary Log)입니다.

replica를 생성하기 전에 binlog가 켜져 있는지 확인해 봅시다. 이게 켜져 있지 않는다면, replica를 생성할 수 없다고 합니다.

SHOW VARIABLES LIKE 'log_bin';

 

다행히 켜져 있군요.

여기서 2가지 짚고 넘어가야 문제가 있습니다.
1. binlog는 어떻게 전달하는가?
2. primary는 어떤 기준으로 설정하는가?

정확히 말하면, Primary가 Replica에게 binlog를 능동적으로 전달(push)하는 구조는 아닙니다.
Replication은 Primary가 생성한 binlog를 Replica가 네트워크를 통해 지속적으로 요청하고 읽어오는 pull 기반 구조입니다.

네트워크를 통해 지속적으로 요청하고 읽어오는 구조라면? 인스턴스끼리 통신이 되는 형태여야 한다고 생각합니다.
왜냐하면 같은 인스턴스 내에서 통신을 한다면 네트워크 통신을 할 필요가 없기 때문이죠.

대충 그려보면 다음과 같이 그릴 수 있을 거 같습니다.

도커 컴포즈

현재 도커 컴포즈는 다음과 같이 하나의 MySQL 인스턴스를 실행하고 있습니다.

services:
  mysql:
    image: mysql:8.0
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: _
      MYSQL_DATABASE: _
      MYSQL_USER: _
      MYSQL_PASSWORD: _
    ports:
      - "3306:3306"

Replication을 구성하려면 인스턴스가 최소 두 개 필요하므로 다음과 같이 작성할 수 있습니다.

services:
  mysql-primary:
    image: mysql:8.0
    container_name: mysql-primary
    environment:
      MYSQL_ROOT_PASSWORD: root
    ports:
      - "3306:3306"
    command:
      - --server-id=1
      - --log-bin=mysql-bin
      - --binlog-format=ROW
      - --gtid-mode=ON
      - --enforce-gtid-consistency=ON

  mysql-replica:
    image: mysql:8.0
    container_name: mysql-replica
    environment:
      MYSQL_ROOT_PASSWORD: root
    ports:
      - "3307:3306"
    command:
      - --server-id=2
      - --gtid-mode=ON
      - --enforce-gtid-consistency=ON
      - --read-only=ON

여기까지는 단순히 MySQL 인스턴스 두 개를 실행한 것뿐입니다.
이 상태에서는 둘 다 독립 서버이고 Primary도 아니고 Replica도 아닙니다.
단지 server-id만 다를 뿐입니다.

MySQL은 스스로 나는 Primary다라고 선언하지 않습니다.
Replication은 관계 설정을 통해 만들어집니다. 즉, Replica 역할을 수행할 서버에서 다음 명령을 실행해야 합니다.

CHANGE REPLICATION SOURCE TO
  SOURCE_HOST='mysql-primary',
  SOURCE_USER='repl',
  SOURCE_PASSWORD='repl',
  SOURCE_AUTO_POSITION=1;

이 명령을 실행하는 순간 mysql-primary → Primary 역할을 하고 mysql-replica → Replica 역할을 하게 되어집니다.
이것들을 command에 넣지 못하는 이유는 mysql에서 command에 넣는 정보는 mysqld명령어로 실행하게 되어집니다.
하지만 Replication source로 변경하는 명령어는 MySQL이 실행된 이후, 내부 메타데이터를 수정하는 SQL 명령입니다.
즉, 서버 시작 옵션이 아니라 런타임 설정이기 때문에 command에 넣을 수 없습니다.

 volumes:
      - ./replica-init.sql:/docker-entrypoint-initdb.d/init.sql

만약에, 도커 컴포즈에서 전부 하고 싶다면? volume으로 빼서 도커 컴포즈에서 그 파일을 실행을 시켜주는 방법도 있습니다.
그렇다면 이것은 무엇을 말하는 걸까요?

[mysqld]
server-id=1
log-bin=mysql-bin
binlog-format=ROW
gtid-mode=ON
enforce-gtid-consistency=ON

server-id: 각 MySQL 인스턴스를 식별하는 고유 ID
log-bin: MySQL에서 Binary Log를 활성화하는 옵션
binlog-format: binlog에 무엇을 기록할지 결정하는 옵션

의미 특징
STATEMENT 실행한 SQL 문장 기록 가볍지만 위험 요소 있음
ROW 변경된 row 데이터 기록 가장 정확, 실무 표준
MIXED 상황에 따라 자동 선택 과거 타협안

gtid-mode: 각 트랜잭션에 고유 ID를 부여하여 파일과 위치 대신 트랜잭션 기준으로 복제를 수행하도록 만드는 설정
enforce-gtid-consistency: GTID에 안전하지 않은 SQL을 아예 실행 못 하게 막는 역할

연결 테스트

설정을 대략적으로 마무리하고 연결이 잘 되었는지 테스트를 해보았습니다.
다음과 같은 명령어가 있다고 합니다. 
서비스 ID는 잘 등록이 되어졌는데 이상하게도 primary와 replica가 정상적으로 되지 않았습니다.

SHOW REPLICA STATUS;


그래서 확인해보니 에러가 발생하였습니다.

Error connecting to source 'repl@mysql-primary:3306'. This was attempt 3/86400, with a delay of 60 seconds between attempts. Message: Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.

알고보니 제가 init sql문을 잘못 작성해뒀습니다.
그래서 다음과 같이 변경했습니다.

CREATE USER 'repl'@'%'
IDENTIFIED WITH mysql_native_password
BY 'repl';

GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

FLUSH PRIVILEGES;

그 이유는 init이든 수동 실행이든 MySQL 8에서 인증 플러그인을 명시하지 않으면 caching_sha2_password가 기본 적용된다고 합니다.

다시 내리고 실행해보겠습니다.

다행히 정상적으로 실행이 되었습니다.!!!!!

 

결론

Primary-Replica 구조에 대해 정리해보았습니다. 처음에는 Primary가 Replica로 데이터를 직접 밀어 넣는 구조라고 생각했습니다. 그렇다면 Primary에 상당한 부하가 발생하고, Replica 수가 늘어날수록 한계가 생길 것이라고 추측했습니다. 하지만 실제 구조는 달랐습니다. Primary가 데이터를 직접 전송하는 것이 아니라, 변경 이력을 담은 binlog를 생성하고, Replica가 이를 네트워크를 통해 읽어와 동일한 트랜잭션을 재실행하는 방식이었습니다. 즉, 데이터를 "복사"하는 구조가 아니라, 변경 사항을 "재현"하는 구조라는 점에서 Replication의 본질을 이해할 수 있었습니다.

반응형

댓글

Designed by JB FACTORY