우아한형제들 - 회원시스템 이벤트기반 아키텍처 구축하기

본 글은 우아한테크, 권용근 - 회원시스템 이벤트기반 아키텍처 구축하기를 정리한 글입니다.

목차

  1. 배달의민족 마이크로서비스 여행기
  2. 무엇을 이벤트로 발행할 것인가?
  3. 이벤트 발행과 구독
  4. 이벤트 저장소 구축
  5. 기록 테이블 통합
  6. 정리

배달의민족 마이크로서비스 여행기

들어가기에 앞서 김영한 - 배달의민족 마이크로서비스 여행기를 간략히 소개합니다.

  • 2015년 초기 배달의민족은 모놀리식 아키텍처로 구성됨
  • 2017년 비즈니스의 폭발적 성장으로 대장애의 시대가 열림
  • 2019년 마이크로서비스(이벤트기반 아키텍처)로 전환 완료, 시스템 안정화

위와 같이 많은 기업들이 마이크로서비스(이벤트기반 아키텍처)로 전환하고 소개해서 이는 익숙하지만 하나의 시스템에서 이벤트기반 아키텍처는 생소합니다. 본 영상에서는 배달의민족이 하나의 시스템안에서 이벤트기반 아키텍처를 어떻게 사용하는 소개합니다.

무엇을 이벤트로 발행할 것인가?

  • 왜 마이크로서비스와 이벤트기반 아키텍처는 같이 언급될까?
    • 마이크로서비스는 느슨하게 결합된 서비스의 모임으로 구조화하는 서비스 지향 아키텍처(SOA)
    • 이벤트기반 아키텍처는 이를 도움
  • 느슨한 결합을 이벤트기반 아키텍처로 어떻게 만족시킬수 있을까?
  • 회원과 가족계정으로 예시를 보임
    • 회원 도메인가족계정 도메인이 같은 서비스에 존재
      • 회원의 본인인증 초기화시 가족계정에서 탈퇴되어야하는 정책이 존재함 code
    • 트래픽 증가로 가족계정 도메인을 별도의 서비스로 분리
      • HTTP호출로 여전히 강한 결합 code
    • 비동기 요청으로 변경
      • 스레드 레벨 의존은 분리했지만 인증해제시 후속작업 필요하다는 사항이 존재해 여전히 강한 결합 code
    • 요청 행위를 메시징 시스템으로 처리
      • 가족계정 탈퇴를 메시징으로 요청
      • 물리적 의존은 제거되었지만 논리적 의존 관계가 존재, 강한 결합
      • 메시징 시스템을 사용하였지만 비동기요청일뿐 code
    • 도메인 이벤트를 메시징 시스템으로 처리
      • 느슨한 결합 code
  • 이벤트 발행시에는 달성하려는 목적인 아닌 도메인 이벤트 그 자체인것을 알수 있음

이벤트 발행과 구독

  • 회원시스템이 가진 문제를 해결하기 위해 3가지 이벤트 종류3가지 구독자 계층으로 정리
    • 어플리케이션 이벤트
    • 내부 이벤트
    • 외부 이벤트

1. 어플리케이션 이벤트, 첫 번째 구독자 계층

first-layer

  • 하나의 어플리케이션 내에서 다루는 이벤트
  • 스프링의 어플리케이션 이벤트
    • 분산 비동기를 다룰수 있는 이벤트 버스 제공
    • 트랜잭션 제어 가능
    • 단일 어플리케이션에서 사용하기 충분함
  • 단일 어플리케이션 내에서 이벤트를 다루는 이유
    • 느슨한 결합이 외부 시스템과 뿐만 아니라 내부에서도 필요하기 때문
    • 예로 도메인행위와 주 관심사가 먼 메시징 시스템으로 내부 이벤트를 발행하는 행위

2. 내부 이벤트, 두 번째 구독자 계층

second-layer

  • 첫 번째 구독자 계층이 발행하는 이벤트가 내부 이벤트
  • AWS SNS와 AWS SQS
    • 1:N 구조로 사용가능
    • 메시지 유실에 대한 강한 신뢰 확보가능
  • 발행된 내부 이벤트는 시스템 내의 이벤트 처리기(두 번째 구독자 계층)가 구독
  • 첫 번째 구독자 계층이 어플리케이션 내에서 해결해야하는 비관심사를 처리했다면, 두 번째 구독자 계층이 이외 모든 도메인내 비관심사를 처리

비관심사 분리

  • 도메인 행위가 수행될 때 함께 수행되어야하는 부가정책들이 존재함, 이들은 도메인의 주 행위에 대한 응집을 방해

  • 위의 예시처럼 부가정책들이 주행위와 함께존재하면 안됨
  • 이를 내부 이벤트로 처리

second-layer

  • 위와 같이 내부 이벤트를 발행하고 두 번째 구독자 계층에서 처리
    • 분리된 비관심사는 각 구현이 되어 강한응집, 높은 재사용성 확보 가능

어플리케이션 이벤트 vs 내부 이벤트

  • 비관심사 처리를 어플리케이션 이벤트로 처리할 수 있는거 아닌가?
    • TRADE OFF
  • 어플리케이션 이벤트 - 주요 행위와 강한 정합성 보장이 필요한 작업
    • 주요 행위와 트랜잭션 공유
    • 주요 행위와 성능을 고유
  • 내부 이벤트 - 주요 행위와 강한 정합성 보장이 필요하지 않은 작업
    • 주요 행위와 트랜잭션 분리
    • 주요 행위와 성능 분리

3. 외부 이벤트

  • 두 번째 구독자 계층이 발행하는 이벤트가 외부 이벤트
  • 외부 이벤트는 마이스로서비스에서 타서비스(세 번째 구독자 계층)들이 구독하는 이벤트

third-layer

열린 내부이벤트, 닫힌 외부이벤트

  • 타서비스(세 번째 구독자 계층)들이 내부 이벤트를 구독하면 안되는가?
    • 두 이벤트를 분리함으로써 내부에는 열린, 외부에는 닫힌 이벤트를 제공가능
  • 열린 내부이벤트
    • 내부 이벤트를 발행하고 구독하는 하나의 서비스는 같이 배포 될 수 있기 때문에 이벤트 페이로드를 변경가능
    • 이를 열려있다고 함
  • 닫힌 외부이벤트
    • 페이로드를 변경하면 구독중인 타서비스 수정 필요, 이는 많은 비용이듬 (사실상 거의 불가능한 경우도 많음)
    • 이를 닫혀있다고 함

이벤트 일반화

  • 닫혀있는 외부 이벤트를위한 가장안전하고 유연한 고정된 형태의 이벤트 필요
    • 언제(시간)
    • 누가(식별자)
    • 무엇을 하여(행위)
    • 어떤 변화가(속성)
  • 구독자는 데이터가 더 필요할 수 있지만 구독자의 행위를 기대하지 않아야 느슨한 결합
  • ZERO-PAYLOAD
    • 이벤트 순서 보장문제 해결로 주로 소개됨
    • 외부 시스템에 대한 페이로드 의존도 제거가능

이벤트 저장소 구축

  • AWS SNS, AWS SQS 구간은 매우높은 신뢰성 정책을 제공중
  • SQS역시 높은 신뢰성과 데드레터큐(DLQ; Dead Letter Queue) 전략으로 내부 이벤트 처리 및 외부 이벤트 생성 가능
  • 문제는 내부 이벤트 발행시점
    • HTTP를 사용해서 발행 (AWS SNS 발행시점)
  • 도메인 행위는 수행되었는데 이벤트가 발행되어있지 않을수 있음
  • 이를 해결하기 위해 이벤트 저장 필요

이벤트 저장 시점

  • 도메인행위와 이벤트처리를 별도 트랜잭션으로 처리, 즉 이벤트 발행을 보장할 수 없음
  • 이 보장을 이벤트 저장소를 통해 만족시킴

  • SNS 발행에 실패해도 이벤트 저장소에 저장이 보장되기 때문에 재발행가능
  • 저장소 선택시 도메인 저장소와 이벤트 저장소와 트랜잭션을 묶을수 있어야함
    • 다중 데이터베이스 분산 트랜잭션 구현필요
    • 그러나 굉장히 어려운일
    • 스프링의 ChainedTransactionManager는 Deprecated 되었음
    • 여기서는 이벤트 저장소를 별도로 사용하지 않고 도메인 저장소를 사용함
      • 이벤트 유실을 낮은 확률이라도 허용할 수 없기때문
      • 이를 Transactional Outbox Pattern 라고함
        • 로컬 트랜잭션으로 데이터베이스 저장, 이벤트 발행으로 정합성 보장

이벤트 유실 문제 해결

  • 이벤트 저장소에 발행여부 추가
    • 발행여부 - published
    • 발행일시 - published_at
  • 첫 번째 구독자 계층, 이벤트 발행

  • 두 번째 구독자 계층, 이벤트 발행 확인

  • 주기적으로 실행되는 별도 배치로 일정주기동안 발행되지 않은 이벤트를 재발행
  • 결론적으로 이벤트를 유실하지 않고 자동 재발행되는 시스템을 구축함

기록 테이블 통합

  • 회원 시스템은 사용자의 행동을 기록, 각종 사용자의 로그 데이터를 기록함
  • 이 데이터를 이벤트 저장소를 통해 통합할 수 있다는 것을 알게됨
    • 다양한 형태로 기록되던 사용자 로그를 이벤트 저장소로 통합

Reference

  • https://www.baeldung.com/spring-events
  • https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/application-events.html
  • https://aws.amazon.com/ko/what-is/dead-letter-queue/
  • https://microservices.io/patterns/data/transactional-outbox.html