본문 바로가기
삵
MariaDB

MaxScale로 읽기 쓰기 자동 분리하기

열열린기술자·2026년 10월 16일·조회 0

복제는 걸어뒀는데 애플리케이션이 읽기와 쓰기를 전부 마스터로 보내는 경우를 현장에서 자주 본다. 슬레이브는 놀고 마스터만 부하를 받는다. 커넥션 풀을 둘로 나눠 코드에서 분기하는 방법도 있지만, 장애가 나면 또 코드를 고쳐야 한다. 프록시 계층에서 분리하면 애플리케이션은 단일 엔드포인트만 알면 된다. 이 글은 MariaDB MaxScale로 그 분리를 구성하는 순서다.

결론부터 말하자면, MaxScale의 readwritesplit 라우터를 마스터-슬레이브 앞에 두면 쓰기 쿼리는 마스터로, 읽기 쿼리는 슬레이브들로 자동으로 나뉜다. 여기에 mariadbmon 모니터를 붙이면 마스터 장애를 감지해 슬레이브를 자동 승격하고, 라우팅 대상도 새 마스터로 바뀐다. 애플리케이션은 MaxScale 포트 하나만 바라보면 된다.

1. readwritesplit가 하는 일

MaxScale은 MariaDB가 만든 데이터베이스 프록시다. 클라이언트와 DB 서버 사이에 앉아 쿼리를 보고 어느 백엔드로 보낼지 결정한다. 그중 readwritesplit은 라우터 모듈 하나로, 데이터를 바꾸지 않는 읽기 쿼리는 여러 노드로 분산하고 쓰기 쿼리는 단일 노드(마스터)로 보낸다.

판단 기준은 단순하다. SELECT처럼 데이터를 바꾸지 않는 문장은 슬레이브로 갈 수 있고, INSERT/UPDATE/DELETE/DDL과 트랜잭션 안의 문장은 마스터로 간다. 명시적 트랜잭션(BEGIN 이후)은 커밋 전까지 읽기라도 마스터로 보낸다. 복제 지연으로 방금 쓴 데이터를 못 읽는 상황을 막기 위해서다.

슬레이브가 여럿이면 어느 슬레이브로 보낼지는 slave_selection_criteria가 정한다. 기본값은 least_current_operations로 진행 중인 작업이 가장 적은 슬레이브를 고른다. 응답 시간 기반으로 보내고 싶으면 adaptive_routing을 쓴다.

2. 환경

이 글의 토폴로지는 노드 3대와 프록시 1대다.

  • server1 (10.0.0.11) - 마스터
  • server2 (10.0.0.12) - 슬레이브
  • server3 (10.0.0.13) - 슬레이브
  • MaxScale 호스트 (10.0.0.10) - 프록시

DB는 MariaDB 11.4, 프록시는 MaxScale 24.02 기준이다. 세 노드는 GTID 기반 복제가 이미 걸려 있다고 가정한다. 마스터-슬레이브 복제 구성 자체는 이 글의 범위가 아니다. 버전 번호는 저장소 상태에 따라 다를 수 있다.

MaxScale 저장소를 등록하고 설치한다.

$ curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash
$ sudo yum install -y maxscale
$ maxscale --version
MaxScale 24.02.3

3. 모니터 계정과 복제 권한

MaxScale은 두 종류의 계정을 쓴다. 하나는 모니터(mariadbmon)가 복제 상태를 읽을 계정, 다른 하나는 서비스가 사용자 인증 정보를 조회할 계정이다. 실무에선 역할을 나눠 두 계정으로 만든다.

마스터에서 아래를 실행한다. 복제로 슬레이브에 전파된다.

-- 모니터 계정: 복제 상태 감시
CREATE USER 'maxmon'@'%' IDENTIFIED BY 'monpass';
GRANT REPLICATION CLIENT, REPLICA MONITOR ON *.* TO 'maxmon'@'%';

-- 서비스 계정: 사용자/권한 조회
CREATE USER 'maxuser'@'%' IDENTIFIED BY 'svcpass';
GRANT SELECT ON mysql.user TO 'maxuser'@'%';
GRANT SELECT ON mysql.db TO 'maxuser'@'%';
GRANT SELECT ON mysql.tables_priv TO 'maxuser'@'%';
GRANT SELECT ON mysql.roles_mapping TO 'maxuser'@'%';

자동 페일오버까지 쓸 거라면 모니터 계정에 클러스터 조작 권한이 더 필요하다. MariaDB 11.0 이후로는 과거 SUPER 하나로 묶여 있던 권한 일부가 READ_ONLY ADMIN, REPLICATION MASTER ADMIN, REPLICATION SLAVE ADMIN 등으로 쪼개졌다. 권한명은 공백이 아니라 밑줄을 쓰는 READ_ONLY ADMIN 형태다.

GRANT RELOAD, PROCESS, SHOW DATABASES, EVENT ON *.* TO 'maxmon'@'%';
GRANT READ_ONLY ADMIN, REPLICATION MASTER ADMIN, REPLICATION SLAVE ADMIN, BINLOG ADMIN ON *.* TO 'maxmon'@'%';

4. maxscale.cnf 구성

설정 파일은 /etc/maxscale.cnf다. 서버, 모니터, 서비스, 리스너 네 블록으로 나눠 쓴다. 서버 블록은 백엔드 DB를 정의한다.

[server1]
type=server
address=10.0.0.11
port=3306

[server2]
type=server
address=10.0.0.12
port=3306

[server3]
type=server
address=10.0.0.13
port=3306

모니터 블록은 mariadbmon으로 복제 상태를 감시한다. auto_failover와 auto_rejoin을 켜면 마스터 장애 시 슬레이브를 승격하고, 복구된 옛 마스터를 새 마스터의 슬레이브로 다시 붙인다.

[MariaDB-Monitor]
type=monitor
module=mariadbmon
servers=server1,server2,server3
user=maxmon
password=monpass
monitor_interval=2s
auto_failover=true
auto_rejoin=true
failcount=5
enforce_read_only_slaves=true

failcount는 마스터가 몇 번 연속으로 응답하지 않으면 죽은 것으로 보고 페일오버를 시작할지다. monitor_interval이 2초, failcount가 5면 약 10초 무응답에서 승격이 시작된다. enforce_read_only_slaves=true는 슬레이브에 read_only를 강제해 실수로 슬레이브에 쓰기가 들어가는 일을 막는다.

서비스 블록에 라우터를 지정한다.

[Read-Write-Service]
type=service
router=readwritesplit
servers=server1,server2,server3
user=maxuser
password=svcpass
master_reconnection=true
master_failure_mode=fail_on_write
transaction_replay=true
slave_selection_criteria=adaptive_routing

마지막으로 리스너가 클라이언트 접속 포트를 연다. 애플리케이션은 이 포트로만 붙는다.

[Read-Write-Listener]
type=listener
service=Read-Write-Service
protocol=MariaDBProtocol
port=3306

설정을 올리고 기동한다.

$ sudo systemctl enable --now maxscale
$ sudo systemctl status maxscale --no-pager
● maxscale.service - MariaDB MaxScale Database Proxy
     Active: active (running)

5. 상태와 라우팅 검증

먼저 백엔드가 제대로 잡혔는지 maxctrl로 본다. 여기서 한 번 걸린다. Master, Slave 라벨이 보이면 정상이다.

$ maxctrl list servers
┌─────────┬────────────┬──────┬─────────────┬─────────────────┬──────────┐
│ Server  │ Address    │ Port │ Connections │ State           │ GTID     │
├─────────┼────────────┼──────┼─────────────┼─────────────────┼──────────┤
│ server1 │ 10.0.0.11  │ 3306 │ 0           │ Master, Running │ 0-1-1000 │
├─────────┼────────────┼──────┼─────────────┼─────────────────┼──────────┤
│ server2 │ 10.0.0.12  │ 3306 │ 0           │ Slave, Running  │ 0-1-1000 │
├─────────┼────────────┼──────┼─────────────┼─────────────────┼──────────┤
│ server3 │ 10.0.0.13  │ 3306 │ 0           │ Slave, Running  │ 0-1-1000 │
└─────────┴────────────┴──────┴─────────────┴─────────────────┴──────────┘

State에 Running만 뜨고 Master/Slave 라벨이 안 붙을 때가 있다. 복제가 감지되지 않은 경우로, SHOW SLAVE STATUS가 정상이 아니거나 GTID가 어긋난 상태가 자주 보이는 원인 중 하나다. 모니터 계정 권한 부족도 원인일 수 있으니 REPLICATION CLIENT, REPLICA MONITOR가 제대로 부여됐는지 같이 확인한다.

이제 읽기와 쓰기가 실제로 갈라지는지 본다. MaxScale 리스너 포트(여기선 프록시 호스트의 3306)로 붙어 @@hostname을 조회한다. 단순 SELECT는 슬레이브로 간다.

$ mysql -h 10.0.0.10 -P 3306 -u app -p
MariaDB> SELECT @@hostname;
+------------+
| @@hostname |
+------------+
| db-slave02 |
+------------+

트랜잭션으로 묶으면 같은 SELECT라도 마스터로 간다.

MariaDB> BEGIN;
MariaDB> SELECT @@hostname;
+------------+
| @@hostname |
+------------+
| db-master  |
+------------+
MariaDB> COMMIT;

서비스 단에서 요청이 어디로 갔는지 통계로도 확인된다.

$ maxctrl show service Read-Write-Service | grep -iE 'routed|read|write'

6. 페일오버 감지와 자동 승격

마스터를 일부러 멈춰 페일오버를 본다. 운영 환경에서는 유지보수 창에서만 한다.

$ sudo systemctl stop mariadb   # server1(마스터)에서

failcount만큼 무응답이 쌓이면 모니터가 마스터를 죽은 것으로 판정하고, 가장 최신 GTID를 가진 슬레이브를 새 마스터로 승격한다. 다시 서버 목록을 보면 라벨이 바뀌어 있다.

$ maxctrl list servers
┌─────────┬────────────┬──────┬─────────────┬─────────────────┬──────────┐
│ server1 │ 10.0.0.11  │ 3306 │ 0           │ Down            │          │
│ server2 │ 10.0.0.12  │ 3306 │ 1           │ Master, Running │ 0-2-1100 │
│ server3 │ 10.0.0.13  │ 3306 │ 0           │ Slave, Running  │ 0-2-1100 │
└─────────┴────────────┴──────┴─────────────┴─────────────────┴──────────┘

승격 과정은 모니터 로그에 남는다.

$ sudo journalctl -u maxscale -n 30 --no-pager | grep -i failover
... [mariadbmon] Performing automatic failover to promote server 'server2'.
... [mariadbmon] Failover 'server1' -> 'server2' performed.

옛 마스터(server1)를 다시 올리면 auto_rejoin=true 덕분에 새 마스터의 슬레이브로 자동 편입된다. GTID가 갈라져 재편입이 안 되면 maxctrl call command mariadbmon rejoin MariaDB-Monitor server1로 수동 지시한다.

7. 페일오버 중 커넥션 처리

마스터가 바뀌는 순간 기존 클라이언트 커넥션을 어떻게 할지는 master_failure_mode가 정한다. 값은 세 가지다.

fail_instantly

마스터가 끊기는 즉시 클라이언트 커넥션을 닫는다. 가장 보수적이다.

fail_on_write

마스터가 없어도 커넥션을 유지하다가, 쓰기 쿼리가 들어올 때 보낼 마스터가 없으면 그때 커넥션을 닫는다. 읽기만 하는 세션은 승격이 끝날 때까지 살아남는다.

error_on_write

커넥션은 유지하고, 쓰기 시점에 커넥션을 닫는 대신 에러만 돌려준다.

MaxScale 24.02에서는 master_failure_mode의 기본값이 fail_on_write, master_reconnection의 기본값이 true다. 공식 문서가 권장하는 조합도 이 둘이라, 24.02에서는 사실 명시하지 않아도 이 값으로 동작한다. 위 설정 파일에 두 줄을 적어 둔 건 의도를 드러내려는 것이고, 23.08 이하에서 올라온 환경이라면(그쪽 기본값은 fail_instantly와 false) 명시해 두는 편이 혼선을 줄인다.

master_reconnection=true는 세션 도중에 마스터가 바뀌어도 커넥션을 새 마스터로 다시 연결한다. 여기에 transaction_replay=true를 더하면, 승격 직전 진행 중이던 트랜잭션을 새 마스터에서 다시 실행해 결과 체크섬이 맞으면 클라이언트에게 장애를 감춘다. 다만 재실행이 항상 안전한 것은 아니므로, 비결정적 쿼리가 많은 워크로드에서는 동작을 검증한 뒤 켠다.

8. 결론

읽기 쓰기 분리는 애플리케이션 코드가 아니라 프록시 계층에서 푸는 것이 운영상 깔끔하다. MaxScale의 readwritesplit이 쿼리 종류로 대상을 가르고, mariadbmon이 복제 상태를 감시해 마스터 장애를 자동 승격한다. 구성 후에는 반드시 maxctrl list servers로 라벨을 확인하고, 트랜잭션 안팎의 @@hostname으로 라우팅이 갈라지는지, 마스터를 멈춰 페일오버가 도는지까지 검증한다. 24.02라면 페일오버 커넥션 처리의 기본값이 이미 권장 조합이라는 점을 기억해 두면 설정이 한결 짧아진다.

자주 묻는 질문

readwritesplit는 어떤 쿼리를 슬레이브로 보내나?

데이터를 바꾸지 않는 SELECT 같은 읽기 쿼리를 슬레이브로 분산한다. INSERT/UPDATE/DELETE와 DDL, 그리고 명시적 트랜잭션(BEGIN 이후 COMMIT 전) 안의 문장은 읽기라도 마스터로 보낸다. 복제 지연으로 방금 쓴 값을 못 읽는 상황을 피하기 위해서다.

MaxScale 24.02에서 master_failure_mode 기본값은 무엇인가?

fail_on_write다. master_reconnection 기본값은 true다. 둘 다 공식 문서가 권장하는 조합이라 24.02에서는 명시하지 않아도 이 값으로 동작한다. fail_instantly와 false가 기본이던 것은 23.08 이하 버전이다.

maxctrl list servers에서 Master/Slave 라벨이 안 보인다.

State에 Running만 뜨고 라벨이 없으면 모니터가 복제를 감지하지 못한 경우다. SHOW SLAVE STATUS가 비정상이거나 GTID가 어긋난 상태가 원인인 경우가 자주 보인다. 모니터 계정에 REPLICATION CLIENT, REPLICA MONITOR 권한이 제대로 부여됐는지도 확인한다.

자동 페일오버를 쓰려면 모니터 계정에 어떤 권한이 더 필요한가?

복제 감시용 REPLICATION CLIENT, REPLICA MONITOR 외에 클러스터 조작 권한이 필요하다. RELOAD, PROCESS, SHOW DATABASES, EVENT에 더해 MariaDB 11.0 이후에는 SUPER에서 분리된 READ_ONLY ADMIN, REPLICATION MASTER ADMIN, REPLICATION SLAVE ADMIN, BINLOG ADMIN을 부여한다. 권한명은 밑줄을 쓰는 READ_ONLY ADMIN 형태다.

transaction_replay는 켜도 안전한가?

페일오버 중 진행 중이던 트랜잭션을 새 마스터에서 다시 실행하고 결과 체크섬이 맞으면 클라이언트에게 장애를 감춘다. 다만 비결정적 쿼리가 많으면 재실행 결과가 원본과 달라질 수 있으니, 해당 워크로드에서 동작을 검증한 뒤 켜는 것이 맞다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.