요약

부르미와 드리미의 상호 동의가 끝나면 주문은 IN_PROGRESS로 전이되고, 매칭 엔진이 DeliveryService.startDelivery()를 호출해 DELIVERY 행을 생성한다.

기존 구조에서는 주문 상태 변경 트랜잭션이 먼저 커밋된 뒤 별도의 트랜잭션에서 배달을 생성한다. 따라서 startDelivery()의 비즈니스 검증이 실패하면 DELIVERY 행은 생성되지 않지만, 앞서 커밋된 ORDERS.order_cd = IN_PROGRESS는 되돌릴 수 없다.

해결책으로 배달 시작 전에 판단할 수 있는 비즈니스 검증을 BoormiService.confirmDreami()로 이관하고, startDelivery()는 검증이 끝난 내부 요청을 실행하는 역할만 담당하도록 결정했다.

배경

매칭 엔진의 인메모리 상태는 비동기 단일 스레드에서만 변경한다. HTTP 요청 스레드는 DB 트랜잭션을 처리한 뒤 이벤트를 발행하고, 트랜잭션이 커밋된 후에만 매칭 엔진에 액션을 제출한다.

현재 상호 동의 이후의 흐름은 다음과 같다.

부르미 확정 HTTP 요청
  └─ BoormiService.confirmDreami() [트랜잭션 A]
       ├─ 확정 대상과 주문 검증
       ├─ ORDERS: PENDING_BOORMI_CONFIRMATION → IN_PROGRESS
       ├─ MATCHING 이력 저장
       └─ BoormiConfirmedEvent 발행
            ↓ AFTER_COMMIT
MatchingService.onBoormiConfirmed()
  └─ 매칭 엔진 큐에 AcceptByBoormi 제출
       └─ MatchingService.applyAcceptByBoormi() [엔진 단일 스레드]
            ├─ 인메모리 오퍼: PENDING_BOORMI_CONFIRMATION → MATCHED
            ├─ DeliveryService.startDelivery() [트랜잭션 B]
            │    └─ DELIVERY 행 저장
            └─ 매칭 엔진의 드리미·오퍼·그룹 정리

MatchingStartRequestedEvent는 주문 접수 트랜잭션이 끝난 뒤 매칭을 시작하기 위한 이벤트다. 상호 동의 후 배달을 시작하는 흐름에서는 BoormiConfirmedEvent가 사용된다.

문제 상황

기존 startDelivery()DELIVERY 행을 저장하기 전에 다음 비즈니스 조건을 다시 검사한다.

@Transactional
public void startDelivery(UUID orderId, UUID dreamiId, UUID boormiId) {
    Orders order = orderService.getOrder(orderId);

    if (order.getOrderCd() != OrderCd.IN_PROGRESS) {
        throw new BusinessException(DeliveryErrorCode.DELIVERY_START_NOT_ALLOWED);
    }
    if (!userService.getUserInfo(dreamiId).isDreami()) {
        throw new BusinessException(DeliveryErrorCode.DREAMI_NOT_ACTIVATED);
    }

    Delivery delivery = deliveryRepository.save(Delivery.create(orderId, dreamiId, boormiId));
    alarmBoormiDeliveryStartedBySSE(delivery);
    alarmDreamiDeliveryStartedBySSE(delivery);
}

예를 들어 확정된 사용자가 활성 드리미가 아니면 다음과 같은 불일치가 생긴다.

구분 결과
주문 트랜잭션 A 이미 커밋되어 ORDERS.order_cdIN_PROGRESS로 유지됨
배달 트랜잭션 B DREAMI_NOT_ACTIVATED 예외로 롤백되어 DELIVERY 행이 없음
매칭 엔진 오퍼는 이미 MATCHED로 바뀌었지만 예외 때문에 후속 인메모리 정리가 실행되지 않음

즉, DB에는 "진행 중인 주문이지만 배달 정보가 없는 상태"가 남고, 매칭 엔진에도 완료되지 않은 오퍼와 그룹이 잔존할 수 있다.

원인

핵심 원인은 하나의 비즈니스 상태 전이가 서로 다른 두 트랜잭션과 인메모리 상태 변경에 걸쳐 있다는 점이다.