Data/DataBase

프록시 DB란? 읽기·쓰기 분리

누구세연 2025. 11. 19. 22:29

대규모 서비스를 운영하다 보면, 트래픽이 늘어날수록 DB 부하가 병목이 되는 순간이 찾아옵니다.
트래픽 스파이크나 정기 리포트 배치처럼 단순 조회성 쿼리가 몰리면 “운영 데이터베이스의 안정성”이 흔들리기 시작하죠.

이럴 때 자주 등장하는 해결책 중 하나가 바로 프록시 DB(Proxy DB) 입니다.
단순히 DB 앞단에 장비를 하나 더 세우는 것이 아니라, 읽기/쓰기 특성을 분리하고 운영 전략을 명시적으로 가져가겠다는 선언에 가깝습니다.


이번 글에서는 프록시 DB의 기본 개념부터, 운영 중 겪는 실전 이슈, 그리고 개발자 입장에서의 사용 패턴까지 정리해보려 합니다.

 

 


 

📖 프록시 DB란 무엇일까?

말 그대로 본 DB(Primary) 앞단에 서서 요청을 중계하는 “중간 계층”입니다. 애플리케이션은 Proxy 하나만 바라보고, Proxy가 알아서 Primary/Replica를 골라주는 구조입니다. 👀
여기서 Primary는 “쓰기 전용 DB”, Replica는 “Primary를 복제한 읽기 전용 DB”라고 이해하면 쉽습니다.

아키텍처 다이어그램은 아래와 같습니다.

  • Application: 클라이언트 요청을 받아 SQL을 Proxy로 던집니다. DB가 하나인 것처럼 느끼죠.
  • DB Proxy / Router: SQL을 파싱하고 커넥션 풀, 트랜잭션 스티키니스를 관리합니다.
  • Primary DB (Writer): INSERT/UPDATE/DELETE를 처리하고 binlog를 생성합니다.
  • Replica DB (Readers): Primary의 binlog를 비동기로 따라가며 읽기 트래픽을 분산합니다.

MySQL 진영에서 ProxySQL, MaxScale, MySQL Router가 대표적이고, 클라우드에서는 RDS Proxy나 Aurora Router, 그리고 Vitess VTGate 같은 도구도 자주 언급됩니다.

 


 

🔍 어떻게 동작할까?

  1. 애플리케이션이 Proxy 엔드포인트로 SQL을 보냅니다.
  2. Proxy는 SQL 타입, 세션 힌트, 트랜잭션 상태를 보고 “이건 Primary”, “이건 Replica”라고 결정합니다.
  3. Primary는 쓰기 트랜잭션을 처리하고 binlog를 남깁니다.
  4. Replica는 binlog를 적용해 최신 상태를 따라잡고, SELECT 요청을 받습니다.
  5. Proxy는 커넥션 수, Replica lag, 헬스 상태를 감시하며 Failover 전략을 실행합니다.
  6. 필요하다면 Proxy가 자체 커넥션 풀과 캐시 정책을 적용해 애플리케이션의 DB 커넥션 개수를 간접적으로 조절합니다.

예를 들어 사용자가 “새 글 작성” 버튼을 눌러 POST /items 를 호출하면, Proxy는 INSERT 쿼리를 Primary로 보냅니다.
곧바로 GET /items/{id} 요청이 들어오면 Proxy는 “방금 쓴 글을 바로 보여줘야 한다”는 조건을 보고 Primary로 라우팅 하지만, “전체 글 목록” 같은 리스트 조회는 Replica로 분산합니다.
이 흐름이 읽기/쓰기 분리의 기본 골격입니다.

핵심은 쓰기 쿼리는 반드시 Primary, 읽기 쿼리는 Replica로 분산. 그리고 “쓰기 직후 조회”처럼 정합성이 민감한 흐름은 Proxy가 Primary를 고정시켜 준다는 점입니다.

 


 

🎯 왜 도입할까?

  • 읽기/쓰기 분리: SELECT를 Replica로 보내 Primary 부하를 줄입니다.
  • 트래픽 분산: 리포트, 리스트 API 같은 읽기를 Replica에서 처리해 전체 응답 시간을 안정화합니다.
  • 장애 대비: Primary 장애 시 Replica를 임시 조회 노드로 쓰고, Proxy가 커넥션을 차단해 장애 범위를 제한합니다.
  • 보안/정책 분리: 외부에서는 Proxy만 노출하고, 내부 정책을 Proxy에서 통제할 수 있습니다.

결국 프록시 DB는 “DB를 더 많이 장만하는 것”이 아니라, 읽기와 쓰기의 속성을 나눠 다루는 운영 방식입니다.

⚠️ 도입 전에 생각할 것

  • 운영 복잡도 증가: Proxy 자체도 장애 포인트이므로 이중화, 패치, 모니터링 계획이 선행돼야 합니다.
  • SQL 파싱 한계: Stored Procedure나 복잡한 커스텀 함수는 Proxy가 읽기/쓰기를 잘못 판단할 수 있습니다.
  • 정합성 정책 수립: “eventual consistency를 허용하는 범위”를 UX 관점에서 합의하지 않으면 도입 효과가 반감됩니다.

 


 

🧱 운영 중 자주 맞닥뜨리는 이슈

Proxy를 세우면 마치 모든 것이 자동화될 것 같지만, 실제 운영에서는 다음과 같은 함정이 반복해서 등장합니다.
각 이슈가 왜 생기는지, 현장에서 즉시 적용할 수 있는 대응법을 함께 정리했습니다.

1) 🔥 복제 지연(Replication Lag)

Replica는 Primary의 binlog를 따라가는데, 트래픽이 쏠리거나 대량 쓰기가 붙으면 지연이 발생합니다.

  • 증상: “방금 저장한 데이터가 조회되지 않는다”, 빈 리스트, 오래된 캐시 갱신.
  • 왜 생길까?: Primary → Replica 동기화는 네트워크와 I/O에 의존하므로, 초당 수천 건의 INSERT가 들어오면 Replica가 그 속도를 따라잡지 못합니다.
  • : CloudWatch나 PMM처럼 Replica_Lag 지표에 알람을 걸고, 허용 지연(예: 1초 이상) 시 Proxy가 해당 Replica를 읽기 대상에서 제외하도록 설정합니다.
  • 대응: 쓰기 직후 조회는 반드시 Primary, Replica lag가 일정 기준 초과하면 Proxy에서 제외, 도메인별로 “읽기라도 Primary 강제” 정책을 두는 것도 방법입니다.

2) 🧱 스키마 불일치(DDL 타이밍)

Primary에는 새로운 컬럼이 있는데 Replica에는 없어 Unknown column 에러가 나는 상황입니다.

  • 왜 생길까?: Replica는 binlog를 순차적으로 따라가기 때문에, Primary에서 ALTER TABLE 을 실행한 직후 Replica는 아직 예전 스키마로 SELECT를 처리합니다.
  • : pt-online-schema-change, Liquibase 같은 도구로 DDL 계획을 코드화하고, 배포 일정과 Proxy 라우팅 정책을 한 문서에서 관리합니다.
  • 대응: DDL 적용 순서를 Primary → Replica로 명확히 하고, 마이그레이션 동안 해당 도메인의 읽기는 Primary로 고정합니다. 호환 스키마(old/new 필드 동시 지원)를 설계하는 것도 안전합니다.

3) 💥 Failover(장애 전환) 복잡성

Primary 장애 시 Replica를 Primary로 승격해야 하는데, 비동기 복제라면 binlog가 다 적용되지 않았을 수 있습니다.

  • 왜 생길까?: 장애는 대개 새벽이나 주말에 오고, 운영자가 직접 커넥션을 끊고 호스트를 바꾸는 동안 수천 개 커넥션이 타임아웃 나 버립니다.
  • : ProxySQL의 scheduler, RDS Proxy의 자동 failover 같은 기능을 미리 테스트하고, “누가 어떤 명령을 실행할지”까지 체크리스트로 만들어 두면 당황하지 않습니다.
  • 대응: 세미동기 복제를 고려하고, Proxy의 자동 Failover 기능을 적극 활용합니다. 장애 시 어떤 순서로 hostgroup 업데이트, DNS 변경, 애플리케이션 재연결을 할지 문서화해 두는 것이 정말 중요합니다.

4) ⚙️ 트랜잭션 스티키니스

BEGIN; SELECT ...; UPDATE ...; 가 서로 다른 노드로 라우팅되면 정합성이 깨집니다.

  • 왜 생길까?: Proxy가 각 SQL을 독립적으로 판단하면, 트랜잭션 중간 SELECT를 Replica로 보내 버릴 수 있기 때문입니다.
  • : ProxySQL은 hostgroup “stickiness” 옵션이 있고, RDS Proxy는 트랜잭션 단위로 Primary에 붙어 있게 만들 수 있습니다. 프레임워크 단에서도 @Transactional 에 readOnly=false 같은 속성을 명시합니다.
  • 대응: 트랜잭션 시작 시 Proxy가 세션을 Primary에 고정하고, SELECT ... FOR UPDATE 는 무조건 Primary로 보내도록 설정합니다.

5) 📡 관측과 모니터링

Proxy, Primary, Replica 각각의 지표를 보지 않으면 장애 원인을 Primary 탓으로만 돌리게 됩니다.

  • 무엇을 볼까?: Replica lag, Proxy 커넥션 풀 사용률, hostgroup 별 큐 길이, 라우팅 실패율, Primary CPU/IO, slow query 비율이 기본 세트입니다.
  • : 한 대시보드에서 Proxy 지표와 DB 지표를 함께 보고, 월 1회라도 장애 리허설을 하면 “Proxy 설정은 잘돼 있는데 Primary가 버티지 못했다” 같은 오해를 줄일 수 있습니다.
  • 대응: Replica lag, Proxy 큐 길이, 라우팅 실패율 등을 모니터링하고, 주기적으로 Failover 리허설을 합니다.

 


 

🧑‍💻 개발자 입장에서의 사용 패턴

프록시 DB를 도입한다고 애플리케이션 코드가 모두 바뀌는 것은 아닙니다..! 

현실적으로는 (찾아보며) 아래 두 패턴을 가장 많이 보았습니다.


핵심은 “Proxy가 자동으로 판단하게 둘 것인가”, “애플리케이션이 읽기/쓰기를 명시할 것인가”의 선택입니다.
도입한 뒤에도 “어떤 쿼리가 왜 특정 노드로 가는지”를 전제와 함께 문서화해야 지속적으로 운용할 수 있습니다.

🅰️ 패턴 A — 프록시가 모든 라우팅 담당

프록시가 “SELECT는 Replica, INSERT/UPDATE는 Primary”를 자동으로 결정합니다. 모놀리식이나 레거시 시스템에서 도입하기 쉽습니다.
개발자는 기존 커넥션 문자열만 Proxy 주소로 바꾸면 되기 때문에 “비침투적”인 방식입니다.

정합성 규칙

  1. INSERT 직후 데이터는 이미 알고 있다
    Primary에서 INSERT를 했으므로 PK, 입력 값, default 값, trigger 결과까지 애플리케이션이 보유하고 있습니다. ⇒ POST 응답은 Replica 지연과 무관하게 정확합니다.
  2. 문제는 그다음 GET이 Replica로 갈 수 있다는 점
    POST /items 다음 바로 GET /items/{id} 를 호출하면 Replica에서 아직 복제가 안 끝났을 수 있습니다.
  3. 쓰기 직후 조회는 Primary로 강제
    Proxy 세션 스티키니스(read-your-writes), SQL 힌트(/* read_from_primary */), hostgroup routing을 사용합니다.
  4. POST 응답을 풍부하게
    생성된 리소스 정보를 충분히 내려주면 클라이언트가 즉시 GET을 호출할 이유가 줄어듭니다.
  5. 리스트 조회는 Replica 기반으로
    “조금 늦게 보여도 되는 데이터”는 Replica에서, 단건 상세 조회는 Primary로 분리합니다.

🅱️ 패턴 B — 애플리케이션이 R/W 의도 명시

Spring+JPA 환경에서 많이 쓰는 방식으로 DataSource/Port를 나눕니다.

writerDataSource → Primary
readerDataSource → Replica

즉, 코드 레벨에서 “이 쿼리는 읽기용, 저 쿼리는 쓰기용”을 분리해 Proxy에게 명확한 신호를 줍니다.
MSA 구조나 이벤트 기반 시스템처럼 서비스별 특성을 살리고 싶을 때 유리합니다.

정합성 규칙

  1. 쓰기와 조회를 같은 트랜잭션 안에서 처리
    @Transactional
    public ItemResponse createItem(CreateItemReq req) {
        Item item = itemWriteRepo.save(...);    // INSERT → Primary
        return ItemResponse.from(item);         // 엔티티로 바로 응답
    }
    별도 SELECT 없이도 정확한 응답을 만들 수 있어 가장 안전합니다.
  2. 상세 조회는 필요에 따라 Primary 강제
    force_primary=true 같은 파라미터로 Primary를 선택하거나, 쓰기 직후 상세 API는 writerDataSource를 사용합니다.
  3. 리스트/통계는 Replica 전용
    eventual consistency를 UX로 허용하고, 읽기 부하 대부분을 Replica로 넘깁니다.
  4. Reader/Writer 혼용 금지
    readOnly 트랜잭션에서 write 호출 금지, reader 포트에서 INSERT/UPDATE 금지 등 규칙을 코드로 강제합니다.

 


 

✍️ 마무리 & 체크리스트

프록시 DB는 “읽기/쓰기 분리를 자동으로 해 주는 중간 계층”이라는 단순한 정의 뒤에, 운영 디테일과 정합성 전략이 숨어 있습니다.
아래 체크리스트만 꾸준히 확인해도 Proxy 도입 효과를 확실히 체감할 수 있었습니다.

질문 체크 포인트
어느 API가 최신성을 요구하는가? 라우팅 힌트나 Primary 강제가 필요한 API를 목록화합니다.
Proxy가 SQL을 정확히 분류할 수 있는가? Prepared statement, 세션 변수, Stored Procedure 사용 여부를 점검합니다.
Replica lag와 Failover 상태를 모니터링하는가? Proxy/DB 지표를 한 대시보드로 묶고 알람을 정의합니다.
장애 시나리오는 문서화되어 있는가? Primary 승격, hostgroup 업데이트, 애플리케이션 재연결 순서를 정리합니다.

프록시 DB를 도입하면 단순히 “DB를 분산했다”가 아니라, 쓰기 직후 조회는 Primary로, eventual consistency를 허용할 영역은 Replica로, 장애 시나리오를 미리 연습한다는 습관을 가지게 됩니다.

이 세 가지 원칙만 실천해도 트래픽 폭증 순간마다 Proxy가 든든한 방패가 되어 줄 거라는 생각이 듭니다. 😉