Trouble Shooting ‐ S3 ‐ DB 간 데이터 정합성을 유지하는 방법에 대한 고민 - dnwls16071/Backend_Study_TIL GitHub Wiki

❓작성하게 된 이유

  • 사이드 프로젝트에서 공간 관련 REST API를 개발했다.
  • 해당 API는 공간을 등록할 때 이미지를 첨부할 수 있는 API이다.
  • 부가적인 기능(이미지 첨부)의 실패가 핵심 기능(공간 등록)에 영향을 미쳐서는 안되며 반대로 핵심 기능(공간 등록)이 실패했는데 부가적인 기능(이미지 첨부)이 성공하면 안 된다.

💻첫 번째 방법 : propagation=REQUIRES_NEW

  • 하나의 트랜잭션 범위(공간 등록 & 이미지 추가)가 넓은 것이 문제니까 트랜잭션을 따로 분리해주면 되지 않을까라는 생각을 했다.
  • 스프링에서는 @Transactional 어노테이션을 통해 여러 전파 옵션을 제공한다.

image

  • 물리 트랜잭션1에서 로직1을 호출하고 그 로직1이 새로운 물리 트랜잭션2를 만들어 내고 로직2를 호출하게 된다.
  • 결과적으로 다른 트랜잭션이기 때문에 롤백 자체가 전파되지 않는다.

스크린샷 2025-02-19 오전 11 28 14

💻두 번째 방법 : ApplicationEventPublisher으로 이벤트 처리하기

  • 해당 방법은 이미 다른 프로젝트에서 SSE 알림 구현 중 다중 서버로 Scale Out시 발생하는 문제를 해결하기 위해 Redis에서 적용을 해보기도 했었는데 한 번 더 정리하고 가려 한다.
  • ApplicationEventPublisher는 Event를 받아 Listener들에게 Event를 Publish 해준다.
  • 하지만 해당 방법은 한 트랜잭션으로 묶이는 문제는 여전히 해결되지 않는다.

💻세 번째 방법 : @TransactionalEventListener 사용하기

  • @TransactionalEventListener 어노테이션은 phase 옵션을 통해 트랜잭션에 따른 이벤트 처리를 지원해준다.
  • @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) : 기본값이며, 트랜잭션이 커밋되었을 때, 이벤트를 실행한다.
  • @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) : 트랜잭션이 롤백되었을 때 이벤트를 실행한다.
  • @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION) : 트랜잭션이 COMPLETION 되었을 때, 이벤트를 실행한다. 결국 AFTER_COMMIT, AFTER_ROLLBACK이 발생했을 떄를 의미한다.
  • @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) : 트랜잭션이 커밋되기 전에 이벤트를 실행한다.

❓공간 등록 프로세스 - 2가지 Case에 대해서 생각해보기

스크린샷 2025-02-19 오후 12 58 07

  • API 요구사항은 공간을 등록할 때 반드시 이미지도 필수로 추가해야 하는 것이었다.
  • 위의 방법으로 한다면 어떤 문제가 발생하는지를 체크해보았다.
    • 만약 공간 등록에 실패했다면 당연히 이벤트가 호출되지 않는다. S3 - DB 간 데이터 정합성은 유지된다.
    • 만약 공간 등록에 성공했다면 그 뒤에 이미지 저장 이벤트가 호출이 된다.
      • 이 때, 발급받은 PreSigned URL의 수명이 지나 만료되었다면 이미지 업로드를 할 수 없다. 따라서 공간은 등록되겠지만 이미지 업로드가 되지 않아 데이터 정합성은 유지되지 않는다.

스크린샷 2025-02-19 오후 1 11 31

  • 위의 방법으로 한다면 어떤 문제가 발생하는지를 체크해보았다.
    • 만약 이미지 등록에 실패(기타 오류, PreSigned URL 수명 만료 등의 사유)했다면 당연히 공간 등록도 할 수 없다.
    • 만약 이미지 등록에 성공했다면 그 뒤에 공간 등록 이벤트가 호출이 된다.
      • 이 때, JWT AccessToken이 절묘한 타이밍에 만료되었다면 공간 등록을 할 수 없다. 따라서 이미지 등록은 되겠지만 공간 등록이 되지 않아 데이터 정합성은 유지되지 않는다.

💻네 번째 방법

스크린샷 2025-02-19 오전 11 01 42

  • S3 버킷과 DB에 저장되는 데이터 정합성을 유지하는 방법을 위와 같이 생각했고 과정을 나열하면 아래와 같다.
  1. 클라이언트는 PreSigned-URL을 발급받는다. 이 때, 공간 등록 시 첨부하는 이미지를 정확히 식별할 수 있도록 UUID를 발급한다.
  2. 발급받은 PreSigned-URL을 사용해 클라이언트는 HTTP PUT 요청으로 S3 임시 버킷에 이미지를 등록한다.
  3. 공간 등록 API를 호출하면 S3 임시 버킷에 저장된 파일이 S3 메인 버킷으로 복사되면서 공간과 이미지가 같이 저장된다.
  4. 만약 공간 등록에 실패했다면 S3 임시 버킷에 저장된 이미지는 Lambda와 CloudFront로 주기적으로 클리너 이벤트를 호출하여 버킷을 제거한다.