블로그

최범수

CQRS와 조회 전용 저장소, 그리고 카테고리-상품 관계 고민기

도메인 주도 설계를 배우면서 겪은 고민최근 도메인 주도 개발(DDD)에 관한 책을 읽으면서 여러 가지 설계에 대한 깊은 고민을 하게 되었다. 특히 관심을 끈 부분은 CQRS(Command Query Responsibility Segregation) 개념이었다. 이 글에서는 공부한 CQRS 개념과 함께, 카테고리와 상품 사이의 관계를 어떻게 효율적으로 설계할 수 있는지 정리해 보려고 한다. 쓰기 모델과 조회 모델을 분리한 이유일반적인 웹 애플리케이션은 데이터베이스 하나에서 읽기 쓰기를 모두 처리한다. 이렇게 구현해도 초반에는 큰 문제없이 잘 동작하지만, 서비스가 성장하면서 읽기(조회) 트래픽이 급증하면 다음과 같은 문제가 발생할 수 있다.읽기 요청 증가로 인한 데이터베이스 성능 저하복잡한 검색 및 필터링으로 인한 성능 한계이를 해결하기 위한 방법 중 하나가 CQRS 이다. CQRS의 핵심 개념쓰기(명령)와 읽기(쿼리)의 책임을 분리쓰기 작업은 RDB에 반영하고, 읽기는 ElasticSearch나 Redis 같은 빠른 저장소를 활용CQRS 흐름쓰기 발생: 데이터를 RDB에 저장이벤트 또는 동기화: Kafka 등을 통해 조회 전용 저장소(ElasticSearch)에 데이터 동기화조회 요청 처리: 조회 전용 저장소에서 빠르게 검색/조회이를 통해 조회 성능 향상과 시스템 부하 분산을 얻을 수 있다. 하지만 이벤트 동기화 과정에서 구현 복잡도가 늘어나고, 실시간성이 필요한 경우 동기화 지연이 단점으로 작용할 수 있다.간단 예시: CQRS 적용 시나리오 상품 생성 시:상품 정보를 RDB에 저장이벤트를 발생 (예: "상품 생성됨")이벤트 소비자가 ElasticSearch에 해당 상품 정보를 인덱싱사용자 조회 시:ElasticSearch에서 상품 목록 검색 -> 빠른 검색 결과 반환카테고리와 상품 관계 설계카테고리와 상품 간의 설계는 애플리케이션 성능, 특히 페이징 처리와 깊은 관련이 있다.데이터 조회와 성능에 큰 영향을 미치는 두 가지 대표적인 설계 방식을 자세히 살펴보자. N-1 관계 (상품 -> 카테고리 참조)N-1 관계는 상품(Product)이 특정 카테고리(Category)를 직접 참조하는 방식이다. 일반적으로 상품이 반드시 하나의 카테고리에만 속할 때 이 방식을 사용한다.@Entity public class Product { @Id private Long id; @ManyToOne @JoinColumn(name = "category_id") private Category category; }장점특정 카테고리에 속한 상품을 효율적으로 페이징 가능하다.category_id컬럼에 DB 인덱스를 설정하면 빠른 조회가 가능하다.단점카테고리에서 직접적으로 상품 목록에 접근하는 것이 어렵다.N+1 문제가 발생할 가능성이 높다.이 문제는 JPA의 Fetch Join이나 EntityGraph 같은 기법으로 해결할 수는 있으나 본 글의 내용과는 관련성이 떨어지기 때문에 자세히 다루진 않도록 하겠다.  M-N 관계 (상품 <-> 카테고리 ID로 연결)M-N 방식은 하나의 상품이 여러 개의 카테고리에 속할 때 사용하는 구조로, 중간 테이블을 사용하여 다대다 관계를 처리한다. 특히 ID기반으로 연관관계를 표현하여 직접적인 엔티티 참조를 피하는 방식이다.@Entity public class Product { @Id private Long id; @ElementCollection @CollectionTable(name = "product_category", joinColumns = @JoinColumn(name = "product_id")) @Column(name = "category_id") private Set<Long> categoryIds; }장점엔티티 간 직접적인 양방향 참조 없이 ID로만 관계를 맺어 결합도를 낮춘다.데이터가 많은 때도 페이징 및 필터링을 DB에서 효과적으로 처리할 수 있다.단점특정 카테고리에 속한 상품을 조회하려면 추가적인 쿼리가 필요하다.카테고리와 상품 정보를 함께 조회할 경우 성능 저하가 발생할 수 있다. 어떤 방식을 선택해야 할까?관계를 설계할 때는 애플리케이션의 사용 패턴과 비즈니스 요구사항을 깊게 고민해야 한다. 각각의 관계 방식은 다음과 같은 상황에 효과적이다.N-1 방식상품이 주로 하나의 카테고리에 속하는 경우상품 중심으로 자주 조회하는 기능이 필요할 때조회 성능 및 빠른 페이징이 중요한 경우M-N 방식하나의 상품이 여러 카테고리에 동시에 속할 수 있는 경우다양한 조건으로 필터링하거나 복잡한 조회가 필요한 경우많은 데이터를 효율적으로 처리해야 할 경우 글을 마무리하며이 글을 작성하면서 설계 방식 하나를 선택하는 일이 얼마나 중요한 고민인지 새삼 느끼게 되었다.이론적으로는 두 방식 모두 장단점이 있기 때문에 어느 하나가 더 우월하다 말할 수는 없겠지만, 결국 실제 서비스에서 어떤 방식이 더 효율적일지는 요구사항을 고려하여 더 효율적인 선택을 하는건 개발자의 몫인 것 같다.앞으로도 단순히 이론에 그치지 않고 실무에서 끊임없이 고민하며 상황에 맞는 좋은 설계 방식을 찾아가야겠다는 생각이 들었으며 끝으로 이 글의 내용과 비슷한 고민을 하고 있는 사람들에게 조금이나마 도움이 되었기를 바란다.

백엔드CQRSManyToOneManyToMany조회전용저장소

채널톡 아이콘