Docker로 실습한 MySQL 복제와 읽기·쓰기 분리
조회와 쓰기가 한 DB에 몰리자 PopcornTalk 프로젝트에서는 MySQL 복제를 실험했다. AWS RDS 복제 구성으로 부하 테스트를 하면 비용이 발생할 수 있어, 우선 Docker Compose로 MySQL 컨테이너 두 개를 띄웠다. 한쪽은 쓰기를 받고 다른 쪽은 읽기 요청을 처리하도록 연결을 나누는 실습이었다.
실습의 출발점은 간단했다. 원본 DB가 변경을 binary log에 기록하고 복제본이 이를 가져와 자기 데이터에 적용하면, 읽기 트래픽 일부를 복제본으로 보낼 수 있다.
쓰기 → Source DB ── 변경 로그 ──→ Replica DB
읽기 ←───────────────────────────┘
이 구조는 조회 부하를 여러 서버로 나눌 수 있다. Docker 컨테이너 두 개를 띄우는 것만으로 복제가 시작되는 것은 아니다. 원본의 binary log와 서버 식별 설정, 복제 계정과 접속 정보, 복제본의 source 연결이 맞아야 변경이 전달된다. 하지만 복제본은 원본과 항상 같은 순간의 데이터를 보여 주지는 않는다. MySQL의 기본 복제는 비동기이므로 원본의 커밋이 성공한 뒤 복제본이 변경을 적용하기까지 시차가 생길 수 있다. 복제 연결이 느리거나 중단되면 지연은 더 커진다.
읽기 복제본으로 보낼 수 있는 조회
메인 페이지의 목록이나 통계처럼 몇 초 전 상태를 보여도 되는 조회는 복제본 후보가 될 수 있다. 반면 사용자가 방금 프로필을 수정한 뒤 새로고침하거나 주문을 생성한 직후 주문 내역을 확인하는 요청은 최신 데이터를 기대한다. 이런 요청까지 복제본으로 보내면 저장은 성공했는데 화면에는 이전 상태가 보이는 경험이 생긴다.
이 문제를 read-your-writes로 다룰 수 있다. 쓰기 직후 이어지는 조회는 원본으로 보내거나, 복제본이 해당 변경을 적용했다는 점을 확인한 뒤 읽는다. 어느 방식을 택할지는 정확성 요구와 시스템 복잡도에 따라 달라진다. 중요한 건 모든 SELECT를 복제본으로 보내는 규칙을 기계적으로 적용하지 않는 것이다.
애플리케이션에서는 일반적으로 데이터소스 라우팅 계층을 두고 트랜잭션 성격에 따라 연결을 선택한다. 다만 프레임워크의 readOnly 설정이 곧바로 올바른 복제본 라우팅과 최신성 보장을 제공하는 것은 아니다. 실제 커넥션이 어디로 연결되는지, 하나의 트랜잭션 안에서 조회와 쓰기가 섞일 때 어느 DB를 쓰는지 통합 테스트로 확인해야 한다.
로컬 Docker 실습의 범위와 한계
컨테이너 두 개를 실행하면 source와 replica 사이의 복제 설정, 계정 권한, 네트워크 연결, 애플리케이션 라우팅을 낮은 비용으로 연습할 수 있다. 의도적으로 복제를 멈추고 다시 연결해 보면서 lag가 어떻게 보이는지도 확인할 수 있다. 원문에서 계정과 비밀번호를 간단하게 두었던 예제 값은 로컬 학습용일 뿐이므로 운영 설정으로 가져오면 안 된다. 운영 자격 증명은 환경 변수나 비밀 저장소로 분리하고, 복제 전용 계정에 필요한 권한만 부여해야 한다.
반면 로컬 컨테이너 둘이 고가용성을 증명하지는 않는다. 복제본은 원본에서 발생한 삭제나 잘못된 업데이트도 따라갈 수 있어 독립 백업이 아니다. 원본 장애가 나면 누가 복제본을 승격할지, 마지막 변경 일부가 아직 적용되지 않았을 때 데이터 손실을 어떻게 판단할지, 애플리케이션의 쓰기 주소를 어떻게 바꿀지도 별도 문제다. 단순 복제 설정만으로 자동 장애 조치가 완성되지는 않는다.
실습 결과를 운영 판단으로 연결하기
읽기·쓰기 분리를 도입하기 전에는 먼저 느린 조회를 찾아야 한다. 복제본을 추가해도 비효율적인 SQL이나 부족한 인덱스가 자동으로 고쳐지지는 않는다. 원본에서 실행되는 쿼리와 복제본으로 보낼 후보를 나누고, 각 쿼리가 얼마나 오래된 값을 허용하는지 정리한다. 부하 테스트에서는 처리량뿐 아니라 복제 지연, 원본의 쓰기량, 복제본의 CPU·디스크 사용량도 함께 본다.
이번 실습의 목적은 작은 비용으로 복제 구조를 눈으로 확인하는 것이었다. Docker 환경은 source와 replica의 역할, 네트워크와 복제 지연을 배우는 데 알맞았다. 실제 서비스에 적용할 때는 그 다음 단계로 최신성 정책, 장애 조치, 백업·복구를 설계해야 한다. 복제는 데이터베이스를 하나 더 띄우는 일이 아니라, 데이터가 두 위치에 서로 다른 시점으로 존재할 수 있음을 애플리케이션이 받아들이는 변화다.
조회 쿼리 자체가 느리다면 복제본 증설보다 인덱스 검증이 먼저일 수 있다. 게시글 목록에 created_at 인덱스를 적용한 실험도 함께 참고할 수 있다.
참고 문서: MySQL 8.4 Replication 개요, MySQL 복제 지연 FAQ
당시 기록: DB 복제 도입, 복제 개념, Docker Compose 구성