MySQL InnoDB Cluster 구현 및 로드밸런싱

이경헌·2025년 5월 11일

1. MySQL InnoDB Cluster 개요

🧩 InnoDB Cluster란?

MySQL에서 제공하는 고가용성(HA) 솔루션으로, 다음 구성요소들을 조합하여 클러스터를 구성합니다:

  • MySQL Server + Group Replication: 데이터 자동 복제, 장애 조치
  • MySQL Shell: 클러스터 생성/관리용 CLI (AdminAPI 제공)
  • MySQL Router: 애플리케이션 ↔ 클러스터 간 라우팅/부하 분산

🔁 구조와 동작

  • 최소 3개 MySQL 인스턴스로 구성
  • 기본은 단일 Primary 모드:
    • 1개 Primary (읽기/쓰기)
    • 2개 Secondary (읽기 전용)
  • Group Replication으로 Primary → Secondary 데이터 자동 복제
  • 장애 발생 시 자동 Failover

⚙️ AdminAPI (MySQL Shell)

  • JavaScript 또는 Python 기반 API
  • 클러스터 생성/확장/모니터링을 스크립트로 자동화 가능
  • MySQL Clone 기능으로 신규 인스턴스 자동 동기화

🔀 MySQL Router 통합

  • 클러스터 상태 기반 자동 라우팅 설정 (Bootstrapping)
  • 애플리케이션은 라우터만 바라보면 됨
  • 라우터 사용자도 AdminAPI로 생성/관리 가능

2. 📦구성도

3. ⚙️구성

1. 폴더 구조

mysql/
├── docker-compose.yml
├── mysql-shell/
│   ├── Dockerfile/
│   └── run.sh
├── scripts/
│   ├── db.sql
│   └── setupCluster.js
├── server-1/
├── server-2/
├── server-3/
├── mysql-router.env
├── mysql-server.env
└── mysql.shell.env

2. docker-compose.yml 설정

다음은 MySQL InnoDB Cluster를 Docker Compose로 구성한 예시입니다. 이 구성은 3개의 MySQL 서버를 설정하고, 각 서버는 동일한 클러스터에 속하게 됩니다.

services:
  mysql-server-1:
    container_name: mysql-server-1
    image: mysql/mysql-server:8.0.12
    env_file:
      - mysql-server.env
    ports:
      - "3301:3306"
    volumes:
      - ./server-1/data:/var/lib/mysql
    networks:
      - eventor_back-network
    environment:
      TZ: Asia/Seoul

  mysql-server-2:
    container_name: mysql-server-2
    image: mysql/mysql-server:8.0.12
    env_file:
      - mysql-server.env
    ports:
      - "3302:3306"
    volumes:
      - ./server-2/data:/var/lib/mysql
    networks:
      - eventor_back-network
    environment:
      TZ: Asia/Seoul

  mysql-server-3:
    container_name: mysql-server-3
    image: mysql/mysql-server:8.0.12
    env_file:
      - mysql-server.env
    ports:
      - "3303:3306"
    volumes:
      - ./server-3/data:/var/lib/mysql
    networks:
      - eventor_back-network
    environment:
      TZ: Asia/Seoul

  mysql-shell:
    container_name: mysql-shell
    env_file:
      - mysql-shell.env
    build:
      context: .
      dockerfile: ./mysql-shell/Dockerfile
    command: /run.sh
    volumes:
        - ./scripts/:/scripts/
    depends_on:
      - mysql-server-1
      - mysql-server-2
      - mysql-server-3
    networks:
      - eventor_back-network

  mysql-router:
    container_name: mysql-router
    env_file:
      - mysql-router.env
    image: mysql/mysql-router:8.0
    ports:
      - "6446:6446"
      - "6447:6447"
    depends_on:
      - mysql-server-1
      - mysql-server-2
      - mysql-server-3
      - mysql-shell
    restart: on-failure
    networks:
      - eventor_back-network

networks:
  eventor_back-network:
    external: true

3. my.cnf 설정

[mysqld]
server_id=1
report_host=mysql-server-1
relay_log=relay-bin
relay_log_index=relay-bin.index
binlog_checksum=NONE
gtid_mode=ON
enforce_gtid_consistency=ON
log_bin=ON
log_slave_updates=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
transaction_write_set_extraction=XXHASH64
user=mysql
skip-host-cache
default_authentication_plugin=mysql_native_password
binlog_transaction_dependency_tracking=WRITESET
default-time-zone = '+09:00'

주요 설정 항목 설명

  • server_id: 각 인스턴스의 고유 ID. 반드시 서로 달라야 합니다. 예: mysql-server-2server_id=2.
  • report_host: 인스턴스 식별용 호스트명. docker-compose 내 컨테이너 이름과 맞추는 것이 좋습니다.
  • gtid_mode, enforce_gtid_consistency: GTID 기반 복제를 위한 필수 옵션.
  • log_bin, log_slave_updates: binary log 활성화 및 복제 로그 유지.
  • transaction_write_set_extraction: Group Replication을 위한 트랜잭션 추적 방식.
  • binlog_transaction_dependency_tracking=WRITESET: 충돌 최소화를 위한 의존성 추적 방식.
  • default_authentication_plugin: 기본 인증 플러그인을 mysql_native_password로 고정 (MySQL 8.x 이상 호환 이슈 방지용).

4. 컨테이너 실행

docker-compose up -d

5. 컨테이너 접속

docker exec -it mysql-server-1 bash

6. mysql-server-1 접속

mysqlsh root@mysql-server-1:3306
  • mysqlsh는 MySQL 8에서 클러스터 및 고가용성 관련 기능을 지원하는 셸입니다.
  • 위 명령어로 mysql-server-1에 접속한 후, JavaScript 모드에서 클러스터 명령을 실행합니다.

7. 클러스터 메타데이터 업그레이드

dba.upgradeMetadata()
  • 기존 클러스터 메타데이터를 최신 형식으로 업그레이드합니다.
  • InnoDB Cluster의 내부 구조가 변경되거나 기능이 개선될 때 사용됩니다.

8. 클러스터 재스캔 (Rescan)

dba.getCluster().rescan()
  • 클러스터 노드 구성을 다시 확인하고, 현재 참여 중인 인스턴스를 갱신합니다.
  • 새로운 노드를 반영하거나 변경된 설정을 적용하기 위해 사용합니다.

Warning 메시지

WARNING: The Cluster is not configured to use 'group_replication_view_change_uuid', which is required for InnoDB ClusterSet. Configuring it requires a full Cluster reboot.
Would you like 'group_replication_view_change_uuid' to be configured automatically? [Y/n]: y
  • group_replication_view_change_uuidClusterSet 구성 시 필수 옵션입니다.
  • y를 입력하여 자동 설정을 진행합니다.

이후 출력:

NOTE: The Cluster's group_replication_view_change_uuid is not set
Generating and setting a value for group_replication_view_change_uuid...
WARNING: The Cluster must be completely taken OFFLINE and restarted (dba.rebootClusterFromCompleteOutage()) for the settings to be effective
Updating group_replication_view_change_uuid in the Cluster's metadata...
Updating group_replication_transaction_size_limit in the Cluster's metadata...
  • 변경 사항을 정식 반영하려면 클러스터를 완전히 내리고 재시작해야 합니다.

4. MySQL Router 적용 후 JDBC URL 수정

InnoDB Cluster를 사용할 경우, 클러스터 내 장애 조치(Failover)나 자동 복구 기능을 활용하기 위해 MySQL Router를 경유하여 연결해야 합니다.

JDBC URL도 그에 맞게 바꿔야 합니다. 기존에는:


기존 JDBC 연결 방식

InnoDB Cluster를 구성하기 전, 일반적인 JDBC URL은 다음과 같이 설정되어 있었습니다:

spring.datasource.url=jdbc:mysql://mysql:3306/eventor

여기서 mysql은 Docker Compose 등에서 정의한 MySQL 컨테이너 이름이고, 포트는 기본 포트 3306을 사용합니다.


Router 적용 후 JDBC 연결 방식

MySQL Router를 사용하면 다음과 같이 URL이 변경됩니다:

spring.datasource.url=jdbc:mysql://mysql-router:6446/eventor
  • mysql-router: Docker Compose에서 정의한 MySQL Router 컨테이너 이름
  • 6446: MySQL Router의 Read-Write 포트 (기본값)
  • eventor: 접속할 데이터베이스 이름

MySQL Router는 내부적으로 클러스터 내에서 쓰기 가능한 Primary 노드로 트래픽을 라우팅해줍니다.

DataSourceConfig.java

@Configuration
public class DataSourceConfig {

	@Value("${spring.datasource.url}")
	private String url;

	@Value("${spring.datasource.dbcp2.username}")
	private String username;

	@Value("${spring.datasource.dbcp2.password}")
	private String password;

	/**
	 * PoolSize = Tn × ( Cm - 1 ) + ( Tn / 2 )
	 * thread count : 12
	 * simultaneous connection count : 2
	 * pool size : 12 * ( 2 – 1 ) + (12 / 2) = 18
	 */
	@Bean
	public BasicDataSource dataSource() {
		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setUrl(url);
		dataSource.setUsername(username);
		dataSource.setPassword(password);
		dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");

		// 최적화 파라미터 설정
		dataSource.setInitialSize(18);
		dataSource.setMaxTotal(18);
		dataSource.setMaxIdle(18);
		dataSource.setMinIdle(18);

		// 추가 최적화 설정
		dataSource.setTestOnBorrow(true);
		dataSource.setValidationQuery("SELECT 1");

		return dataSource;
	}
	// 이중화
}

5. 🔁MySQL InnoDB Cluster 재부팅 후 복구 방법

MySQL InnoDB Cluster에서 서버가 재부팅된 후, 일부 서버가 RECOVERING 상태로 전환될 수 있습니다. RECOVERING 상태에서 ONLINE 상태로 전환하는 방법에 대해 여러 번 시도했지만, 결국 해결하지 못했습니다.

1. 클러스터 구성 컨테이너 재시작

먼저 docker-compose로 모든 컨테이너를 다시 올려줍니다.

docker-compose up -d

2. GTID Executed 값이 가장 높은 서버 확인

복구의 핵심은 가장 최신 트랜잭션 데이터를 가진 노드를 기준 서버(source) 로 지정하는 것입니다. 각 서버에 접속해 아래 명령어로 GTID 값을 비교하세요:

SHOW GLOBAL VARIABLES LIKE 'gtid_executed';

예시:

docker exec -it mysql-server-1 bash
mysql -u root -p
SHOW GLOBAL VARIABLES LIKE 'gtid_executed';
  • 이 값이 가장 긴 서버가 최신 상태를 유지하고 있으므로, 복구 시 이 서버를 기준으로 클러스터를 재구성해야 데이터 유실을 최소화할 수 있습니다.

3. 클러스터 복구 명령 실행

선택된 기준 서버에 접속 후, MySQL Shell을 사용하여 클러스터를 재시작합니다.

docker exec -it mysql-server-1 bash
mysqlsh root@mysql-server-1:3306

MySQL Shell 내에서 다음 명령어를 실행합니다:

dba.rebootClusterFromCompleteOutage('devCluster')

완료 확인:

dba.getCluster().status()
  • 모든 노드의 status"ONLINE"으로 나올 경우 복구가 성공적으로 완료된 것입니다.

특정 DB가 클러스터에서 n/a 상태일 경우 해결 방법

  • mysql-server-2:3306 인스턴스가 Group Replication에 참여하려다 실패함

  • group_replication_applier 채널의 relay log 파일이 손상됨

  • 오류 메시지:

    Error reading relay log event for channel 'group_replication_applier': corrupted data in log event
    ...
    Error running query, slave SQL thread aborted.

원인 분석

  1. relay log 손상:

    • 전원이 꺼지거나 디스크 문제, 네트워크 장애 등으로 인해 relay log 파일이 손상되었을 수 있음
  2. 복구 시 잘못된 바이너리 로그 정보:

    • 다른 멤버로부터 수신한 binlog 이벤트를 재적용하던 중 파싱 실패
  3. 이전 상태에서 제대로 정리되지 않은 Group Replication 구성:

    • group_replication_applier 채널이 중간에 비정상 종료된 이력이 있을 수 있음

해결 방법

다음은 mysql-server-2:3306을 클러스터에 다시 참여시키기 위한 절차입니다:

1. relay log 삭제 및 재시작

STOP SLAVE FOR CHANNEL 'group_replication_applier';
RESET SLAVE ALL FOR CHANNEL 'group_replication_applier';

참고: RESET SLAVE ALL모든 복제 정보와 relay log를 초기화합니다. 재참여 시 새로 동기화를 수행합니다.

2. group_replication 수동 재시작

START GROUP_REPLICATION;

MySQL Shell 종료 방법

MySQL Shell(JS 모드)에서 나가려면 다음 명령어를 입력하세요:

\q

또는

\exit

4. 라우터 재시작

클러스터 복구 후에는 MySQL Router도 재시작하여 새로운 클러스터 상태를 반영하도록 합니다.

docker-compose restart mysql-router

참고

0개의 댓글