티스토리 뷰

서비스가 성장하면서 가장 먼저 한계를 드러내는 지점은 애플리케이션 코드가 아니라 데이터베이스 구조인 경우가 많습니다. 초기에는 단일 관계형 데이터베이스 구조만으로도 충분히 안정적인 서비스를 운영할 수 있지만, 사용자 수 증가와 함께 조회 요청이 폭증하기 시작하면 기존 구조는 점점 부담을 받게 됩니다.

 

특히 Java 기반 백엔드 시스템에서는 트랜잭션 무결성과 개발 편의성을 이유로 RDB 중심 구조를 선택하는 경우가 일반적입니다. 그러나 서비스가 일정 규모를 넘어가면 쓰기 처리보다 조회 처리 비용이 훨씬 커지고, 결국 읽기 요청이 전체 시스템 성능을 제한하는 상황이 발생합니다.

 

이 글에서는 이러한 상황에서 CQRS 패턴을 어떻게 검토하게 되는지, 그리고 Aurora MySQL과 MongoDB Atlas를 함께 사용하는 구조가 어떤 방식으로 성능 개선에 기여할 수 있는지를 운영 관점에서 정리합니다. 또한 CQRS가 항상 정답이 아니라는 점을 전제로, 실제 도입 판단에 도움이 될 수 있는 기준을 함께 공유하고자 합니다.

 


 

단일 RDB 구조에서 나타나는 성능 병목의 실제 모습

 

초기 시스템은 보통 단일 RDB 인스턴스를 중심으로 구성됩니다. 모든 쓰기와 조회가 동일한 데이터 모델을 기반으로 처리되며, 스키마 정규화와 트랜잭션 무결성을 중심으로 설계됩니다. 이 구조는 개발 초기 단계에서는 매우 안정적이고 관리하기도 수월합니다.

 

문제는 서비스가 성장하면서 조회 요청이 폭증할 때 발생합니다. 운영 환경에서 자주 관찰되는 현상은 단순히 DB 응답 속도가 느려지는 문제가 아닙니다. 조회 쿼리가 점점 복잡해지고, 다양한 화면 요구사항을 만족시키기 위해 다수의 조인과 집계 쿼리가 추가되면서 CPU와 디스크 I/O가 급격히 증가합니다.

 

이 과정에서 인덱스를 계속 추가하게 되지만, 인덱스는 쓰기 성능을 함께 저하시킵니다. 또한 특정 조회 요구를 만족시키기 위해 추가한 인덱스가 다른 쿼리에는 도움이 되지 않는 경우도 많습니다. 결국 데이터 모델이 조회 요구사항을 만족시키기 위해 점점 복잡해지며, 쓰기와 읽기가 서로 영향을 주는 구조가 됩니다.

 

이 시점에서 시스템은 단순한 튜닝 문제가 아니라 구조적인 한계에 도달하게 됩니다.

 


 

기존 대안들을 먼저 검토하게 되는 이유

 

CQRS를 도입하기 전에 대부분의 팀은 기존 구조 안에서 해결할 수 있는 방법을 먼저 시도합니다. 이는 합리적인 접근입니다.

 

대표적으로 인덱스 튜닝, 쿼리 리팩터링, 캐시 적용, 리드 레플리카 추가 등이 있습니다. 특히 Aurora MySQL 환경에서는 리드 레플리카를 통해 읽기 부하를 분산시키는 전략이 효과적으로 동작합니다.

 

하지만 이러한 방식들은 조회 부하를 분산시키거나 응답 속도를 개선할 수는 있어도, 조회 모델 자체가 복잡해지는 문제를 해결하지는 못합니다. 결국 조회 쿼리는 여전히 동일한 스키마 구조를 기반으로 동작하고, 비즈니스 요구가 늘어날수록 조회 로직 역시 계속 복잡해집니다.

 

결국 어느 시점에서는 “읽기와 쓰기를 동일한 모델로 유지해야 하는가”라는 질문을 하게 됩니다. 이 지점에서 CQRS 패턴이 설계 선택지로 등장합니다.

 


 

CQRS 패턴을 운영 관점에서 이해하기

 

CQRS는 Command와 Query의 책임을 분리하는 설계 패턴입니다. 핵심은 쓰기 모델과 읽기 모델을 동일하게 유지할 필요가 없다는 점입니다.

 

운영 관점에서 보면 이는 다음과 같은 질문으로 정리됩니다. 쓰기 트랜잭션 안정성과 조회 성능 요구를 하나의 데이터 모델로 동시에 만족시켜야 하는가, 아니면 각자의 요구에 맞는 모델을 따로 가져가는 것이 더 합리적인가라는 판단입니다.

 

CQRS는 성능 튜닝 기법이 아니라, 데이터 접근 방식 자체를 분리하는 설계 전략입니다. 쓰기 모델은 데이터 무결성과 트랜잭션 안정성에 집중하고, 읽기 모델은 조회 성능과 확장성에 집중하도록 역할을 나누는 접근입니다.

 


 

Command 모델로 Aurora MySQL을 유지하는 이유

 

쓰기 영역에서는 여전히 관계형 데이터베이스가 강력한 선택지입니다. Aurora MySQL은 MySQL과의 호환성을 유지하면서도 분산 스토리지 구조를 통해 높은 가용성과 빠른 장애 복구를 제공합니다.

 

Command 모델에서는 강한 일관성과 트랜잭션 처리가 중요합니다. 주문 처리, 결제 상태 변경, 사용자 정보 수정과 같은 작업에서는 데이터 정합성이 무엇보다 중요합니다. Aurora MySQL은 이러한 요구를 안정적으로 충족시키면서 기존 MySQL 기반 애플리케이션 구조를 그대로 유지할 수 있는 장점이 있습니다.

 

즉, CQRS를 도입하더라도 쓰기 경로는 기존 구조를 크게 변경하지 않고 유지할 수 있으며, 이는 도입 부담을 낮추는 중요한 요소가 됩니다.

 


 

Query 모델로 MongoDB Atlas를 사용하는 판단 배경

 

읽기 모델은 쓰기 모델과 요구사항이 완전히 다릅니다. 조회 요청은 화면 단위로 최적화되는 경우가 많으며, 여러 테이블을 조인하는 대신 이미 조합된 형태의 데이터를 빠르게 읽어오는 것이 중요합니다.

 

MongoDB Atlas는 문서 기반 데이터 모델을 사용하며, 비정규화된 구조를 효율적으로 저장하고 조회할 수 있습니다. 화면에 필요한 데이터를 문서 단위로 저장해두면 복잡한 조인을 수행할 필요 없이 빠르게 데이터를 반환할 수 있습니다.

 

또한 Atlas는 관리형 서비스로 제공되며, 수평 확장이 비교적 용이하고 운영 부담을 줄일 수 있습니다. 읽기 트래픽이 지속적으로 증가하는 환경에서 확장성을 확보하는 데 유리한 선택지가 됩니다.

 

중요한 점은 MongoDB를 전체 시스템의 주 데이터 저장소로 사용하는 것이 아니라, 조회 전용 모델로 제한한다는 점입니다. 쓰기 모델과 읽기 모델의 책임을 분리함으로써 각 데이터베이스의 장점을 활용하는 구조가 됩니다.

 


 

데이터 동기화와 일관성 전략의 현실적인 선택

 

CQRS 구조에서 가장 중요한 설계 요소는 데이터 동기화입니다. 쓰기 모델에서 발생한 데이터 변경을 읽기 모델에 어떻게 전달할 것인지가 핵심 문제입니다.

 

일반적으로는 변경 이벤트를 메시지 큐 또는 CDC 기반 파이프라인을 통해 읽기 저장소로 전달하는 구조를 사용합니다. 이 방식은 비동기 처리를 전제로 하며, 읽기 모델은 eventual consistency를 기반으로 동작합니다.

 

운영 관점에서는 데이터가 몇 초 정도 지연되어도 문제가 없는지 명확히 정의하는 것이 중요합니다. 모든 조회가 강한 일관성을 요구하지는 않기 때문입니다. 핵심 트랜잭션은 Command 모델을 직접 조회하고, 통계나 목록 조회 등은 Query 모델을 사용하도록 분리하는 방식이 현실적인 접근입니다.

 


 

CQRS 도입 이후 관찰되는 성능 변화

 

CQRS를 도입하면 단순히 조회 응답 속도가 개선되는 것 이상으로 구조적 변화가 발생합니다. 읽기 부하가 쓰기 모델에서 완전히 분리되면서 쓰기 처리 안정성이 향상되고, 인덱스 설계 부담도 줄어듭니다.

 

조회 요구사항이 늘어나더라도 쓰기 모델의 스키마를 변경할 필요가 없으며, 읽기 모델은 화면 요구사항에 맞게 자유롭게 구성할 수 있습니다. 이는 장기적인 운영 측면에서 상당한 이점을 제공합니다.

 

다만 운영 복잡도는 증가합니다. 데이터 동기화 파이프라인 관리, 동기화 지연 모니터링, 장애 복구 전략 등이 새로운 운영 과제로 등장합니다. CQRS는 복잡성을 제거하는 방식이 아니라, 복잡성이 위치를 이동하는 설계 선택임을 이해할 필요가 있습니다.

 


 

CQRS가 적합하지 않은 상황도 분명히 존재합니다

 

모든 서비스에 CQRS가 필요한 것은 아닙니다. 트래픽 규모가 크지 않거나 조회 패턴이 단순한 서비스에서는 기존 구조가 훨씬 효율적일 수 있습니다.

 

또한 모든 조회가 강한 일관성을 요구하는 도메인에서는 CQRS 구조의 장점이 제한적입니다. 설계 복잡도만 증가하고 실질적인 성능 개선은 크지 않을 수도 있습니다.

 

CQRS는 반드시 도입해야 할 패턴이 아니라, 특정 조건에서만 효과를 발휘하는 설계 선택지라는 점을 명확히 인식하는 것이 중요합니다.

 


 

마무리하며 정리해보는 판단 기준

 

CQRS 구조를 검토하게 되는 시점은 대체로 명확합니다. 읽기 요청이 시스템 부하의 대부분을 차지하고, 조회 요구사항이 쓰기 모델 구조를 지속적으로 복잡하게 만들기 시작할 때입니다.

 

Aurora MySQL과 MongoDB Atlas를 함께 사용하는 구조는 쓰기 안정성과 조회 확장성을 각각 분리하여 최적화하기 위한 하나의 선택지일 뿐입니다. 어떤 구조가 정답이라고 단정하기보다는, 현재 시스템이 어떤 문제를 겪고 있는지를 정확히 이해하고 그에 맞는 선택을 하는 것이 중요합니다.

 

개인적으로도 여러 구조를 운영하면서 느낀 점은, 성능 문제는 단일 기술 선택으로 해결되지 않는다는 사실이었습니다. 결국 중요한 것은 특정 기술을 도입하는 것이 아니라, 현재 구조의 한계를 정확히 이해하고 변화의 방향을 판단하는 과정이라고 생각합니다.