Backend Deep Dive #2: NoSQL 선택 가이드와 분산 트랜잭션의 해법, Saga 패턴

backendnosqlmongodbcassandrasaga-patternmsa

Backend Deep Dive.
그 두 번째, 오늘은 수많은 NoSQL 데이터베이스 중 어떤 것을 선택해야 하는지, 그리고 MSA 환경에서 발생하는 분산 트랜잭션 문제를 해결하는 Saga 패턴에 대해 알아본다.

1. 어떤 NoSQL DB를 선택해야 할까?

NoSQL이 등장한 이유

처음부터 왜 NoSQL이 등장했는지를 이해하는 것이 중요하다.
RDBMS는 수십 년간 가장 널리 쓰인 데이터베이스지만, 현대적인 웹 서비스의 요구사항을 만족시키기에는 몇 가지 한계가 있었다.

  1. 확장성의 한계

    • RDB는 고사양 단일 서버 기반의 수직 확장(Scale-Up)에 의존한다.
    • 이는 트래픽이 폭발적으로 증가하는 현대 서비스 환경에서는 물리적·금전적 한계에 직면하게 된다.
    • 반면 NoSQL은 수평 확장(Scale-Out)이 가능하여, 노드를 추가함으로써 선형적인 확장을 실현할 수 있다.
  2. 스키마 유연성

    • RDB는 고정된 스키마 기반으로 설계된다. 컬럼 추가나 구조 변경 시 마이그레이션 작업이 필요하다.
    • NoSQL은 스키마리스 구조를 통해 다양한 구조의 데이터를 유연하게 처리할 수 있다.
  3. 고가용성과 분산 시스템 적합성

    • RDB는 강한 일관성(Consistency)을 보장하지만, 네트워크 장애나 분산 환경에서는 가용성을 확보하기 어렵다.
    • NoSQL은 CAP 이론을 기반으로 하여, 시스템 성격에 따라 일관성(CP) 또는 가용성(AP)을 우선하는 구조를 선택할 수 있다.
  4. 특화된 목적에 최적화된 설계

    • NoSQL은 다양한 유형으로 분화되어 있으며, 사용 목적에 따라 적절한 형태를 선택할 수 있다.

      유형특징예시
      Document StoreJSON 기반 문서 저장MongoDB, Couchbase
      Key-Value Store매우 빠른 단순 조회Redis, DynamoDB
      Column Store쓰기·분석에 강함Cassandra, HBase
      Graph DB관계 탐색에 특화Neo4j, ArangoDB

"NoSQL"은 "No SQL"이 아니다 — Not Only SQL

NoSQL이라는 이름은 자칫 "SQL을 배제하겠다"는 뜻처럼 들리지만, 정확히는 "Not Only SQL", 즉 SQL만으로는 부족한 문제를 해결하겠다는 철학을 담고 있다.

  • 대부분의 NoSQL DB는 SQL과 유사한 쿼리 기능 또는 DSL을 지원한다.
    • MongoDB → 복잡한 필터 쿼리, Aggregation
    • Cassandra → CQL(Cassandra Query Language)
  • 즉, NoSQL은 기존 RDBMS를 대체한다기보다 보완하거나 목적에 맞게 함께 사용되는 경우가 많다.

요약하자면, NoSQL은 RDB가 못 하는 것을 보완하는 도구 상자의 확장이다.

그중 대표적인 두 가지인 MongoDBCassandra를 통해 선택 기준을 살펴보자.

MongoDB: 유연한 문서 보관함

MongoDB는 문서 지향(Document-Oriented) 데이터베이스다.
JSON과 유사한 형태의 데이터를 문서로 저장하고, 이를 컬렉션 단위로 관리한다. 마치 다양한 종류의 서류를 서류철에 정리해두는 것과 비슷하다.

특징

  • 유연한 스키마: 다양한 구조의 데이터를 하나의 문서에 담을 수 있음
  • 강력한 쿼리 기능: 다양한 필드에 대한 인덱싱과 복잡한 조회(Aggregation)를 지원하여 범용성이 높음
  • 아키텍처: Primary(쓰기를 담당)-Replica(Primary 노드 복제) 구조
  • 확장성: 단일 서버의 CPU나 RAM 성능을 높이는 수직 확장 (Scale-Up)에 유리
  • CAP 이론: CP (일관성 우선) -> 데이터 정합성을 중요하게 생각한다.

MongoDB가 적합한 경우

  • 스키마가 자주 변경되는 초기 프로젝트
  • 블로그, 커머스 등 복합적인 문서 단위로 데이터를 관리해야 하는 웹 애플리케이션

Cassandra: 초고속 쓰기 전문 창고

Cassandra는 와이드 컬럼 스토어(Wide Column Store) 이다.
Key-Value 저장 방식에서 한 단계 발전하여, 하나의 키에 수많은 컬럼을 동적으로 추가할 수 있는 구조다. 거대한 창고에 물건을 빠르게 쌓고(Write), 송장 번호(Key)로 즉시 찾는(Read) 그림을 연상하면 된다.

특징

  • 압도적인 쓰기 성능: 대규모 쓰기 작업을 빠르고 안정적으로 처리하는 데 특화
  • 높은 가용성: 모든 노드가 동등한 역할을 하는 Masterless 구조, 장애 시에도 운영 가능
  • 확장성: 서버 노드를 계속 추가하는 수평적 확장(Scale-Out)에 최적화되어 있어, 이론상 무한한 확장이 가능
  • 제한적 쿼리: Partition Key 기반 검색 최적화 (복잡한 조건의 검색에는 불리)
  • CAP 이론: AP (가용성 우선) -> 어떤 상황에서도 데이터를 쓰고 읽을 수 있는 능력을 우선시한다.

Cassandra가 적합한 경우

  • IoT, 실시간 채팅, 시계열 로그 등 끊임없이 들어오는 데이터
  • 중단 없는 시스템 운영이 필수적인 서비스

현실적인 선택의 기준

기술적 특성 외에도 현실적으로 다음 세 가지를 반드시 고려해야 한다.

  • Read vs. Write 비율: 우리 서비스는 읽기 작업이 많은가, 쓰기 작업이 많은가? 복잡한 조건으로 데이터를 읽는(Read) 작업이 많다면 MongoDB가 유리하고, 대규모의 데이터를 끊임없이 쓰는(Write) 작업이 많다면 Cassandra가 압도적으로 유리하다.

  • 확장 방식과 비용: MongoDB의 수직적 확장은 한 대의 서버를 고사양으로 업그레이드하는 방식이다. 초기에는 단순하지만, 일정 수준을 넘어가면 비용이 기하급수적으로 증가할 수 있다. 반면 Cassandra의 수평적 확장은 상대적으로 저렴한 서버 여러 대를 추가하는 방식이다. 초기 구성은 더 복잡하지만, 예측 가능한 비용으로 선형적인 확장이 가능하다.

  • 운영 및 관리 인력: 우리 팀이 이 기술을 잘 다룰 수 있는가? MongoDB는 상대적으로 구조가 단순하고 커뮤니티가 활발하여 운영 난이도가 낮은 편이다. 하지만 Cassandra는 분산 시스템에 대한 깊은 이해가 필요하며, 클러스터링, 장애 대응 등 운영 난이도가 훨씬 높다.

항목MongoDBCassandra
데이터 모델문서(Document)와이드 컬럼(Wide Column)
강점유연한 스키마, 쿼리 범용성쓰기 성능, 고가용성
확장 방식수직 확장 (Scale-Up)수평 확장 (Scale-Out)
CAP 속성CP (일관성 우선)AP (가용성 우선)
적합한 서비스웹 앱, CMS, 블로그IoT, 메시징, 로그 수집 등

2. 분산 트랜잭션의 해법, Saga 패턴

마이크로서비스 아키텍처(MSA)에서는 각 서비스가 독립된 데이터베이스를 가진다. 여기서 문제가 발생한다.

"사용자가 상품을 주문하면, '주문 서비스'는 주문을 생성하고, '결제 서비스'는 돈을 처리하며, '재고 서비스'는 재고를 줄여야 한다. 만약 결제는 성공했는데 재고가 없다면 어떻게 해야 할까?"

하나의 데이터베이스라면 '트랜잭션'으로 묶어 모두 성공하거나 모두 실패(Rollback)시킬 수 있지만, 여러 DB에 흩어져 있는 작업을 하나로 묶을 방법이 없다. 이 문제를 해결하기 위한 설계 방식이 바로 Saga 패턴이다.

Saga란 무엇인가?

전통적인 데이터베이스 환경에서는 ACID 트랜잭션이라는 강력한 보호 장치가 있다. 예를 들어 A 계좌에서 B 계좌로 돈을 이체하는 과정은 다음과 같이 하나의 트랜잭션으로 묶인다.

  1. 시작 (Begin Transaction)
  2. A 계좌의 잔액을 1,000원 줄인다.
  3. B 계좌의 잔액을 1,000원 늘린다.
  4. 완료 (Commit)

만약 3번 과정에서 시스템이 멈추면, 데이터베이스는 이 모든 과정을 없었던 일(Rollback)로 되돌린다. A 계좌의 돈만 사라지는 끔찍한 상황은 발생하지 않는 것이다. 전부 성공하거나, 전부 실패하거나(All or Nothing)가 보장되는 것이다.

하지만 마이크로서비스 환경에서는 '계좌 서비스'와 '이체 서비스'가 서로 다른 데이터베이스를 사용한다. 이들을 하나의 트랜잭션으로 묶을 방법이 없다. 여기서 Saga가 등장한다.

Saga는 여러 서비스에 걸친 로컬 트랜잭션들의 연속적인 시퀀스이다. 각 서비스는 자신의 로컬 트랜잭션을 성공적으로 완료한 뒤, 다음 서비스를 호출하는 이벤트를 발생시킨다.

Saga의 핵심은 보상 트랜잭션(Compensating Transaction)이다. 만약 사가의 중간 단계에서 작업이 실패하면, 이전까지 성공했던 모든 작업들을 거꾸로 거슬러 올라가며 취소하는 '보상 트랜잭션'을 실행한다.

  • 정상 흐름: 주문 생성 → (성공) → 결제 처리 → (성공) → 재고 감소
  • 실패 및 보상 흐름: 주문 생성 → (성공) → 결제 처리 → (실패) → 주문 취소 (보상)

이렇게 함으로써 전체 비즈니스 과정의 데이터 일관성을 결과적으로(Eventually) 맞출 수 있다.

Saga 구현 방식: Choreography vs Orchestration

사용자가 "상품 주문"을 하는 시나리오를 통해 두 가지 방식을 비교해 보려고 한다. 이 주문 과정에는 주문 서비스, 결제 서비스, 재고 서비스가 관여한다.

Choreography (코레오그래피 방식) : 이벤트 기반

방식은 중앙 지휘자 없이, 서비스들이 서로의 행동(이벤트)을 보고 알아서 움직이는 방식이다.

[성공 시나리오]

  1. (주문 서비스): "주문이 생성되었어!" 라는 OrderCreated 이벤트를 발행(Publish)합니다.
  2. (결제 서비스): OrderCreated 이벤트를 듣고(Subscribe) 결제를 시도한다. 성공하면 "결제가 완료됐어!" 라는 PaymentCompleted 이벤트를 발행한다.
  3. (재고 서비스): PaymentCompleted 이벤트를 듣고 재고를 줄인다. 성공하면 "재고 처리가 끝났어!" 라는 StockUpdated 이벤트를 발행하며 모든 과정이 끝난다.

[실패 및 보상 시나리오]

  1. (주문 서비스): OrderCreated 이벤트를 발행합니다.
  2. (결제 서비스): 결제를 시도했지만 실패한다. "결제가 실패했어!" 라는 PaymentFailed 이벤트를 발행한다.
  3. (주문 서비스): PaymentFailed 이벤트를 듣고, 자신이 생성했던 주문을 취소하는 보상 트랜잭션을 실행한다.

장점: 서비스들이 서로를 몰라도 되기 때문에(느슨한 결합), 새로운 서비스를 추가하거나 변경하기 쉽다.
단점: "지금 주문이 어느 단계에 있지?"를 한눈에 파악하기 매우 어렵다. 각 서비스의 로그를 모두 뒤져봐야 한다. 서비스가 5~6개만 넘어가도 흐름을 추적하는 것은 거의 불가능에 가깝다.

Orchestration (오케스트레이션 방식) : 중앙 지휘자 기반

[성공 시나리오]

  1. (지휘자): 주문 요청을 받고, 결제 서비스에게 "이 주문 결제해줘" 라고 직접 명령한다.
  2. (결제 서비스): 결제를 처리하고, 지휘자에게 "결제 성공했어" 라고 응답한다.
  3. (지휘자): 응답을 받고, 이번엔 재고 서비스에게 "이 상품 재고 줄여줘" 라고 명령한다.
  4. (재고 서비스): 재고를 줄이고, 지휘자에게 "재고 처리 완료" 라고 응답한다.
  5. (지휘자): 모든 응답을 확인하고, 최종적으로 주문 상태를 '완료'로 변경한다.

[실패 및 보상 시나리오]

  1. (지휘자): 결제 서비스에 결제를 명령한다.
  2. (결제 서비스): 결제에 실패하고, 지휘자에게 "결제 실패했어" 라고 응답한다.
  3. (지휘자): 실패 응답을 받고, 이미 성공했던 이전 단계가 있다면 해당 서비스에게 보상 트랜잭션을 직접 명령한다. (이 시나리오에서는 첫 단계라 보상할 것이 없음)
  4. (지휘자): 최종적으로 주문 상태를 '실패'로 변경한다.

장점: 비즈니스 프로세스가 지휘자 코드에 명확하게 나타나므로 이해하고 관리하기 쉽다.
단점: 모든 로직이 지휘자에 집중되어 지휘자가 매우 복잡해지고, 지휘자 서비스에 문제가 생기면 전체가 마비될 수 있다.

어떤 방식을 선택할지는 서비스의 복잡도와 팀의 설계 철학에 따라 달라진다.

어느 방식이 더 좋을까?

  • 서비스가 단순하고 독립적이면 Choreography
  • 흐름 제어와 추적이 중요하면 Orchestration

Saga 패턴의 어려운 점

Saga 패턴은 강력하지만 결코 은탄환이 아니다. 다음과 같은 어려운 점들을 반드시 고려해야 한다.

  • 복잡성 증가: 단순한 트랜잭션에 비해 구현하고 테스트해야 할 것이 훨씬 많다. 특히 보상 트랜잭션을 빠짐없이 설계하는 것은 매우 어렵다.

  • 데이터 격리 문제: Saga가 진행되는 동안에는 데이터의 일관성이 깨진 상태일 수 있다. 예를 들어, 결제가 진행되는 동안 다른 사용자가 재고를 확인하면 아직 줄어들지 않은 상태로 보일 수 있다. 이런 중간 상태를 어떻게 처리할지 정책이 필요하다.

Saga 패턴은 분산 시스템에서 데이터 일관성을 유지하기 위한 현실적인 해법이지만, 그만큼 신중한 설계와 구현이 필요하다.

마치며

오늘은 서비스의 요구사항에 맞는 NoSQL 데이터베이스를 선택하는 기준과, 분산된 환경에서 데이터의 일관성을 지키는 Saga 패턴에 대해 알아보았다. 적절한 데이터 저장소를 고르고, 흩어진 데이터의 흐름을 안정적으로 제어하는 것은 견고한 백엔드 시스템의 핵심이라고 생각한다.