대규모 서비스를 운영하다 보면, 트래픽이 늘어날수록 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 같은 도구도 자주 언급됩니다.
🔍 어떻게 동작할까?
- 애플리케이션이 Proxy 엔드포인트로 SQL을 보냅니다.
- Proxy는 SQL 타입, 세션 힌트, 트랜잭션 상태를 보고 “이건 Primary”, “이건 Replica”라고 결정합니다.
- Primary는 쓰기 트랜잭션을 처리하고 binlog를 남깁니다.
- Replica는 binlog를 적용해 최신 상태를 따라잡고, SELECT 요청을 받습니다.
- Proxy는 커넥션 수, Replica lag, 헬스 상태를 감시하며 Failover 전략을 실행합니다.
- 필요하다면 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 주소로 바꾸면 되기 때문에 “비침투적”인 방식입니다.
정합성 규칙
- INSERT 직후 데이터는 이미 알고 있다
Primary에서 INSERT를 했으므로 PK, 입력 값, default 값, trigger 결과까지 애플리케이션이 보유하고 있습니다. ⇒ POST 응답은 Replica 지연과 무관하게 정확합니다. - 문제는 그다음 GET이 Replica로 갈 수 있다는 점
POST /items다음 바로GET /items/{id}를 호출하면 Replica에서 아직 복제가 안 끝났을 수 있습니다. - 쓰기 직후 조회는 Primary로 강제
Proxy 세션 스티키니스(read-your-writes), SQL 힌트(/* read_from_primary */), hostgroup routing을 사용합니다. - POST 응답을 풍부하게
생성된 리소스 정보를 충분히 내려주면 클라이언트가 즉시 GET을 호출할 이유가 줄어듭니다. - 리스트 조회는 Replica 기반으로
“조금 늦게 보여도 되는 데이터”는 Replica에서, 단건 상세 조회는 Primary로 분리합니다.
🅱️ 패턴 B — 애플리케이션이 R/W 의도 명시
Spring+JPA 환경에서 많이 쓰는 방식으로 DataSource/Port를 나눕니다.
writerDataSource → Primary
readerDataSource → Replica
즉, 코드 레벨에서 “이 쿼리는 읽기용, 저 쿼리는 쓰기용”을 분리해 Proxy에게 명확한 신호를 줍니다.
MSA 구조나 이벤트 기반 시스템처럼 서비스별 특성을 살리고 싶을 때 유리합니다.
정합성 규칙
- 쓰기와 조회를 같은 트랜잭션 안에서 처리
별도 SELECT 없이도 정확한 응답을 만들 수 있어 가장 안전합니다.@Transactional public ItemResponse createItem(CreateItemReq req) { Item item = itemWriteRepo.save(...); // INSERT → Primary return ItemResponse.from(item); // 엔티티로 바로 응답 } - 상세 조회는 필요에 따라 Primary 강제
force_primary=true같은 파라미터로 Primary를 선택하거나, 쓰기 직후 상세 API는 writerDataSource를 사용합니다. - 리스트/통계는 Replica 전용
eventual consistency를 UX로 허용하고, 읽기 부하 대부분을 Replica로 넘깁니다. - 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가 든든한 방패가 되어 줄 거라는 생각이 듭니다. 😉
'Data > DataBase' 카테고리의 다른 글
| Nested Set Model: 효율적인 트리 구조 (0) | 2025.03.14 |
|---|---|
| 데이터베이스 Anti-Pattern 피하기 (0) | 2024.11.25 |
| [DataBase] 샤딩(Sharding), 파티셔닝(Partitiong), 레플리케이션(Replication) (0) | 2024.10.20 |
| [DB] RDBMS와 NoSQL의 개념과 차이점 (0) | 2024.01.12 |