코드를 좀 더 잘하기 여러 패턴을 알고 싶어서 여러 패턴을 보던 중 CQRS패턴에 대해서 좀 더 알고 싶어서 정리해 보았습니다.
CQRS 패턴에 대해서
서비스가 커지고 트래픽이 증가하면서, 단순한 CRUD 기반의 아키텍처로는 한계를 느끼는 경우가 많습니다. 특히 읽기 요청과 쓰기 요청의 성격과 양이 다를 때, 이를 하나의 모델로 처리하는 것은 비효율적입니다. 이러한 문제를 해결하기 위해 사용되는 대표적인 패턴이 CQRS입니다.
CQRS란?
CQRS는 Command Query Responsibility Segregation의 약자로, 명령(Command)과 조회(Query)의 책임을 분리하는 아키텍처 패턴입니다. 전통적인 시스템에서는 동일한 데이터 모델을 통해 읽기와 쓰기를 모두 처리하지만, CQRS는 이 둘을 분리하여 각각 독립적으로 최적화할 수 있도록 합니다.
- Command: 데이터를 변경하는 작업. 예를 들어, 생성, 수정, 삭제와 같은 요청입니다.
- Query: 데이터를 조회하는 작업. 예를 들어, 목록 조회, 상세 정보 보기와 같은 요청입니다.
왜 분리해야 하는가?
읽기와 쓰기는 본질적으로 다릅니다. 쓰기 작업은 트랜잭션 관리와 데이터 무결성이 중요하며, 복잡한 도메인 로직이 들어갈 수 있습니다. 반면, 읽기 작업은 빠른 응답 속도와 단순한 데이터 구조가 중요합니다.
이를 하나의 모델로 처리할 경우, 다음과 같은 문제가 발생할 수 있습니다.
- 불필요한 JOIN이나 복잡한 조회 로직이 쓰기 모델에 영향을 줌
- 단일 DB 구조로 인해 성능 병목이 발생
- 복잡한 비즈니스 로직으로 코드 유지 보수가 어려워짐
CQRS는 이러한 문제를 구조적으로 분리하여 해결합니다.
CQRS의 구조
CQRS는 일반적으로 다음과 같은 구조를 가집니다.
[Client]
|
|-- Command --> [Command Handler] --> [Domain Model] --> [Write DB]
|
|-- Query --> [Query Handler] --> [Read Model] --> [Read DB]
- Command Handler: 사용자 요청을 받아 도메인 로직을 수행하고 상태를 변경합니다.
- Query Handler: 단순히 데이터를 조회하여 반환합니다.
- Write Model: 도메인 중심의 모델로, 트랜잭션과 규칙이 중요합니다.
- Read Model: 조회 중심의 모델로, DTO 형태로 구성되며 빠른 응답을 위해 최적화됩니다.
읽기와 쓰기를 아예 별도의 DB로 구성하는 경우도 있으며, 읽기 성능 향상을 위해 캐시나 NoSQL을 활용하기도 합니다.
CQRS의 장점
1. 성능 향상
조회 요청이 많은 서비스에서는 읽기 전용 모델을 통해 성능을 높일 수 있습니다. 예를 들어, 게시판 조회 기능은 복잡한 도메인 로직이 필요 없으므로 단순한 쿼리 모델만으로 충분합니다.
2. 코드 분리
비즈니스 로직이 복잡해질수록 쓰기 모델과 읽기 모델을 하나의 객체에 억지로 우겨 넣는 것보다, 역할을 명확히 분리하는 것이 유지 보수에 유리합니다.
3. 확장성 확보
읽기와 쓰기를 서로 다른 방식으로 확장할 수 있습니다. 예를 들어, 읽기 요청이 많을 경우 Read 모델만 스케일 아웃할 수 있습니다.
단점 및 주의점
1. 구조 복잡도 증가
CQRS를 도입하면 단순 CRUD에 비해 구조가 복잡해집니다. 핸들러, 모델, DB 모두 두 세트로 나뉘기 때문에 개발 비용이 증가합니다.
2. 데이터 동기화 이슈
읽기 DB와 쓰기 DB가 분리된 경우, 동기화 시점에 따라 데이터 일관성 문제가 발생할 수 있습니다. 이를 해결하기 위해 Eventual Consistency 개념이 자주 사용됩니다.
3. 트랜잭션 처리 어려움
읽기와 쓰기가 분리되면서, 복합 트랜잭션 처리에 제약이 생깁니다. 예를 들어, 사용자가 글을 작성하고 그 글이 바로 목록에 보이길 기대할 때, 읽기 모델에 데이터가 반영되기까지 지연이 발생할 수 있습니다.
CQRS와 Event Sourcing
CQRS는 종종 Event Sourcing과 함께 사용됩니다. Event Sourcing은 상태를 직접 저장하지 않고, 모든 변경 이력을 이벤트 형태로 저장합니다. 이 이벤트를 기반으로 현재 상태를 재구성할 수 있습니다.
이 방식은 감사 로깅, 롤백, 히스토리 관리 등에 유리하지만, 구현 난이도가 높고 러닝 커브가 존재합니다.
언제 CQRS를 도입할까?
CQRS는 모든 서비스에 적합한 것은 아닙니다. 다음과 같은 조건일 때 고려해볼 수 있습니다.
- 서비스 트래픽 중 읽기 요청이 압도적으로 많을 때
- 도메인이 복잡하여 읽기/쓰기 로직을 명확히 분리하고 싶을 때
- 마이크로서비스 아키텍처에서 서비스 간 책임 분리가 필요한 경우
- 비즈니스 로직이 많고, 테스트 가능한 구조가 필요한 경우
단순한 CRUD 중심의 서비스라면 오히려 과도한 설계가 될 수 있으며, 초기에는 단일 모델로 구현하고 필요에 따라 CQRS로 분리하는 방식이 좋습니다.
실제 사용 예시
대규모 서비스에서는 다음과 같은 형태로 사용되곤 합니다.
- 게시판 서비스에서 글 등록은 Command 모델로 처리하고, 조회는 캐시 기반의 Read 모델로 처리
- 주문 처리 시스템에서 결제 완료 시점에 도메인 이벤트를 발생시키고, 이를 기반으로 배송 시스템에 반영
- 마이크로서비스 간 통신에서 이벤트 기반으로 Command와 Query를 나누어 연동
마무리
CQRS는 단순한 패턴이지만, 잘 적용하면 구조적 유연성과 성능 향상을 가져올 수 있습니다. 하지만 구조 복잡도와 트랜잭션 관리 이슈도 함께 따라오므로, 도입 여부는 서비스의 특성과 요구사항을 고려하여 신중하게 결정해야 합니다.
'아키텍쳐' 카테고리의 다른 글
| 아키텍처 [ study: 멀티 테넌시(Multi-tenancy) ] (0) | 2025.08.21 |
|---|