5. IT기술노트/인프라&개발

CQRS 개념과 Read-Your-Own-Writes 해결 전략

쿼드큐브 2026. 2. 9. 18:35
반응형
반응형

 

CQRS 개념과 Read-Your-Own-Writes 해결 전략

 

CQRS 개념 삽화 이미지
CQRS 개념 삽화 이미지

 

1. CQRS란 무엇인가 - CRUD와 무엇이 다른가

CQRS(Command Query Responsibility Segregation)는 시스템에서 데이터를 변경하는 작업(Command) 과 데이터를 조회하는 작업(Query)의 책임을 분리하는 아키텍처 패턴입니다.

 

일반적인 CRUD 구조에서는 하나의 모델이 생성(Create), 조회(Read), 수정(Update), 삭제(Delete)를 모두 담당합니다.

초기에는 구현이 단순하고 이해하기 쉽지만, 서비스 규모가 커질수록 몇 가지 어려움이 나타날 수 있습니다

 

예를 들어 다음과 같은 상황을 생각해 볼 수 있습니다.
▸ 읽기 요청이 쓰기 요청보다 훨씬 많아집니다.

▸ 목록, 통계, 검색 등 조회 요구가 점점 복잡해집니다.

▸ 캐시나 검색엔진을 도입하려 할 때 기존 모델이 적합하지 않은 경우가 생깁니다.

 

이처럼 쓰기와 읽기의 요구사항이 다르기 때문에 하나의 모델로 모두 해결하려 하면 구조가 점점 복잡해질 수 있습니다.

CQRS는 이러한 문제를 해결하기 위해 다음과 같은 관점으로 접근합니다.
▸ 쓰기 모델은 도메인 규칙과 데이터 무결성에 집중합니다.
▸ 읽기 모델은 조회 성능과 응답 속도에 집중합니다.

이렇게 책임을 분리하면 각 목적에 맞는 구조를 설계할 수 있게 됩니다.

 

✔️ CQRS와 CRUD 비교

구분 CRUD CQRS
구조 하나의 모델이 생성, 조회, 수정, 삭제를 모두 처리합니다. 쓰기(Command)와 조회(Query)의 모델을 분리합니다.
설계 목적 단순한 구현과 빠른 개발에 유리합니다. 읽기 성능 최적화와 확장성에 유리합니다.
조회 처리 동일한 테이블과 모델을 사용하여 조회합니다. 조회 전용 모델이나 저장소를 사용할 수 있습니다.
복잡도 구조가 단순합니다. 구조가 상대적으로 복잡해질 수 있습니다.
적합한 경우 소규모 서비스나 단순한 비즈니스 로직에 적합합니다. 조회가 많거나 도메인이 복잡한 서비스에 적합합니다.

 

2. CQRS 아키텍처 구조와 동작 방식

CQRS 구조는 반드시 복잡하게 시작할 필요는 없으며, 비교적 단순한 형태부터 점진적으로 발전시킬 수 있습니다.

 

✔️ 가장 기본적인 흐름은 다음과 같습니다.
1. 사용자의 요청이 Command API로 들어옵니다.
2. 서버는 비즈니스 로직을 처리하고 Write DB에 저장합니다.
3. 이후 이벤트나 복제 과정을 통해 Read DB가 갱신됩니다.
4. 조회 요청은 Query API를 통해 Read DB에서 처리됩니다.

 

구조를 간단히 표현하면 다음과 같습니다

Client ├── Command API → Write DB 
       └── Query API → Read DB

여기서 중요한 개념이 Eventual Consistency(최종 일관성) 입니다.

Write DB에 반영된 내용이 Read DB에 즉시 반영되지 않을 수 있지만, 일정 시간이 지나면 결국 동일한 상태에 도달하게 됩니다.

 

✔️ 다음과 같은 구조로 구현하는 경우도 많습니다.

Command 처리 
   ↓ 
Write DB 저장 
   ↓ 
이벤트 생성 
   ↓ 
메시지 브로커 전달 
   ↓ 
Projection Worker 
   ↓ 
Read DB 갱신

 

예를 들어 주문 목록을 빠르게 조회하기 위해 다음과 같은 조회 전용 테이블을 구성할 수 있습니다.

order_view 
  - order_id 
  - user_name 
  - total_price 
  - item_count 
  - created_at

이러한 구조를 사용하면 여러 테이블을 조인하지 않고도 빠르게 목록을 조회할 수 있습니다.

반응형

 

3. Write DB와 Read DB 동기화 문제와 해결 전략

CQRS를 실제 서비스에 적용할 때 가장 자주 마주하는 상황이 있습니다.

 

예를 들어 사용자가 상품을 구매한 직후 주문 목록을 조회했는데, 방금 주문한 항목이 보이지 않는 경우입니다.
이 문제는 Write DB와 Read DB 사이에 동기화 지연이 존재하기 때문에 발생합니다.

 

이러한 문제를 해결하기 위해 다음과 같은 방법을 많이 사용합니다.

 

이러한 방법들은 모두 Read-Your-Own-Writes 라는 원칙을 만족시키기 위한 접근입니다.
즉, 사용자가 직접 수행한 작업의 결과는 바로 확인할 수 있어야 한다는 사용자 경험 관점의 설계 원칙입니다.

 

📌 Read-Your-Own-Writes는

분산 시스템 환경에서 "내가 방금 쓴(Write) 데이터는 내가 다시 읽었을 때(Read) 반드시 최신 상태로 보여야 한다"는 원칙입니다.

 

🔷 Primary Fallback 조회

가장 널리 사용되는 방법입니다.
구매 직후 일정 시간 동안은 Read DB가 아니라 Write DB를 조회하도록 하는 방식입니다.

 

예를 들어 다음과 같은 흐름을 구성할 수 있습니다.

POST /orders → Write DB 저장 
GET /orders (짧은 시간 내) → Write DB 조회 
GET /orders (이후) → Read DB 조회

구현이 비교적 단순하고 안정적이기 때문에 많은 서비스에서 기본 전략으로 사용하고 있습니다.

 

🔷 Optimistic UI

구매 요청이 성공하면 실제 조회 결과를 기다리지 않고 화면에 먼저 반영하는 방식입니다.

 

예를 들어 다음과 같은 흐름이 가능합니다.
1. 주문 생성 성공 응답을 받습니다.
2. 클라이언트에서 목록에 항목을 먼저 추가합니다.
3. 이후 실제 조회 결과와 동기화합니다.


이 방식은 사용자 경험을 개선하는 데 매우 효과적이며, 프론트엔드 애플리케이션에서 널리 사용되고 있습니다.

 

🔷 Write-through Cache

쓰기 작업이 발생할 때 캐시에도 동시에 반영하는 방식입니다.


구조 예시는 다음과 같습니다.

Write DB 저장
    ↓ 
Redis 업데이트 
    ↓ 
조회 시 Redis 우선 조회

알림 목록, 활동 피드, 주문 목록처럼 즉시성이 중요한 데이터에서 자주 사용되는 방법입니다.

 

🔷 Polling 또는 상태 기반 조회

처리 완료까지 시간이 필요한 작업에서는 상태 값을 기반으로 반복 조회하는 방식을 사용할 수 있습니다.

 

예를 들어 다음과 같은 흐름입니다.

POST /orders → status: PROCESSING 
GET /orders → 일정 시간 간격 조회 
status: COMPLETED

이 방식은 처리 중인 상태를 사용자에게 명확히 보여줄 수 있다는 장점이 있습니다.

 

4. CQRS를 적용할 때의 현실적인 가이드

CQRS는 매우 유용한 패턴이지만 모든 시스템에 반드시 필요한 것은 아닙니다.


✔️ 다음과 같은 경우에 특히 효과적입니다.
▸ 읽기 트래픽이 쓰기보다 훨씬 많은 경우
▸ 복잡한 조회나 집계가 많은 경우
▸ 검색이나 캐시 요구가 많은 서비스
▸ 도메인 로직이 복잡한 시스템

 

✔️ 반대로 다음과 같은 경우에는 단순한 구조가 더 적합할 수 있습니다.
▸ 초기 단계의 서비스
▸ 트래픽이 많지 않은 내부 시스템
▸ 조회 구조가 단순한 경우

 

✔️ 보통 다음과 같은 단계로 점진적으로 적용합니다.
▸ 1단계 : Command와 Query 코드 구조만 분리합니다.
▸ 2단계 : 조회 전용 DTO 또는 View 모델을 분리합니다.
▸ 3단계 : 읽기 전용 Replica DB를 도입합니다.
▸ 4단계 : 이벤트 기반 Projection을 도입합니다.

 

✔️ 또한 운영 단계에서는 다음과 같은 요소를 반드시 고려하는 것이 좋습니다.
▸ Read DB 반영 지연(Lag) 모니터링
▸ Projection 재생성 기능
▸ 이벤트 처리 실패에 대한 재시도 전략
▸ 장애 발생 시 Read 모델 재구성 절차


※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.

반응형

 

반응형