마이크로서비스 아키텍처에서 자주 발생하는 DB 안티 패턴과 해결 방안

궁금하면 500원·2024년 11월 27일
0

MSA&아키텍처

목록 보기
20/42

마이크로서비스 아키텍처(MSA)는 모듈화, 확장성, 독립적 배포 등의 많은 이점을 제공하지만, 데이터베이스 통합 및 일관성 유지 측면에서는 여러 가지 문제를 발생시킬 수 있습니다.

이와 관련된 안티 패턴과 효과적인 디자인 패턴에 대해 자세히 살펴보겠습니다.

1. 안티 패턴: 공유 데이터베이스 패턴

여러 마이크로서비스가 하나의 데이터베이스를 공유하는 패턴입니다.
예를 들어, 결제 서비스와 사용자 서비스가 같은 데이터베이스를 사용하는 구조입니다.

문제점

  • 모듈성과 확장성 저하: 서비스 간 데이터베이스 공유로 인해 독립적인 확장이 어렵습니다.
    하나의 서비스가 데이터베이스를 잠그면 다른 서비스의 운영에 영향을 줄 수 있습니다.

  • 팀 간 충돌: 서로 다른 팀이 같은 데이터베이스 스키마를 관리하면 설계 충돌과 관리 이슈가 발생할 수 있습니다.

기술적 문제

  • 교착 상태(Deadlock): 두 개 이상의 트랜잭션이 서로 필요한 리소스를 점유한 채 해제되기를 기다리는 상황으로, 이는 시스템 중단을 초래할 수 있습니다.

예를 들어, 결제 서비스가 데이터베이스 업데이트 중일 때 사용자 서비스가 같은 테이블을 수정하려 하면 교착 상태가 발생할 수 있습니다.

  • 성능 영향: 데이터베이스 커넥션 풀 경합, 테이블 락으로 인한 대기 시간 증가, 데이터베이스 리소스(CPU, 메모리) 경합 등이 발생합니다.

운영 관점의 문제

  • 스키마 변경 시 모든 서비스의 영향도 분석 필요
  • 데이터베이스 백업/복구 시 모든 서비스에 영향
  • 데이터베이스 유지보수 작업의 어려움

결론

작은 규모의 애플리케이션에서는 사용할 수 있지만, 시스템 확장이나 복잡한 아키텍처를 고려할 때는 지양해야 합니다.

2. 추천 패턴: 서비스별 개별 데이터베이스

각 마이크로서비스가 자신의 데이터베이스를 가지는 구조입니다.
결제 서비스는 결제 DB, 사용자 서비스는 사용자 DB를 사용합니다.

장점

  • 독립적 확장성: 각 서비스가 독립적으로 확장될 수 있습니다.
    예를 들어, 결제 서비스가 높은 부하를 받을 경우 해당 서비스와 데이터베이스만 확장하면 됩니다.

  • 격리 및 장애 방지: 하나의 서비스나 데이터베이스에서 문제가 발생하더라도 다른 서비스에 영향을 주지 않습니다.
    예를 들어, 결제 DB가 교착 상태에 빠져도 사용자 서비스는 정상적으로 작동할 수 있습니다.

  • 기술 스택 최적화: 서비스 특성에 맞는 데이터베이스 선택이 가능합니다.

    • 결제 서비스 → RDBMS(MySQL)
    • 로그 서비스 → 시계열 DB(InfluxDB)
    • 검색 서비스 → 검색엔진(Elasticsearch)
  • 보안 강화

    • 서비스별 독립적인 데이터 접근 정책 적용
    • 데이터 유출 위험 최소화
    • 규제 준수(예: PCI DSS, GDPR) 용이

문제점

  • 분산 트랜잭션 처리의 어려움: 서로 다른 데이터베이스 간 트랜잭션 일관성을 유지하기 어렵습니다.

3. 분산 트랜잭션 처리 방법

3.1 2단계 커밋(Two-Phase Commit, 2PC)

모든 서비스가 준비 상태(Prepare)에 도달한 후 최종 커밋(Commit) 또는 롤백(Rollback)을 수행하는 방식입니다.

단계

  • 준비 단계(Prepare): 주문 서비스가 사용자 서비스와 결제 서비스에 트랜잭션 준비를 요청합니다.
    각 서비스는 트랜잭션 준비 완료 여부를 응답합니다.

  • 커밋/롤백 단계(Commit/Rollback): 모든 서비스가 준비 완료 상태라면 커밋을 진행하고, 하나라도 실패 시 전체 롤백을 수행합니다.

문제점

  • 확장성 제한: 서비스가 많아질수록 관리와 동기화가 어려워집니다.
  • 복잡성: 트랜잭션 관리가 복잡해지고 시스템 지연이 발생할 수 있습니다.

사가 패턴(Saga Pattern)

트랜잭션을 여러 단계의 로컬 트랜잭션으로 나누고, 각 단계가 성공하면 다음 단계로 넘어갑니다.
실패 시 보상 트랜잭션(Compensation Transaction)을 실행하여 이전 상태로 되돌립니다.

구조

  • 이벤트 기반(Event-driven): 주문 서비스가 이벤트를 발행하고, 결제 서비스, 사용자 서비스, 인벤토리 서비스 등이 메시지 브로커(RabbitMQ, Kafka 등)를 통해 이를 수신하고 처리합니다.

구체적 구현 방법

a) 코레오그래피(Choreography) 방식

# 주문 서비스
@kafka_listener(topic="order_created")
def handle_order_created(order_event):
    publish_event("payment_required", order_event)

# 결제 서비스
@kafka_listener(topic="payment_required")
def handle_payment_required(payment_event):
    try:
        process_payment(payment_event)
        publish_event("payment_completed", payment_event)
    except PaymentException:
        publish_event("payment_failed", payment_event)

b) 오케스트레이션(Orchestration) 방식

class OrderSaga:
    def start_order(self, order_details):
        try:
            payment_result = payment_service.process(order_details)
            inventory_result = inventory_service.check(order_details)
            order_service.confirm(order_details)
        except Exception:
            self.compensate()

장점

  • 비동기 처리 및 확장성: 각 서비스가 독립적으로 동작하며 확장성이 뛰어납니다.

  • 장애 허용성: 일부 서비스 실패 시에도 전체 시스템이 중단되지 않고 복구할 수 있습니다.

문제점

  • 복잡성 증가: 보상 트랜잭션 설계가 필요하며, 서비스 간 이벤트 흐름을 잘 관리해야 합니다.

4. 데이터 일관성 유지 전략

a) 이벤트 소싱(Event Sourcing)

모든 상태 변경을 이벤트로 저장하고, 이벤트 스트림을 통해 데이터 동기화를 수행합니다.

예시 코드

class OrderEventStore:
    def save_event(self, event):
        event_data = {
            'type': event.type,
            'timestamp': datetime.now(),
            'data': event.data
        }
        self.events.append(event_data)
    
    def rebuild_state(self):
        state = OrderState()
        for event in self.events:
            state.apply(event)
        return state

장점

  • 감사(Audit) 및 디버깅 용이
  • 이벤트 스트림을 통한 데이터 일관성 유지

5. 모니터링 및 트러블슈팅

a) 분산 트레이싱

  • 도구: OpenTelemetry 활용
  • 기능: 서비스 간 트랜잭션 추적, 성능 병목 지점 식별

b) 로깅 전략

  • 상관관계 ID(Correlation ID) 활용: 요청 간 연관성을 파악하기 위해 사용
  • 구조화된 로깅(Structured Logging): JSON 형식 등으로 로그를 체계화
  • 중앙화된 로그 수집: ELK 스택(Elasticsearch, Logstash, Kibana) 활용

결론

  1. 공유 데이터베이스 패턴은 단기적으로는 간편하지만, 확장성과 모듈성을 저해하고 교착 상태 등의 기술적 문제를 유발하기 때문에 피하는 것이 좋습니다.

  2. 서비스별 개별 데이터베이스 구조가 마이크로서비스의 독립성과 확장성을 최대한 활용할 수 있는 구조입니다.

  3. 분산 트랜잭션 처리는 2PC와 사가 패턴을 통해 해결할 수 있으며, 2PC는 단순하지만 확장성이 떨어지고, 사가 패턴은 확장성이 뛰어나지만 설계가 복잡합니다.

마이크로서비스 아키텍처를 적용할 때는 시스템 규모, 요구 사항, 팀의 역량 등을 고려하여 적절한 패턴을 선택하는 것이 중요하다는것을 배웠습니다.

profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글