Command Bus는 반드시 비동기를 지원해야할까?

배경
CQRS(Command Query Responsibility Segregation) Pattern은 명령(Command)과 조회(Query)를 분리하여 각각 독립적으로 최적화할 수 있는 강력한 아키텍처 패턴이다. 이를 통해 시스템의 확장성과 유지보수성을 높일 수 있다.
CQRS에서 명령을 처리하는 과정에서 Command Bus
라는 개념이 등장하는데, 많은 자료에서 이를 비동기 방식으로 처리하는 예시를 주로 다루고 있다. 그러나 반드시 비동기 처리가 요구되는 것은 아니며, 오히려 동기 방식을 기본으로 시작하는 것이 더 적절할 수 있다.
이번 글에서는 Command Bus의 동기 및 비동기 방식에 대한 선택 기준을 알아보고, 어떤 경우에 비동기 방식을 도입하는 것이 적절한지에 대해 다뤄보려한다.
반드시 비동기를 지원해야하는가?
CQRS 패턴에서 Command Bus가 반드시 비동기로 동작해야 하는 것은 아니다.
실제로 많은 시스템에서 Command Bus는 기본적으로 동기적으로 동작하며, 필요에 따라 비동기 방식으로 전환하는 것이 더 바람직하다.
반 버논(Vaughn Vernon)의 Implementing Domain-Driven Design
책에서는 다음과 같이 언급된다.
"커맨드를 비동기식 메시지로 보내며, 이는 전용 스타일로 설계된 핸들러로 전달된다. 이는 각 커맨드 처리기 컴포넌트가 특정 타입의 메시지를 받도록 할 뿐만 아니라, 커맨드 프로세싱 부하를 처리할 수 있도록 주어진 타입의 처리기를 추가할 수 있다. 그러나 이 접근법에는 좀 더 복잡한 설계가 필요하기 때문에 이를 기본으로 사용하면 안 된다. 일단은 동기식 커맨드 처리기로 선택해 시작하자. 확장성(Scalability)의 요구가 있을 때에만 비동기식으로 변경하자."
즉, 비동기 처리는 기본값이 아니라 필요할 때 적용하는 것이 적절하다.
그렇다면 기본적으로 동기 방식이 적절한 이유는 무엇일까?
일관성 유지가 용이하다
동기적으로 커맨드를 처리하면, 명령 실행 후 즉시 조회 모델이 최신 상태로 유지될 가능성이 높아진다.
이는 사용자가 커맨드 실행 후 변경 사항을 즉시 확인할 수 있음을 의미한다.
- 예를들어, 고객이 쇼핑몰에서 주문하면 주문 정보가 즉시 반영되어 보여주기를 기대한다. 만약 비동기 방식이라면, 메세지 큐에 의해 주문이 나중에 처리될 수 있으므로 즉시 조회했을 때 아직 데이터가 반영되지 않았을 가능성이 존재한다.
트랜잭션 관리가 단순하다
동기 방식에서는 하나의 트랜잭션 내에서 명령을 실행하고 필요 시 쉽게 롤백할 수 있다.
- 예를들어, 은행 계좌 이체를 수행할 때 하나의 트랜잭션으로 송금과 입금을 처리하면, 실패 시 쉽게 복구할 수 있다.
반면 비동기 시스템에서는 비동기 방식에서는 분산 트랜잭션(Distributed Transaction) 처리가 필요해지는 복잡성이 초래된다.
디버깅이 용이하다
동기 방식에서는 문제가 발생한 시점과 원인을 즉시 파악할 수 있다. 요청을 보낸 후 응답이 즉시 오기 때문에, 에러 발생 시 트레이스를 쉽게 따라갈 수 있다.
반면 비동기 시스템에서는 메시지 지연이나 실패 시 추적이 어려울 수 있다.
그럼 언제 비동기 방식을 고려해야할까?
확장성이 요구될 때
사용자가 많아지면 동기 방식의 커맨드 처리는 서버 부하를 증가시키고, 응답 속도가 느려질 수 있다.
커맨드 처리를 메세징 서비스(e.g. Kafka)를 통해 비동기로 분산하면 수평 확장이 쉬워지고, 부하를 분산할 수 있다.
고성능이 요구되는 시스템일 때
대량의 트래픽을 처리하는 시스템에서는 동기 방식으로 인해 성능 병목(Bottleneck)이 발생할 수 있다.
- 예를들어, 실시간 데이터 스트리밍이 필요한 시스템에서는 빠른 응답을 위해 비동기 방식이 적절할 수 있다. 대규모 로그 처리 시스테에서 Kafka와 같은 서비스를 이용하여 이벤트를 비동기로 수집하는 사례까 대표적이다.
즉각적인 응답이 필요하지 않은 경우
- 일부 도메인에서는 명령 실행 후 즉시 결과를 제공할 필요없는 경우가 있다.
위 케이스 외에도 더 다양한 트레이드 오프가 존재할것이라 생각한다. 만약 비동기로 명령을 처리하고 프론트쪽에서는 폴링 기법을 활용하여 처리가 완료될 때 까지 조회를 진행하는 방식을 채택할 수도 있다.
CQRS Pattern는 다양한 비지니스 요구사항을 만족할 수 있는 좋은 기법이라 생각하지만, 이번글에서 다룬 명령 처리 부분 외에도 읽기 모델(Read Model)을 가공하는 부분에서 대게 결과적 일관성 방식으로 구현되어 지연(Lag)과 실패에 대한 핸들링이 필요해진다.
Subscribe to my newsletter
Read articles from Jeongkyun An directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
