글 목록으로 돌아가기

CloudNative

CQRS와 읽기 · 쓰기 · 책임의 분리

CQRS의 개념과 기존 데이터 모델의 문제점, Command와 Query 분리 구조 및 적용 기준

Dohyeon Kim
Dohyeon Kim 2026년 8월 12일 · 6분 읽기
CloudNative AutoEverSW CQRS

1 ) CQRS


CQRS란

CQRS(Command Query Responsibility Segregation)는 데이터를 변경하는 작업과 조회하는 작업의 책임을 분리하는 패턴이다.

여기서 애플리케이션의 요청을 크게 두 가지로 구분한다.

구분 역할
Command 데이터의 상태를 변경한다.
Query 데이터를 조회한다.

즉, 하나의 모델에서 데이터의 조회와 변경을 모두 담당하는 대신 쓰기(Command)와 읽기(Query)를 서로 다른 책임으로 분리한다.

                  ┌→ Command → 데이터 변경
Client ───────────┤
                  └→ Query   → 데이터 조회

CQRS를 적용하면 읽기와 쓰기를 각각의 요구사항에 맞게 구성할 수 있으므로 성능, 확장성, 보안 등의 측면에서 서로 다른 전략을 적용할 수 있다.

전통적인 구조의 문제점

일반적인 CRUD 애플리케이션에서는 하나의 데이터 모델을 이용하여 데이터의 생성, 조회, 수정, 삭제를 모두 처리할 수 있다.

                하나의 Model
                     │
        ┌────────────┼────────────┐
        ↓            ↓            ↓
      Create       Read      Update / Delete
                     │
                 Database

단순한 애플리케이션에서는 이러한 구조가 오히려 이해하고 구현하기 쉽다.

하지만 애플리케이션과 비즈니스 로직이 복잡해지면 읽기와 쓰기가 하나의 모델에 서로 다른 요구사항을 요구하는 문제가 발생할 수 있다.

읽기와 쓰기가 요구하는 형태의 차이

데이터를 조회할 때는 사용자에게 필요한 형태에 따라 서로 다른 DTO(Data Transfer Object)를 반환해야 할 수 있다.

반면 데이터를 생성하거나 수정할 때는 단순한 데이터 저장뿐 아니라 다음과 같은 처리가 필요할 수 있다.

입력
 ↓
유효성 검사
 ↓
비즈니스 로직
 ↓
데이터 변경

하나의 모델이 이러한 읽기와 쓰기의 요구사항을 모두 처리하기 시작하면 모델의 역할과 구조가 복잡해질 수 있다.

읽기와 쓰기의 부하 차이

일반적으로 읽기와 쓰기의 요청량이나 필요한 성능이 항상 동일하지는 않다.

예를 들어 조회 요청이 쓰기 요청보다 훨씬 많은 서비스라면 읽기 영역에 더 많은 자원이 필요할 수 있다.

하지만 읽기와 쓰기가 하나의 구조에 강하게 묶여 있으면 각각을 독립적으로 최적화하거나 확장하기 어려울 수 있다.

불필요한 데이터 처리

읽기와 쓰기에서 사용하는 데이터 표현이 서로 일치하지 않을 수도 있다.

하나의 모델을 공통으로 사용하면 특정 작업에는 필요하지 않은 Column이나 속성까지 함께 처리해야 하는 상황이 발생할 수 있다.

데이터 경합

여러 사용자가 동일한 데이터에 대해 병렬로 작업하면 데이터 경합이 발생할 수 있다.

특히 하나의 데이터 모델과 저장소에 조회와 변경 요청이 집중되면 복잡한 동시성 처리가 필요해질 수 있다.

복잡한 조회

사용자가 요구하는 형태의 데이터를 만들기 위해 여러 Table을 대상으로 복잡한 Query를 수행해야 할 수도 있다.

Table A ─┐
Table B ─┼→ JOIN / 복잡한 Query → 조회 결과
Table C ─┘

조회 요구사항이 복잡해질수록 Query의 복잡도가 증가하고 성능에도 영향을 줄 수 있다.

정리

전통적인 방식 자체가 문제가 되는 것은 아니다.

하지만 시스템이 복잡해지면서 읽기와 쓰기가 서로 다른 데이터 형태, 성능, 확장 방식과 비즈니스 로직을 요구하기 시작하면 하나의 모델이 너무 많은 책임을 담당하게 될 수 있다.

2 )Command와 Query의 분리


CQRS에서는 이러한 문제를 해결하기 위해 읽기와 쓰기에 서로 다른 모델을 사용한다.

                     Application
                          │
              ┌───────────┴───────────┐
              ↓                       ↓
        Command Model             Query Model
              ↓                       ↓
         데이터 변경                데이터 조회

Command

Command는 시스템의 상태를 변경하기 위한 요청이다.

단순히 데이터를 중심으로 표현하기보다 어떤 작업을 수행할 것인지를 중심으로 구성한다.

데이터 중심
→ Book 데이터 변경

작업 중심
→ 도서 등록
→ 도서 가격 변경
→ 도서 삭제

강의에서 다루는 Kafka 기반 구조처럼 Command를 Queue에 전달하여 비동기적으로 처리하는 방식도 사용할 수 있다.

Command
   ↓
Queue
   ↓
Command 처리
   ↓
데이터 변경

이를 통해 요청을 전달하는 부분과 실제 작업을 처리하는 부분을 분리할 수 있다.

Query

Query는 데이터를 조회하기 위한 요청이다.

Query는 데이터베이스의 상태를 변경하지 않고 조회 결과를 반환한다.

Query
  ↓
Read Model
  ↓
DTO
  ↓
Client

조회에 필요한 형태에 맞는 DTO를 구성함으로써 쓰기 모델의 복잡한 비즈니스 로직과 조회 로직을 분리할 수 있다.

저장소까지 분리하는 구조

CQRS의 핵심은 Command와 Query의 책임과 모델을 분리하는 것이다.

필요한 경우 여기서 더 나아가 읽기와 쓰기의 저장소 자체를 분리할 수도 있다.

Command
   ↓
Write Model
   ↓
Write Database


Query
   ↓
Read Model
   ↓
Read Database

이렇게 구성하면 각각의 작업에 적합한 데이터베이스와 데이터 구조를 선택할 수 있다.

다음 실습에서는 이 구조를 이용하여 쓰기 저장소와 읽기 저장소를 서로 다른 데이터베이스로 구성한다.

3 ) CQRS의 장점


읽기와 쓰기의 독립적인 확장

읽기와 쓰기의 부하가 서로 다르다면 각 영역을 독립적으로 확장할 수 있다.

Read 요청 증가
     ↓
Read 영역 확장

Write 요청 증가
     ↓
Write 영역 확장

전체 시스템을 동일한 비율로 확장하지 않고 필요한 부분에 맞게 자원을 조정할 수 있다.

작업에 최적화된 데이터 구조

읽기 저장소와 쓰기 저장소를 분리하면 각 작업에 적합한 Schema를 구성할 수 있다.

Write Model

데이터 변경과 비즈니스 로직 처리에 적합한 구조를 사용할 수 있다.

Read Model

조회에 적합한 구조를 구성하여 복잡한 Join을 줄일 수 있다.

예를 들어 읽기에 최적화된 Materialized View와 같은 형태를 이용하면 미리 구성된 데이터를 조회하여 복잡한 Query를 줄일 수 있다.

관심사의 분리

복잡한 비즈니스 로직은 주로 쓰기 모델에서 처리하고 읽기 모델은 상대적으로 단순한 조회에 집중하도록 구성할 수 있다.

Command Model
→ Validation
→ Business Logic
→ 데이터 변경

Query Model
→ 데이터 조회
→ DTO 반환

각 모델의 책임이 명확해지므로 하나의 모델이 읽기와 쓰기의 모든 요구사항을 처리하는 문제를 줄일 수 있다.

보안

Command와 Query의 진입점과 책임이 분리되므로 데이터 변경 작업에 대해 별도의 권한이나 보안 정책을 적용하는 구조를 구성할 수 있다.

4 ) CQRS 적용 시 고려할 문제


CQRS는 읽기와 쓰기를 분리함으로써 여러 장점을 얻을 수 있지만 시스템 구조 자체는 기존 CRUD 방식보다 복잡해질 수 있다.

구조의 복잡성

단순했던 하나의 모델을 Command와 Query로 분리해야 하므로 관리해야 하는 구성 요소가 증가한다.

특히 Event Sourcing과 같은 패턴까지 함께 적용하면 전체 구조의 복잡성이 더욱 증가할 수 있다.

따라서 CQRS가 항상 기존 CRUD 방식보다 좋은 것은 아니다.

Messaging 처리

Command 처리 이후 Event를 발행하여 다른 시스템의 데이터를 갱신하는 구조에서는 Messaging을 사용하게 될 수 있다.

Command
   ↓
Write 처리
   ↓
Event 발행
   ↓
Message Broker
   ↓
Read Model 갱신

이 경우 애플리케이션에서는 정상적인 Message 처리뿐 아니라 Message 전송 실패나 중복 Message와 같은 상황도 고려해야 한다.

데이터 일관성

읽기와 쓰기의 저장소를 분리하고 Message를 통해 데이터를 전달하면 두 저장소가 항상 같은 순간에 갱신되는 것은 아니다.

Write Database 갱신
        ↓
    Event 전달
        ↓
Read Database 갱신

따라서 Write Database의 변경 사항이 Read Database에 반영되기 전까지 일시적으로 두 데이터의 상태가 다를 수 있다.

CQRS 구조를 설계할 때에는 이러한 데이터 일관성 문제를 함께 고려해야 한다.

ORM 사용의 제약

강의에서는 CQRS 적용 시 읽기와 쓰기의 모델을 서로 다른 목적에 맞게 구성하므로, 단일 데이터베이스 Schema를 기준으로 ORM을 통해 전체 모델을 단순하게 자동 생성하는 방식과는 맞지 않을 수 있다는 점을 다루었다.

각 모델이 자신의 목적에 맞는 구조를 가지기 때문에 모델과 데이터 구조를 별도로 설계해야 할 수 있다.

5 ) CQRS를 적용할 수 있는 경우


CQRS는 시스템의 복잡성이 증가하는 대신 읽기와 쓰기를 독립적으로 구성할 수 있다는 장점을 얻는 패턴이다.

따라서 이러한 분리가 실제로 필요한 상황에서 사용하는 것이 적절하다.

다음과 같은 경우 적용을 고려할 수 있다.

  • 많은 사용자가 동일한 데이터에 병렬로 접근하는 경우
  • 복잡한 프로세스나 Domain Model을 기반으로 작업을 처리하는 경우
  • 읽기 성능과 쓰기 성능을 서로 독립적으로 조정해야 하는 경우
  • 읽기와 쓰기를 명확하게 분리하여 구현하려는 경우
  • 하나의 시스템이 장기간 운영되면서 여러 Version을 가지며 지속적으로 변경되는 경우
  • 다른 시스템과의 통합이 필요한 경우

반대로 다음과 같은 경우에는 CQRS로 인한 구조적 복잡성이 더 큰 부담이 될 수 있다.

  • Domain이나 Business Logic이 단순한 경우
  • 단순한 CRUD 중심의 애플리케이션
단순한 CRUD
→ 기존 구조가 적합할 수 있음

복잡한 Domain
+ 읽기/쓰기 요구사항 차이
+ 독립적인 확장 필요
→ CQRS 적용 고려

정리

  • CQRS (Command Query Responsibility Segregation)는 데이터 변경을 담당하는 Command와 데이터 조회를 담당하는 Query의 책임을 분리하는 패턴이다.

  • 전통적인 구조에서는 읽기와 쓰기가 하나의 데이터 모델을 공유하므로, 시스템이 복잡해질수록 하나의 모델이 조회와 변경에 필요한 서로 다른 요구사항을 모두 담당하게 될 수 있다.

  • 읽기와 쓰기는 요구되는 데이터 형태와 부하가 서로 다를 수 있으며, 동일한 데이터에 병렬로 접근할 경우 데이터 경합이 발생할 수도 있다.

  • CQRS에서는 Command Model과 Query Model을 분리하여 각각의 책임에 집중하도록 구성한다.

  • Command는 데이터 자체보다 수행할 작업을 중심으로 구성하며, Message Queue 등을 이용하여 비동기적으로 처리할 수 있다.

  • Query는 데이터를 변경하지 않고 조회 결과를 DTO 등의 형태로 반환한다.

  • Write Database와 Read Database를 분리하면 각각의 작업에 적합한 데이터 스키마와 저장 기술을 사용할 수 있다.

  • 읽기 저장소에서는 Materialized View 등을 활용하여 복잡한 조회를 단순화할 수 있으며, 읽기와 쓰기를 독립적으로 확장하고 최적화할 수 있다.

  • CQRS는 성능과 확장성, 보안, 관심사 분리 측면에서 장점이 있지만 구조 자체의 복잡성이 증가한다.

  • Messaging을 함께 사용하는 경우 Message 처리 실패, 중복 Message, 데이터 일관성 등의 문제를 고려해야 한다.

  • CQRS는 복잡한 Domain이나 읽기와 쓰기의 요구사항이 크게 다른 시스템에서 고려할 수 있으며, 단순 CRUD 애플리케이션에서는 불필요한 복잡성을 만들 수 있다.

댓글