이론 정리

배민에서 대규모 트랜잭션 처리하는법

철매존 2024. 5. 20. 22:38
728x90

배민에서 대규모 트랜잭션 처리하는법

사실 이번에 브런치에서 좀 정보성 데이터를 많이 가지고 이걸 전달해야 하는 개발사항이 있었다.
뭔가 많은 조인과... 많은 데이터가 필요하고 약간의 실시간성을 가진 친구를 만들어야 하는데 이걸 어떻게 할까 하다가 여기까지 굴러들어왔다.
근데 들어보니까 내가 고민한 것과 매우 비슷했던 것 같고 좋은 내용이 많아 간략히 정리해 보았다.

https://www.youtube.com/watch?v=704qQs6KoUk
요거

배민 주문 시스템의 특징

  • 배민 주문 시스템
    • 장바구니, 주문하기, 주문내역 같은 애들을 만드는곳이다.
    • 여기 시스템은 12시, 18:30 같은 식사시간에 주문수 추이가 확 늘어난다.
      • 즉 트래픽이 특정 시간에 집중된다는것이다.
  • MSA
    • 배민은 수많은 시스템으로 이루어져 있다.
      • 그냥 주문 -> 배달이 아니라 가게,메뉴,주문,결제,배달 등 수많은 시스템이 있고 장애가 전파되지 않게 느슨한 결합을 하고있다.
        • 근데 이 때에 Transaction을 잘 할 수 있을까?
  • 대용량 데이터
    • 일평균 300만건 정도 주문 발생 (수년간의 데이터를 보관하고 관리해야한다.)
      • 어떻게 대규모 데이터 정합성을 보장하고 제공할까?
  • 대규모 트랜잭션
    • 순간적으로 많은 트랜잭션 발생
  • 여러 시스템과 연계
    • 이벤트 기반으로 통신을 잘 해야한다.

문제와 해결 방안

  • 주문수 증가 추이
    • 가파르게 주문수가 증가함
      • 처음에는 100만 주문도 못했는데 300만건 이상 주문으로 가파르게 증가(특히 이벤트가 있으면 더 많은 주문이 발생한다.)
  • 성장통
    • SPOF
      • 한곳에서 장애나면 전체 장애
    • 대용량 데이터
      • RDBMS 조인 연산으로 조회 성능이 좋지 않았다.
    • 대규모 트랜잭션
      • 주문소 증가로 저장소의 쓰기 처리량 한계에 도달
    • 복잡한 이벤트 아키텍처
      • 규칙 없는 이벤트 발행으로 서비스 복잡도가 높아졌다.

SPOF (단일 장애 시스템)

  • 기존에는 중앙 집중 DB가 있어, 이게 장애나면 전체 시스템에 장애가 전파됐다.
    • 중앙 저장소에서 각 시스템은 분리하는 프로젝트 진행
      • 기존 집중 DB 폐기 -> 이렇게 MSA
    • 메세지 큐를 통한 이벤트 기반 통신 진행

대용량 데이터

  • 데이터가 많아지니까 join 성능 저하 -> 즉 조회 성능 저하
  • image
    • 이거는 보통 비슷할 것 같은데... 비동기로 정보성 데이터를 저장하고 나머지 이벤트 시작하는 방식으로 보인다.
      • 주문과 조회가 함께 일어나는데 documentDB로 하나의 싱글 도큐먼트로 구성해서 저장
        • ID기반 조회 연산으로 가져오기 가능
    • 그럼 동기화는 어떻게 했을까?
      • 주문은 보통 주문생성 -> 접수 가 있고 배달완료/주문취소가 있다.
        • DB의 변화는 이 사이클 내에서만 일어난다.
          • image
            • 초록색 : 주문이 일어나면 주문 데이터를 RDBMS에 저장
            • 파란색 : 동시에 동기화로 필요 데이터를 documentDB에 저장
            • 빨간색 : 조회하는 경우 이 documentDB에서 조회
              • CQRS 패턴

대규모 트랜잭션

  • 주문 DB의 분당 쓰기 처리량 한계치 도달
  • image
    • 주문 DB는 이런 식으로 HA구성이 되어있는데 1개의 master이 있다.
      • 그래서 읽기가 많으면 scale-out이 되는데 쓰기가 많으면 scale-up 밖에 안됨...
  • 샤드 클러스터 구성 -> 쓰기 부하 분산
    • 근데 AWS 오로라는 샤딩을 지원하지 않음
  • 애플리케이션에서 샤딩하기!
    • 샤드 클러스터 내 어느 샤드에 접근할지 결정하는 샤딩전략 고민
      • 일단 샤드 전략
        • Key Based Sharding == Hash Based Sharding
          • 샤드 키를 이용해서 데이터 소스 결정
          • 해시 방식을 통해 값을 통해 찾아가도록
          • 장점은 구현이 간단하고 데이터를 골고루 분배 가능하다.
          • 단점은 장비를 동적으로 추가, 제거할 때 데이터 재배치 필요(해시 function이 변경되면 재배치가 필요)
        • Range Based Sharding
          • 값의 범위 기반으로 데이터를 분산
          • 범위 기반으로 하는거고, 처음에는 가격 기반으로 하려고 했다고 한다.
          • 장점은 이것도 매우 간단하다.
          • 단점은 데이터가 균등하지 않아 특정 샤드에 데이터가 몰리면 Hotspot 이 되어 성능 저하 발생 가능.
        • Directory Based Sharding
          • 샤드가 어떤 데이터를 가질지 look up table 유지하는 방식
          • 중간에 테이블을 둬서 이거는 절로가~ 저거는 일로가~ 이렇게 하는거임
          • 장점은 중간에 Table이 있어서 유연하게 샤드를 추가하거나 변경이 가능하다.
          • 단점은 이 Look up table이 단일 장애 포인트가 될 수 있다는 것.
      • 일단 배민의 주문 시스템은
        • 주문이 정상 동작하지 않으면 배민 서비스 전체의 좋지 않은 경험
          • 단일 장애 포인트는 피하자.
        • 동적 주문 데이터는 최대 30일만 저장한다. -> 보통 대부분 하루내에 데이터가 스냅샷으로 바뀐다는것이다.
          • 샤드 추가 이후 30일이 지나면 데이터는 다시 균등하게 분배된다.
        • 는 특징이 있기 때문에 Key Based Sharding을 선택했다.
          • 이거는 주문이 생길때마다 다음 샤딩 DB로 저장되는 방식(아주 균등하게 가는거지)
    • 여러 샤드에 있는 데이터를 애그리게이트 하는 방법
      • N개의 샤드에 분산 저장된 데이터를 어떻게 조합해서 내려줄지에 대한 고민
        • 조회성 데이터는 documentDB 활용하면 되지

복잡한 이벤트 아키텍처

  • 규칙성 없는 무분별한 이벤트 발행
  • 배민은 보통 이벤트 기반으로 관심사를 분리한다.
    • 알림전송 / 현금 영수증 발행 / 분석 로그 전송 등등을 격리했다.
  • image
    • 각각의 이벤트와 그에 해당되는 여러 후속 서비스 이벤트가 있는데,
    • image
      • 이런 식으로 주체 어플리케이션이 달라지는게 점점 주체 파악과 유지보수가 어려워졌다.
        • image
          • 내부 이벤트 : ZERO payload 전략 사용
            • 여기다가 괜히 로직 넣지 않도록(그래서 이런게 이렇게 됐어 이렇게만)
          • 이벤트 처리기 : 필요한게 있으면 필요한 데이터를 채워주는 방식 (그거는 이제 documentDB를 쓰면 금방 되니까)
      • 또 이벤트 유실이 발생한 경우 재처리가 어려웠다 (이거는 spring event 사용해서)
        • 트랜잭션 내부에서 이벤트 발행이 실패되는 경우
          • 도메인 로직 자체가 실패된다.(그래서 아예 아무것도 안돼서 상관X)
        • 트랜잭션 외부에서 이벤트 발행이 실패되는 경우
          • 도메인 로직은 성공했는데 이벤트가 실패하면 믿을수가 없지
            • 트랜잭션 아웃박스 패턴
            • 이벤트 발행 실패와 서비스 실패를 격리하여 재발행 수단을 보장
            • 트랜잭션이 완료되면 아웃박스 엔티티에 이벤트 페이로드 저장
              • 그래서 발행 실패되면 아웃박스 엔티티에서 다시 발행하기
  • 이벤트 로직을 단일 애플리케이션에 위임하고, 실패 가능성은 스냅샷을 저장해서 막기