마이크로서비스 아키텍처(MSA)는 모듈화, 확장성, 독립적 배포 등의 많은 이점을 제공하지만, 데이터베이스 통합 및 일관성 유지 측면에서는 여러 가지 문제를 발생시킬 수 있습니다.
이와 관련된 안티 패턴과 효과적인 디자인 패턴에 대해 자세히 살펴보겠습니다.
여러 마이크로서비스가 하나의 데이터베이스를 공유하는 패턴입니다.
예를 들어, 결제 서비스와 사용자 서비스가 같은 데이터베이스를 사용하는 구조입니다.
모듈성과 확장성 저하: 서비스 간 데이터베이스 공유로 인해 독립적인 확장이 어렵습니다.
하나의 서비스가 데이터베이스를 잠그면 다른 서비스의 운영에 영향을 줄 수 있습니다.
팀 간 충돌: 서로 다른 팀이 같은 데이터베이스 스키마를 관리하면 설계 충돌과 관리 이슈가 발생할 수 있습니다.
예를 들어, 결제 서비스가 데이터베이스 업데이트 중일 때 사용자 서비스가 같은 테이블을 수정하려 하면 교착 상태가 발생할 수 있습니다.
작은 규모의 애플리케이션에서는 사용할 수 있지만, 시스템 확장이나 복잡한 아키텍처를 고려할 때는 지양해야 합니다.
각 마이크로서비스가 자신의 데이터베이스를 가지는 구조입니다.
결제 서비스는 결제 DB, 사용자 서비스는 사용자 DB를 사용합니다.
독립적 확장성: 각 서비스가 독립적으로 확장될 수 있습니다.
예를 들어, 결제 서비스가 높은 부하를 받을 경우 해당 서비스와 데이터베이스만 확장하면 됩니다.
격리 및 장애 방지: 하나의 서비스나 데이터베이스에서 문제가 발생하더라도 다른 서비스에 영향을 주지 않습니다.
예를 들어, 결제 DB가 교착 상태에 빠져도 사용자 서비스는 정상적으로 작동할 수 있습니다.
기술 스택 최적화: 서비스 특성에 맞는 데이터베이스 선택이 가능합니다.
보안 강화
모든 서비스가 준비 상태(Prepare)에 도달한 후 최종 커밋(Commit) 또는 롤백(Rollback)을 수행하는 방식입니다.
준비 단계(Prepare): 주문 서비스가 사용자 서비스와 결제 서비스에 트랜잭션 준비를 요청합니다.
각 서비스는 트랜잭션 준비 완료 여부를 응답합니다.
커밋/롤백 단계(Commit/Rollback): 모든 서비스가 준비 완료 상태라면 커밋을 진행하고, 하나라도 실패 시 전체 롤백을 수행합니다.
트랜잭션을 여러 단계의 로컬 트랜잭션으로 나누고, 각 단계가 성공하면 다음 단계로 넘어갑니다.
실패 시 보상 트랜잭션(Compensation Transaction)을 실행하여 이전 상태로 되돌립니다.
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()
비동기 처리 및 확장성: 각 서비스가 독립적으로 동작하며 확장성이 뛰어납니다.
장애 허용성: 일부 서비스 실패 시에도 전체 시스템이 중단되지 않고 복구할 수 있습니다.
모든 상태 변경을 이벤트로 저장하고, 이벤트 스트림을 통해 데이터 동기화를 수행합니다.
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
a) 분산 트레이싱
b) 로깅 전략
공유 데이터베이스 패턴은 단기적으로는 간편하지만, 확장성과 모듈성을 저해하고 교착 상태 등의 기술적 문제를 유발하기 때문에 피하는 것이 좋습니다.
서비스별 개별 데이터베이스 구조가 마이크로서비스의 독립성과 확장성을 최대한 활용할 수 있는 구조입니다.
분산 트랜잭션 처리는 2PC와 사가 패턴을 통해 해결할 수 있으며, 2PC는 단순하지만 확장성이 떨어지고, 사가 패턴은 확장성이 뛰어나지만 설계가 복잡합니다.
마이크로서비스 아키텍처를 적용할 때는 시스템 규모, 요구 사항, 팀의 역량 등을 고려하여 적절한 패턴을 선택하는 것이 중요하다는것을 배웠습니다.