1. CQRS 패턴을 적용한 이유와 의문 🧐
MSA를 공부하며 프로젝트를 진행한 경험이 있습니다. MSA 환경에서는 서비스 간 데이터의 독립성을 위해 CQRS(Command and Query Responsibility Segregation) 패턴을 자주 사용한다고 배웠습니다. 그래서 저희 첫 프로젝트도 자연스럽게 CQRS 패턴을 적용했습니다.
하지만 문득 의문이 들었습니다. "정말 CQRS 패턴이 필수적일까? 만약 MSA 환경에서 CQRS 패턴을 사용하지 않고, 모든 서비스가 단일 데이터베이스를 사용한다면 어떤 성능이 나올까?"
이러한 궁금증을 해결하고 CQRS 패턴의 진정한 이점을 확인하기 위해, 저는 CQRS 패턴 도입 전과 후의 성능을 비교하기로 했습니다. 먼저 단일 DB를 사용할 때의 성능 한계를 파악하기 위한 부하 테스트를 진행했습니다.
2. CQRS 패턴 적용 전
부하 테스트는 게시글 생성(Write API)과 게시글 조회(Read API)에 대해 각각 다른 부하를 동시에 주는 방식으로 진행했습니다.
- 부하 설정:
- Write API: 100명의 동시 사용자가 지속적으로 게시글 생성 요청을 보냄.
- Read API: 1,000명의 동시 사용자가 지속적으로 게시글 조회 요청을 보냄
테스트 결과 💥
단일 DB 환경에서 진행한 테스트 결과는 다음과 같습니다.
1) Write API (쓰기) 결과
- 에러율: 0.07%
- 평균 응답 시간: 1,997ms
- 처리량: 47.2/초
2) Read API (조회) 결과
- 에러율: 0.05%
- 평균 응답 시간: 1,695ms
- 처리량: 553.7/초


분석 📊
이 결과는 단일 DB의 한계를 명확하게 보여줍니다. 에러율은 낮게 나왔지만, Write API의 평균 응답 시간이 2초에 달한다는 것은 부하가 발생했을 때 쓰기 작업이 지연되고 있음을 의미합니다. Read API 역시 1.7초라는 긴 응답 시간을 기록하며, 두 작업 모두 동시성 처리 능력에 한계가 있음을 시사했습니다.
3. CQRS 패턴 적용 후
이제 CQRS 패턴을 적용하고 동일한 부하 테스트를 진행하겠습니다.
Write 모델에서 발생한 이벤트를 Kafka로 전송하여 Read 모델과 동기화하는 구조를 사용했습니다.
- 부하 설정:
- Write API: 100명의 동시 사용자가 지속적으로 게시글 생성 요청을 보냄.
- Read API: 1,000명의 동시 사용자가 지속적으로 게시글 조회 요청을 보냄.
테스트 결과 💥
1) Write API (쓰기) 결과
- 에러율: 0.00%
- 평균 응답 시간: 165ms
- 처리량: 575.0/sec
2) Read API (조회) 결과
- 에러율: 0.00%
- 평균 응답 시간: 1,665ms
- 처리량: 567.5/초


분석 📊
CQRS 패턴 적용의 효과

- 쓰기 성능의 극적인 개선: Write API의 평균 응답 시간이 1,997ms에서 165ms로 크게 단축되었습니다. 이는 쓰기 작업이 Kafka를 통해 비동기적으로 처리되면서 DB에 직접적인 부하를 주지 않았기 때문입니다. 에러율도 0%로 완벽한 안정성을 확보했습니다.
- 여전한 Read API의 한계: Write API와 달리, Read API의 응답 시간은 이전과 비슷한 1.7초를 기록했습니다. 이는 CQRS 패턴 적용만으로는 읽기 성능 문제를 완전히 해결할 수 없으며, Read DB 또는 Read API 자체에 대한 추가적인 최적화가 필요함을 보여줍니다.
4. 결론: CQRS는 선택이 아닌 필수 🚀
이번 테스트를 통해 CQRS 패턴은 MSA 환경에서 성능과 안정성을 확보하는 데 필수적인 아키텍처라는 것을 확인했습니다.
- CQRS 패턴의 명확한 이점: Write API의 성능이 극적으로 개선되며, 읽기-쓰기 부하가 서로 간섭하지 않는다는 것을 증명했습니다.
- 추가적인 최적화의 필요성: 하지만 Read API 성능이 개선되지 않은 점은, CQRS 패턴 적용 후에도 Read 모델에 대한 지속적인 성능 점검과 최적화가 필요하다는 중요한 교훈을 주었습니다.
5. 예상과 달랐던 점, 그리고 그 원인에 대한 분석 🧐
이번 부하 테스트를 통해 CQRS 패턴의 이점을 명확히 확인했지만, 한 가지 예상과 달랐던 점이 있습니다. 바로 Read API의 성능 개선이 크지 않았다는 점입니다. 저는 CQRS 패턴 적용 후 Read API의 응답 시간이 획기적으로 단축될 것으로 기대했지만, 결과는 단일 DB 환경과 유사한 약 1.7초(1,665ms)를 기록했습니다.
이러한 결과의 원인을 분석해보니, 단순히 DB를 분리하는 것만으로는 충분하지 않다는 것을 깨달았습니다. 다음과 같은 이유로 Read API의 성능 개선이 미미했던 것으로 판단됩니다.
- MongoDB의 읽기 성능 특성: MongoDB는 기본적으로 비관계형 데이터 구조와 B-트리 기반의 인덱싱을 통해 읽기 성능이 우수합니다. 따라서, 저희 시스템의 경우 CQRS를 적용하기 전부터 Read API가 이미 어느 정도의 읽기 부하를 잘 처리하고 있었기 때문에, 분리 후의 성능 개선 효과가 체감되지 않았을 수 있습니다.
- Read 모델에 대한 최적화 부족: CQRS 패턴은 읽기 전용 모델을 만들고 최적화할 수 있는 기회를 제공합니다. 하지만 저희는 Read API와 Read DB를 분리하는 데 집중했고, 정작 Read DB의 인덱스를 점검하거나 쿼리 로직을 개선하는 등의 최적화 작업을 충분히 하지 못했습니다. 이로 인해 여전히 비효율적인 쿼리가 발생하여 응답 시간을 길게 만들었을 가능성이 높습니다.
결론적으로, 이번 테스트는 CQRS 패턴이 쓰기 작업의 성능과 안정성을 확보하는 데는 매우 효과적이지만, 읽기 작업의 성능을 극대화하기 위해서는 Read 모델에 대한 별도의 최적화가 필수적이라는 중요한 교훈을 주었습니다.
'공부방' 카테고리의 다른 글
| 복잡한 동적 상품 필터링, 왜 QueryDSL을 선택했나? (2) | 2025.08.13 |
|---|---|
| 인덱스의 중요성, 성능 테스트로 증명하기 (2) | 2025.08.13 |
| EC2 + Docker + GitHub Actions + Nginx + Route 53 기반 프론트 배포 & HTTPS 적용기 (0) | 2025.04.12 |
| EC2 + Docker + GitHub Actions Spring Boot 배포 회고 (0) | 2025.03.23 |
| WebSockets vs SSE (0) | 2025.02.17 |