Skip to main content
Summary

단일 서버 이후에 고민해야 할 것

처음 MySQL을 배울 때는 하나의 서버에 데이터베이스를 만들고, 애플리케이션이 그 서버에 접속하는 구조로 충분합니다. 하지만 서비스가 커지면 단일 서버 구조는 곧 한계를 만납니다.

조회 요청이 많아져 CPU와 디스크 I/O가 바빠질 수 있습니다. 장애가 발생하면 전체 서비스가 멈출 수 있습니다. 데이터가 커지면 백업, 배치, 통계 쿼리도 운영 서버에 부담을 줍니다.

이때 필요한 개념이 복제, 고가용성, 확장 전략입니다. 복제는 데이터를 다른 MySQL 서버로 따라가게 만드는 기술입니다. 고가용성은 장애가 나도 서비스를 계속 운영하기 위한 구조입니다. 확장 전략은 더 많은 요청과 데이터를 처리하기 위해 시스템을 키우는 방법입니다.

중요한 점은 모든 문제를 한 번에 복잡한 구조로 해결하려 해서는 안 된다는 것입니다. 먼저 현재 병목이 읽기인지, 쓰기인지, 데이터 크기인지, 장애 대응인지 구분해야 합니다. 그다음 가장 단순한 해결책부터 적용하는 것이 좋습니다.

복제, 고가용성, 확장 전략AI 생성 이미지

Primary-Replica 복제 구조

MySQL에서 가장 흔히 사용하는 복제 구조는 Primary-Replica입니다. 하나의 Primary 서버가 쓰기를 담당하고, 하나 이상의 Replica 서버가 Primary의 변경 내용을 복제해 따라갑니다.

기본 구조는 다음과 같습니다.

  • Primary: INSERT, UPDATE, DELETE 같은 쓰기 트랜잭션 처리

  • Replica: Primary의 변경 내역을 받아 같은 데이터를 유지

  • 애플리케이션: 쓰기는 Primary로 보내고, 조회는 상황에 따라 Replica로 분산

이 구조는 흔히 active/passive 구조로 이해할 수 있습니다. Primary는 실제 쓰기를 처리하는 활성 노드이고, Replica는 읽기 전용으로 대기하거나 조회 부하를 나눠 갖는 노드입니다. 읽기 요청이 많은 서비스에서는 Replica를 여러 대 두어 목록 조회, 검색, 리포트 조회 같은 작업을 분산할 수 있습니다. 다만 모든 쓰기는 반드시 Primary로 보내야 합니다1.

Replica는 운영에서 여러 용도로 쓰입니다.

  • 읽기 요청 분산

  • 장애 시 승격 후보

  • 백업 수행용 서버

  • 통계, 리포트, 개발자 조회용 서버

하지만 Replica가 있다고 해서 백업이 필요 없어지는 것은 아닙니다. 잘못된 DELETE가 Primary에서 실행되면 그 변경도 Replica로 복제됩니다. 복제는 고가용성과 읽기 분산을 돕는 기술이지, 과거 시점으로 되돌리는 백업을 완전히 대체하지 못합니다.

비동기 복제와 복제 지연

MySQL의 기본 복제는 보통 비동기 복제로 이해하면 됩니다. 애플리케이션이 Primary에 쓰기를 요청하면 Primary는 트랜잭션을 처리하고 응답합니다. 이때 Primary는 모든 Replica가 해당 변경을 적용했는지 끝까지 기다리지 않습니다. 각 Replica는 나중에 변경 로그를 읽고 자기 속도대로 반영합니다1.

이 차이 때문에 복제 지연이 생깁니다. 복제 지연은 Primary에는 이미 반영된 데이터가 Replica에는 아직 반영되지 않은 시간 차이를 말합니다.

예를 들어 사용자가 게시글을 작성했다고 해 보겠습니다.

  1. 애플리케이션이 Primary에 게시글을 저장한다.

  2. 저장 성공 응답을 사용자에게 돌려준다.

  3. 사용자가 곧바로 "내 게시글 목록"을 조회한다.

  4. 조회 요청이 Replica로 전달된다.

  5. Replica가 아직 새 게시글을 복제하지 못했다면 목록에 보이지 않는다.

사용자 입장에서는 "방금 쓴 글이 사라졌다"고 느낍니다. 실제로 데이터가 사라진 것은 아니지만, 조회한 서버가 최신 상태가 아니기 때문에 발생하는 문제입니다. 이를 read-after-write 일관성 문제라고 부를 수 있습니다.

복제 지연은 여러 이유로 커질 수 있습니다.

  • Primary와 Replica 사이의 네트워크 거리가 멀다.

  • Replica 서버 성능이 부족하다.

  • Primary에서 많은 쓰기 트랜잭션이 발생한다.

  • Replica에서 무거운 조회 쿼리가 같이 실행된다.

  • 대량 변경 작업이 복제 흐름을 막는다.

따라서 모든 조회를 무조건 Replica로 보내면 안 됩니다. 최신 데이터가 반드시 필요한 요청은 Primary에서 읽어야 합니다. 예를 들어 주문 완료 직후 주문 상세 조회, 결제 상태 확인, 방금 수정한 프로필 조회는 Primary로 보내는 편이 안전합니다. 반대로 인기 게시글 목록, 상품 카탈로그, 오래된 통계처럼 약간의 지연을 허용할 수 있는 조회는 Replica에 맡기기 좋습니다.

실무에서는 다음과 같은 방식으로 읽기 라우팅을 설계합니다.

  • 쓰기 직후 일정 시간 동안 같은 사용자의 조회를 Primary로 보낸다.

  • 복제 지연이 기준 이상이면 해당 Replica를 읽기 대상에서 제외한다.

  • 중요한 업무 조회는 항상 Primary를 사용한다.

  • 같은 리전 안에서는 반동기 복제를 검토하고, 먼 지역 간 복제는 지연을 감안해 비동기로 운영한다.

핵심은 "Replica는 빠른 조회를 위한 도구이지만 항상 최신이라고 가정하면 안 된다"는 점입니다.

장애 조치의 기본 흐름

고가용성의 핵심은 Primary 장애가 났을 때 서비스를 얼마나 안전하고 빠르게 복구하느냐입니다. 이 과정을 장애 조치, 즉 failover라고 합니다.

장애 조치는 단순히 Replica 하나를 새 Primary로 바꾸는 일이 아닙니다. 운영 관점에서는 다음 흐름을 함께 설계해야 합니다2.

  1. Primary 장애 판단

  2. Replica 상태 확인

  3. 승격할 Replica 선택

  4. 기존 Primary 격리

  5. 새 Primary로 승격

  6. 애플리케이션 또는 프록시의 접속 대상 전환

  7. 나머지 Replica를 새 Primary에 다시 연결

  8. 데이터 정합성 확인

첫 단계는 장애 판단입니다. 단순히 포트 접속이 안 된다고 바로 장애로 단정하면 위험합니다. 네트워크 일부만 끊긴 상태일 수도 있고, 애플리케이션 서버에서만 접근하지 못하는 상황일 수도 있습니다. 헬스 체크는 MySQL 접속 가능 여부, 간단한 쿼리 실행 여부, 복제 상태, 서버 자원 상태를 함께 봐야 합니다.

두 번째는 Replica 상태 확인입니다. 모든 Replica가 같은 위치까지 복제되어 있지는 않을 수 있습니다. 어떤 Replica는 지연이 크고, 어떤 Replica는 더 최신 상태일 수 있습니다. 승격 후보는 가능한 한 최신 변경을 많이 반영한 서버여야 합니다. relay log에 남아 있는 이벤트가 있다면 가능한 한 적용한 뒤 승격하는 것이 안전합니다.

세 번째는 기존 Primary 격리입니다. 장애로 보였던 기존 Primary가 나중에 다시 살아나 쓰기를 받으면 두 개의 쓰기 서버가 생길 수 있습니다. 이를 split-brain이라고 합니다. 이 상태에서는 같은 데이터가 서로 다르게 변경될 수 있어 복구가 매우 어려워집니다. 그래서 장애 조치에서는 기존 Primary를 네트워크에서 제외하거나, 쓰기 불가능 상태로 만들거나, 운영자가 명확히 격리해야 합니다.

마지막으로 애플리케이션 트래픽을 새 Primary로 보내야 합니다. 이 전환은 DNS, 서비스 디스커버리, 로드 밸런서, ProxySQL 같은 프록시 계층을 통해 처리할 수 있습니다. 애플리케이션 코드가 특정 DB 서버 주소를 직접 고정해 두면 장애 조치가 어려워집니다. 운영 환경에서는 접속 대상을 추상화해 두는 것이 좋습니다.

수동 장애 조치와 자동 장애 조치

장애 조치는 크게 수동 방식과 자동 방식으로 나눌 수 있습니다.

수동 장애 조치는 운영자가 상황을 확인한 뒤 Replica를 승격하고 트래픽을 전환하는 방식입니다. 속도는 느릴 수 있지만, 데이터 손실 위험이나 특수 상황을 사람이 판단할 수 있습니다. 규모가 작거나 데이터 정합성이 매우 중요한 서비스에서는 수동 절차를 명확히 준비하는 것만으로도 큰 도움이 됩니다.

자동 장애 조치는 모니터링 도구나 HA 도구가 Primary 장애를 감지하고 정해진 규칙에 따라 Replica를 자동 승격하는 방식입니다. 장애 시간이 짧아지는 장점이 있지만, 잘못된 판단을 하면 더 큰 사고가 날 수 있습니다. 예를 들어 Primary가 실제로 죽은 것이 아니라 네트워크 단절로 잠시 보이지 않는 상태라면, 자동 승격이 split-brain을 만들 수 있습니다.

그래서 자동화는 "사람이 하던 일을 단순히 빠르게 실행하는 것"이 아니라 "잘못 실행되지 않도록 조건을 엄격하게 정의하는 것"까지 포함해야 합니다.

고가용성 설계에서는 다음 항목을 함께 고려해야 합니다3.

  • Replica에는 read_only, super_read_only를 적용한다.

  • 서버 재시작 후에도 Replica가 쓰기 가능 상태로 뜨지 않게 한다.

  • 백업용, 분석용, 개발자 조회용 Replica는 승격 후보에서 제외한다.

  • 복제 지연과 복제 오류를 지속적으로 모니터링한다.

  • 프록시나 라우팅 계층이 현재 Primary를 정확히 알고 있어야 한다.

  • 장애 조치 후 애플리케이션이 새 Primary로 정상 접속하는지 확인한다.

고가용성은 서버를 여러 대 두는 것만으로 완성되지 않습니다. 감지, 판단, 승격, 격리, 전환, 검증 절차가 모두 있어야 합니다.

확장 전략을 선택하는 기준

성능 문제가 생기면 먼저 서버를 늘리고 싶어집니다. 하지만 확장에는 비용과 복잡도가 따릅니다. 따라서 다음 순서로 검토하는 것이 좋습니다.

첫째, 쿼리와 인덱스를 먼저 개선합니다. SELECT *를 줄이고, 필요한 조건에 맞는 인덱스를 만들고, 느린 쿼리를 확인합니다. 앞 장에서 다룬 Slow Query Log와 EXPLAIN은 확장 전 반드시 확인해야 할 도구입니다.

둘째, 수직 확장을 검토합니다. 수직 확장은 한 서버의 CPU, 메모리, 디스크 성능을 높이는 방식입니다. 구조가 단순하고 애플리케이션 변경이 적습니다. 하지만 한 대의 물리적 한계가 있고, 장비 교체나 스토리지 이전 과정에서 작업 시간이 필요할 수 있습니다4.

셋째, 읽기 부하가 문제라면 Replica 기반 수평 확장을 적용합니다. Primary는 쓰기에 집중하고, Replica가 조회 요청을 나눠 받습니다. 대부분의 웹 서비스는 쓰기보다 읽기가 많기 때문에 이 방식이 효과적입니다. 단, 앞서 본 복제 지연과 읽기 라우팅 문제가 함께 따라옵니다.

넷째, 반복 조회가 병목이라면 캐시를 도입합니다. Redis나 Memcached 같은 메모리 저장소에 자주 읽는 값을 저장하면 MySQL 요청을 줄일 수 있습니다. 상품 상세, 설정값, 인기 목록처럼 자주 읽히고 즉시 최신일 필요가 낮은 데이터에 적합합니다. 다만 캐시된 값과 DB 값이 달라질 수 있으므로 만료 시간과 갱신 정책을 정해야 합니다.

다섯째, 테이블이 매우 커졌다면 파티셔닝을 검토할 수 있습니다. 파티셔닝은 하나의 MySQL 서버 안에서 큰 테이블을 날짜, 범위, 해시 같은 기준으로 나누는 방식입니다. 예를 들어 주문 테이블을 월별로 나누면 특정 기간 조회나 오래된 데이터 관리가 쉬워질 수 있습니다. 그러나 파티션 키를 잘못 고르면 효과가 작고, 운영 관리가 오히려 복잡해집니다.

마지막으로, 한 서버나 하나의 Primary로 감당하기 어려운 수준이라면 샤딩을 고려합니다. 샤딩은 데이터를 여러 MySQL 서버에 나누어 저장하는 방식입니다. 예를 들어 회원 ID 범위나 지역 기준으로 데이터를 분산할 수 있습니다. 샤딩은 큰 확장성을 제공하지만 가장 복잡합니다. 여러 샤드에 걸친 조인, 트랜잭션, 집계, 백업, 장애 조치가 어려워집니다. 그래서 샤딩은 가능한 한 마지막 선택지로 두는 것이 좋습니다.

운영에서 기억해야 할 원칙

복제와 확장은 성능을 높이고 장애에 강한 구조를 만들지만, 동시에 운영 난이도를 높입니다. 서버가 한 대일 때는 "어느 데이터가 최신인가"라는 질문이 단순합니다. 서버가 여러 대가 되면 이 질문부터 복잡해집니다.

따라서 다음 원칙을 기억해야 합니다.

  • 쓰기는 하나의 Primary로 모은다.

  • Replica는 읽기 분산에 사용하되 최신성이 필요한 조회는 신중히 라우팅한다.

  • 복제 지연을 항상 모니터링한다.

  • 장애 조치 절차를 문서화하고 정기적으로 연습한다.

  • 백업과 복구 테스트는 복제와 별개로 유지한다.

  • 확장은 쿼리 개선, 수직 확장, 읽기 분산, 캐시, 파티셔닝, 샤딩 순서로 점진적으로 검토한다.

MySQL을 안정적으로 운영한다는 것은 단순히 빠른 서버를 쓰는 일이 아닙니다. 데이터가 어디에 쓰이고, 어디까지 복제되었고, 장애가 났을 때 어느 서버가 쓰기를 이어받을지 명확히 아는 일입니다. 서비스가 커질수록 MySQL 운영의 핵심은 "더 많은 서버"가 아니라 "명확한 역할과 안전한 전환 절차"에 있습니다.

참고

1MySQL replication: Best practices and considerations

2Introduction to Failover for MySQL Replication – the 101 Blog

3MySQL High Availability: Techniques to Ensure Uptime in Critical Systems

4How to Scale MySQL for Maximum Performance

책 목록

처음부터 실무까지 배우는 MySQL

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기