Project/대용량 트래픽 프로젝트

[기술적 의사결정] MSA 환경에서 배송 정보 임시 저장소로 Redis를 사용한 이유

쉬지마 이굥진 2025. 5. 3. 17:44

💡문제 상황

우리 프로젝트에서는 주문을 생성할 때 배송 정보도 함께 입력받는 구조를 채택하고 있다.

 

현재 주문 처리 흐름은

주문 ➡️ 재고 확인 ➡️ 결제 ➡️ 재고 차감 ➡️ 배송 ➡️ 주문 완료

순으로 진행된다.

 

여기서 맞닥뜨렸던 고민이 있었다. 

"배송 정보는 결제가 완료되어야만 유효한데, 주문 생성 시점에서 이 정보를 어떻게 안전하게 처리할 수 있을까?"

 

예를 들어, 사용자가 주문을 시작하면서 배송지 정보를 함께 입력했다고 해보자. 그런데 결제를 하지 않고 중간에 이탈해버린다면?

 

결과적으로 결제가 완료되지 않은 주문과 배송지 정보가 DB에 불필요하게 영구 저장될 수 있다. 사용자 수가 많아질수록, 예컨대 100명이 결제 없이 이탈한다면 그만큼 불필요한 주문 데이터와 함께 민감한 개인정보(주소, 연락처 등)가 DB에 쌓이게 된다. 이는 데이터 정합성과 개인정보 보호 측면 모두에서 문제가 될 수 있다.

 

따라서, 우리는 결제가 아직 완료되지 않은 상태에서 입력받은 배송 정보를 어디에 저장하고 어떻게 처리할지에 대한 설계 고민이 필요했다.

// OrderCreateRequest 예시
{
  "orderDetails": [
    {
      "productId": "dd3e9605-a713-48bf-b87e-aa93393412f8",
      "orderQuantity": 1
    },
    {
      "productId": "f3de0033-7e8e-40a6-9b1d-77e6f3712be8",
      "orderQuantity": 1
    }
  ],
  "deliveryInfo": {
    "deliveryRequirement": "문 앞에 놓아주세요",
    "recipientName": "홍길동",
    "address": "서울특별시 중구 을지로 100",
    "contact": "010-1234-5678"
  }
}

 

💡고려했던 기술 선택지

1. 주문 엔티티에 배송 정보를 추가해 함께 저장하는 방법

  • 장점
    • 배송 정보가 주문과 함께 영구적으로 DB에 저장되므로, 별도의 저장소를 두지 않아도 관리가 쉽다.
  • 단점
    • 배송과 주문 모듈 간의 강한 결합이 생긴다.
    • 그로 인해 MSA 환경에서 모듈 간 독립성이 깨지고, 도메인 분리나 확장이 어려워진다.

2. 결제 요청과 배송 요청을 동시에 처리하는 방법 (Kafka 활용)

  • 장점
    • Kafka를 이용한 비동기 처리를 통해, 각 모듈 간 실시간 데이터 전송이 가능해진다.
  • 단점
    • 결제가 실패해도 배송 요청이 전송될 수 있다. (= 결제 완료 여부와 상관없이 배송 처리가 진행될 위험이 있음)
    • Kafka를 사용한 비동기 메시징의 특성상 정확한 순서 보장이나 데이터 일관성 확보가 어렵다.

3. 배송 정보를 Redis에 임시 저장하는 방법 (✅ 선택)

  • 장점
    • 결제가 완료되기 전까지 배송 데이터를 안전하게 임시 보관할 수 있다.
    • Redis의 빠른 속도와 TTL 설정으로 불필요한 데이터를 자동으로 정리할 수 있다.
    • 주문/배송 모듈 간 결합도를 낮춰 모듈 구조를 유연하게 설계할 수 있다.
  • 단점
    • Redis에 장애 발생 시 임시 저장된 데이터의 유실 위험이 존재한다.
    • 이런 문제를 방지하기 위해 마스터-슬레이브 구성이나 클러스터링과 같은 고가용성 설계가 필요하다.

 

💡선택한 전략 : Redis에 배송 정보 임시 저장 

이 방식은 다음과 같은 이유로 선택하게 되었다:

  • MSA 구조에서 각 도메인의 독립성을 유지할 수 있고, 주문 및 배송 데이터의 결합을 피하면서 유연한 시스템 구성이 가능하다.
  • 결제 완료 이후에만 배송 처리를 시작함으로써 데이터 일관성과 서비스 흐름의 명확한 분리를 달성할 수 있다.
  • 마스터-슬레이브 패턴을 구현하면 데이터 손실 문제도 해결할 수 있다.

배송 정보는 주문 생성 시 바로 DB에 영구 저장되지 않는다. 대신, Redis에 orderId를 Key로 하여 임시 저장되고, 결제 완료 후에만 실제 배송 모듈로 전달된다.

 

 

✏️구현 과정

1. 주문 생성 시 배송 정보 임시 저장

  • 주문 생성 API 호출 시 배송 정보를 Redis에 저장한다.
  • Key는 orderId 로 하고, TTL (Time to Live) 을 설정하여 5분이 지나면 자동 삭제되도록 구성한다.
  • 즉, 임시로 저장한 배송 정보도 함께 삭제되므로 DB 정합성과 보안 모두 유지할 수 있다.
// 배송정보 Redis 저장
String redisKey = orderId.toString();
String deliveryInfoJson = EventSerializer.serialize(deliveryInfo);
redisTemplate.opsForValue()
	.set(redisKey, deliveryInfoJson, 300, TimeUnit.SECONDS); // 5분 TTL 설정

 

2. 결제 완료 시 배송 처리

  • 결제가 성공하면 Kafka를 통해 payment-create-response 이벤트를 수신한다.
  • Redis에서 해당 orderId의 배송 정보를 조회한 후, 배송 모듈로 전달하고 Redis에서는 데이터를 삭제한다.

   2-1. Kafka Consumer

// OrderMessagingConsumerService.java
@KafkaListener(topics = "payment-create-response", groupId = "commerce-service")
public void listenPaymentCreateResponse(String message) {
    PaymentCreateResponseEvent event = EventSerializer.deserialize(message, PaymentCreateResponseEvent.class);
    UUID orderId = event.getOrderId();
    UUID paymentId = event.getPaymentId();

    orderEventService.handlePaymentComplete(orderId, paymentId);
}

 

   2-2. 결제 완료 이벤트 처리

// OrderEventService.java
@RequiredArgsConstructor
@Service
public class OrderEventService {

    private final OrderMessagingProducerService orderMessagingProducerService;
    private final KafkaTemplate<String, Object> kafkaTemplate;

    /**
     * payment-create-response
     */
    @Transactional
    public void handlePaymentComplete(UUID orderId, UUID paymentId) {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new CustomException(CommerceErrorCode.ORDER_NOT_FOUND));

        // 재고 차감
        decreaseStockForOrder(order);
        // 결제 ID 지정
        order.assignPaymentId(paymentId);
        // 주문 상태 저장
        orderRepository.save(order);
        // 배송 생성 요청
        messagingProducerService.sendDeliveryCreateRequest(orderId, order.getUsername());
    }
}

 

   2-3. Kafka Producer (배송 요청)

// OrderMessagingProducerService.java
@RequiredArgsConstructor
@Service
public class OrderMessagingProducerService {

    private final KafkaTemplate<String, String> kafkaTemplate;
    private final TemporaryStorageService temporaryStorageService;

    /**
     * Kafka로 배송 생성 요청을 전송하는 메서드
     */
    public void sendDeliveryCreateRequest(UUID orderId, String username) {
        // Redis에 저장된 배송 정보 조회
        OrderCreateRequestDto.DeliveryInfo deliveryInfo = temporaryStorageService.getDeliveryInfo(orderId);

        // 배송 생성 이벤트 객체 생성
        DeliveryCreateRequestEvent event = new DeliveryCreateRequestEvent(
                orderId,
                deliveryInfo.getDeliveryRequirement(),
                deliveryInfo.getRecipientName(),
                deliveryInfo.getAddress(),
                deliveryInfo.getContact(),
                username
        );

        // delivery-create-request 메시지 전송
        sendMessage(deliveryCreateRequestTopic, event);
        // Redis에 저장된 배송 정보 삭제
        temporaryStorageService.removeDeliveryInfo(orderId);
    }
}

 

이 구조를 통해 주문 생성 시 임시 저장한 배송 정보를 결제 성공 시에만 배송 처리에 활용할 수 있고, Redis TTL 설정과 결합해 보안성과 데이터 정합성도 확보할 수 있다.


3. 데이터 소실 방지 (Redis 고가용성 구성)

  • 비즈니스상 중요한 정보이기 때문에, Redis에 저장된 데이터도 복제 및 장애 복구 전략을 갖춰야 한다.
    • Redis 장애에 대비하여 Master-Slave 패턴 또는 Redis Clustering을 활용한다.

 

✏️실제 서비스 적용 시 고려사항

1. 적절한 TTL 시간 설정

  • TTL을 너무 짧게 설정하면 사용자가 결제를 완료하기 전에 배송 정보가 삭제될 수 있다.
  • 반대로 너무 길면 메모리를 불필요하게 점유하므로, 평균 결제 완료 시간 기준으로 적절히 조절해야 한다.

2. Redis 장애 시 복구 전략

  • 위에서 언급했다시피, Redis는 메모리 기반 저장소이므로 장애 시 데이터가 유실될 수 있다.
  • Redis Sentinel이나 Cluster를 통해 복제 및 자동 Failover 환경을 구성해 데이터 손실을 최소화해야 한다.

3. 데이터 무결성

  • 결제 완료 이벤트와 배송 요청 로직 간 순서가 어긋나면 문제를 일으킬 수 있으므로, 이벤트 순서 제어 및 재시도 로직이 필요할 수 있다.
  • Kafka 소비자에서 처리 실패 시 재시도를 위한 DLT(Dead Letter Topic) 등을 고려할 수 있다.

 

✔️마무리

이번 포스팅에서는 주문 생성 시 함께 들어오는 배송 정보를 어떻게 안전하고 유연하게 처리할 수 있을지를 고민한 과정을 공유했다.

핵심을 요약해보면 아래와 같다.

" 배송 정보는 결제가 완료되어야 유효하며,
결제 전에 이를 DB에 저장하면 정합성 문제와 불필요한 데이터가 발생할 수 있으므로,
TTL이 설정된 Redis에 임시 저장 후, 일정 시간 내 결제가 완료되지 않으면 자동 취소 처리한다. "


이 방법은 모듈 간 결합도를 낮추면서도, 비즈니스 흐름을 안전하게 분리할 수 있는 전략이 되었다. 이후엔 Redis 마스터-슬레이브 구조를 구축하고 Sentinel을 함께 적용했는데, 이 방법까지 포스팅할 예정이다.

 

땡큐포와칭!

고민의 흔적


다음 글

[프로젝트/구현] Redis Replication 마스터-슬레이브 구조를 통한 분산 처리 적용 과정

 

[프로젝트/구현] Redis Replication 마스터-슬레이브 구조를 통한 분산 처리 적용 과정

이전 글2025.05.01 - [Project/대용량 트래픽 프로젝트] - [기술적 의사결정] MSA 환경에서 배송 정보 임시 저장소로 Redis를 사용한 이유 [기술적 의사결정] MSA 환경에서 배송 정보 임시 저장소로 Redis를

developer-jinnie.tistory.com