티스토리 뷰

[9주차 과제]

  • 내가 개발한 기능의 트랜잭션 범위에 대해 이해하고, 서비스의 규모가 확장된다면 서비스들을 어떻게 분리하고 그 분리에 따른 트랜잭션 처리의 한계와 해결방안에 대한 서비스설계 문서 작성
  • 실시간 주문, 좌석예약 정보를 데이터 플랫폼에 전달하는 요구사항을 기존 로직으로 구현했을때 발생하는 문제와 해결방안에 대한 문서 작성

 

[트랜잭션 분리의 중요성]

서버 어플리케이션에서 트랜잭션의 범위를 어떻게 잡는지에 따라서 성능에 영향을 줄 수 있다.

  1. 문제상황 1번 : 1개의 트랜잭션에 너무 많은 작업이 있거나, 조회가 Slow Read 쿼리가 포함되어 있는 경우
    • 트랜잭션 내에서 테이블의 데이터에 Lock을 잡은 상황이라면 다른 사용자는 트랜잭션이 모두 끝날 때까지 대기해야 하는 문제
    • Slow Read 하는 쿼리가 있는 경우 다수의 요청처리에 영향을 줄 수 있다.
    • 트랜잭션이 끝나기 직전에 오류가 발생하면 그 때까지 처리한 트랜잭션이 roll back 해야하는 상황이 발생.
  2. 문제상황 2번 : DB 외적인 작업의 실패로 인해서 트랜잭션 전체가 roll back 하는 경우
    • DB 관련 작업은 끝났는데 외부 API 처리가 오래 걸리는 경우, DB Connection을 불필요하게 오래 가져간다.
    • 외부 API 오류가 발생하면 DB 관련 작업까지 모두 트랜잭션 roll back 처리를 하게 된다. DB 자원 낭비 발생.

이렇게 트랜잭션을 길게 가져가면 문제가 다수의 요청에 영향을 줄 수 있다.

적절하게 트랜잭션 범위를 나눠주는 것이 서버의 성능을 높이는 방법이다.

 

[콘서트 예약시스템에서 예약정보를 데이터 플랫폼에 보내는 트랜잭션이 추가된다면? ]

  • 현재 결제 프로세스에서 예약정보를 데이터 플랫폼에 보낸다면 아래와 같은 트랜잭션이 된다.
public 결제트랜잭션 {
        사용자조회();
        토큰유효성검증();
        좌석상태변경();
        예약상태변경();
        포인트충전();
        결제처리();
        토큰만료처리();
        예약정보 데이터플랫폼에 전송();
}

위 트랜잭션은 1개의 트랜잭션안에 무려 8개의 작업이 있고, 이 모든 작업들이 정상완료가 되어야 트랜잭션이 성공적으로 끝난다.

만약에 예약정보를 데이터플랫폼에 전송하는 작업에 지연이 있거나, 오류가 발생한다면 그 전까지 진행했던 모든 작업들을 roll back 해야 한다. 그래서 위의 트랜잭션을 아래와 같이 4개로 분리해서 서로간의 영향도를 낮추고자 나눴다.

 

[트랜잭션 분리 개선안]

public 트랜잭션1 {
          토큰 검증
          사용자 조회
          결제생성
          토큰 만료
          Outbox 테이블 저장
          이벤트 발행
}

public 트랜잭션2 {
        좌석상태변경
  	예약상태변경
    Outbox 테이블 저장
    이벤트 발행
}

public 트랜잭션3 {
        포인트 사용
        Outbox 테이블 저장
        이벤트 발행
}
public 트랜잭션4 {
        데이터 플랫폼에 정보 전송
}

 

위와 같이 트랜잭션을 분리한 기준은 "Lock 범위가 적절히 작은 트랜잭션" 입니다.이렇게 분리한 이유는 DB Lock이 필요한 트랜잭션(좌석상태변경, 포인트 사용)에서 Lock의 범위를 축소시켜서 불필요한 대기상태를 줄이는 것에 있습니다. 그리고 트랜잭션4에 해당하는 데이터 플랫폼에 정보를 전송하는 작업은 분리해서 트랜잭션 3가 완벽하게 끝난 이후에 이벤트 처리로 별도로 실행될 수 있도록 설계했습니다.

현재 제가 개발하는 환경은 모놀리틱 서버이기 때문에 트랜잭션 1 ~ 3에서는 Outbox Table에 발행할 메시지만 저장하되, 실제로 각각의 트랜잭션에서는 Spring Event의 Publisher/Listner의 기능을 이용해서 트랜잭션이 순차적으로 실행되도록 구현합니다.

그리고 트랜잭션 4번은 데이터 플랫폼 서버가 따로 있다고 가정하고, Kafka를 활용해서 메시지 발행을 구현할 예정입니다.

 

 

[트랜잭션을 분리하는 방법]

이벤트 퍼블리셔와 리스너로 트랜잭션 분리

  1. ApplicationEventPublisher & EventListner / TransactionEventListner
    • 스프링에서 제공하는 Spring Event 기능을 사용하여 Publisher와 EventListner를 만들어서 트랜잭션을 분리할 수 있습니다.
    • Event란? 
      • 특정 트리거가 작동하면 시작되는 "동작 혹은 사건"을 뜻한다.
      • ex1) A는 작업이 끝나서 물건을 B에게 직접 전달한다
      • ex2) A는 작업이 끝나고, B에게 전화를 한다. 그러면 B가 직접 와서 물건을 가져간다. => 이게 Event publish/listner로 하는 것
  2. Kafka 비동기 메시지 통신을 통한 책임 분리
    • Pub/Sub 방식의 메시지 구독 시스템
    • 메시지큐를 사용하면 발신자와 수신자가 서로를 알 필요가 없어서 느슨한 결합이 가능하다
    • 장애가 발생해도 메시지큐에 발신자가 보낸 메시지가 남아있기 때문에 보장성이 확보된다.

트랜잭션 분리를 위한 선택 : Kafka 비동기 메시지 통신 채택

- 분산시스템, 비동기 메시징 환경에서 추구하는 방향

- Out box pattern 적용해서 이벤트 발행 보장

이렇게 해서 도메인 로직이 완료되면 이벤트 발행도 보장이 확실하게 된다. 이를 통해서 로직의 정합성 확보.

* Transactional Outbox Pattern 정의 : 도메인 로직과 이벤트 발행 정보를 기록하는 로직을 하나의 트랜잭션으로 묶는 것

이를 통해서 도메인 로직이 정상 완료되면, 이벤트 발행 정보를 생성하는 것도 100% 보장할 수 있게된다.

- 중간에 서버가 중단 되어도 메시지큐에 메시지가 남아있어서 보장성이 확보되기 때문

 

 

카프카가 무엇이고, 왜 사용하는 것 일까?

메시지 큐와 MOM 출처: https://www.cloudamqp.com/blog/what-is-message-queuing.html 카프카를 이해하기 위해서는 메시지 큐와 MOM을 먼저 알아야한다. 메시지 큐는 분산화된 환경에서 발신자와 수신자 사이에서

hudi.blog

 

 

트랜잭셔널 아웃박스 패턴의 실제 구현 사례 (29CM)

이 글에서는 실무 관점에서의 Apache Kafka 활용 에서 잠깐 소개했던 트랜잭셔널 아웃박스 패턴 (Transactional Outbox Pattern) 을 실제로 구현하여 활용하고 있는 29CM 의 사례를 소개하고자 한다.

medium.com

 

 

 

 

 

 

 

댓글