CQRS란 Command and Query Responsibility Segregation
의 약자로, 이름과 같이 명령과 쿼리의 역할을 구분 한다는 것입니다. 즉 CRUD에서 CUD(Command)와 R(Query)의 책임을 분리한 것이 CQRS 입니다.
데이터베이스를 사용하면서 트랜젝션 ACID를 보장하기 위해 노력했습니다. 허나 MSA가 대두되고, 도메인별로 개발하고 DB를 분리하며 그 문제가 도드라지게 되었습니다.
이러한 변경 가능성과 동시성의 문제 등을 인정하고, 이를 통해서 R과 CUD를 구분함으로써 얻는 이점을 설명하는 것이 CQRS패턴입니다.
개발 중이던 SNS 서비스는 쓰기 대비 읽기 요청이 매우 많은 서비스 중 하나입니다. 더불어 MSA 구조로 개발되고 있던 만큼, 그 장점을 활용해보고자 CQRS를 적용하기로 결정했습니다.
CQRS 패턴을 통해 얻을 수 있는 이점은 다음과 같습니다.
낮은 수준의 CQRS는 Read하는 모델을 따로 만들거나, Read Replica를 만든다거나, Read DB에 Cache를 적용하는 등 다양하게 또 간단하게 적용해볼 수 있습니다.
허나 단순 모델의 분리로는 Single DB의 성능 문제를 해결하지 못했고, 읽기 전용 DB 분리는 동기화 방식의 고민이 남아있었습니다.
때문에 더 높은 수준의 CQRS 패턴은 Event Sourcing과 함께, Queue(AWS SQS, RabbitMQ, Kafka와 같은 Message Queue가 일반적)를 이용하여 비동기적으로 데이터를 쓰고 읽어오는 형태를 취합니다.
이벤트 소싱이란 Application 내의 모든 Activity를 이벤트로 전환해서 이벤트 스트림(Event Stream)을 별도의 Database에 저장하는 방식을 의미합니다.
즉, Application 내의 상태 변경을 이력으로 관리하는 패턴의 발전된 형태로 이해하면 됩니다.
기존 ORM은 변경 사항이 업데이트되면 이전의 내역은 사라졌기 때문에, 변경 내역을 관리하는 용도로는 이벤트 소싱이 더욱 적합합니다.
이벤트 소싱을 위한 저장소는 DB나 캐시를 이용해 직접 구현할 수도, Kafka 등의 메세지큐를 이용할 수도 있습니다.
이벤트 소싱의 이벤트 스트림은 오직 추가만 가능하고, 필요로 하는 시점에 구체화 단계를 가지게 되고 이 처리과정이 CQRS의 모델 분리 관점에서 잘 맞기 때문에 주로 선택합니다.
CQRS 패턴에 이벤트 소싱은 필수가 아니지만 이벤트 소싱에는 CQRS가 필요합니다.
CQRS 구조 적용 후 서비스의 아키텍쳐는 다음과 같이 구성되었습니다.
read 가 빈번한 feed 서버는 Document 기반의 DB(MongoDB와 같은 NoSQL 계열)를 사용하기로 했다.
해당 구조에 대해 알게 되고 공부하면서 가장 힘들었던 부분은 그래서 어떻게 적용하는데? 였던 것 같네요...
위에 명시해뒀듯이 저는 단순히 읽기 모델을 분리하거나, DB를 분리하는 것 만으로도 CQRS를 적용했다고 말할 수 있을 것 같습니다.
개념 자체는 크게 어렵지 않으나, 또 하나의 아키텍쳐인 만큼 적용 이후로 CRUD와는 또 다른 관리 요소들이 생겨나게 됩니다.
저 또한 어떻게 두 저장소를 동기화 할 것이고, 데이터의 일관성을 어떻게 보장할 것이며, 변경과 확장에 용이하면서도 비대해지지 않으려면 어디까지가 필요한 정보인지 계속해서 고민해야 했습니다...
때문에 여타 글에서 나오듯 CQRS는 어디까지나 하나의 방법이지, CRUD보다 개선된 구조라고는 생각하지 않고, 각각에 맞는 형태가 있으니 고려해서 적용하면 좋겠습니다.
이벤트 브로커로 kafka를 사용한 이유와 적용 방법은 다른 글에서 작성하겠습니다!
소셜 네트워크 서비스의 아키텍처에 대하여
[CQRS] CQRS 어떻게 사용할까?
[Architecture] MSA : CQRS 패턴이란
https://blog.neonkid.xyz/267
CQRS 패턴
https://www.youtube.com/watch?v=BnS6343GTkY
https://june-coder.tistory.com/32
Java의 CQRS 및 이벤트 소싱
웹서비스 백엔드 애플리케이션 아키텍처(2)-CQRS