Skip to content

[#65] fix: 기사 중복 배차 할당 문제 해결#66

Merged
Leesowon merged 2 commits intodevfrom
fix/#65
Feb 20, 2026
Merged

[#65] fix: 기사 중복 배차 할당 문제 해결#66
Leesowon merged 2 commits intodevfrom
fix/#65

Conversation

@Leesowon
Copy link
Collaborator

@Leesowon Leesowon commented Feb 19, 2026

관련 이슈

📌 작업 개요

  • 브랜치: fix/#65
  • 문제: 한 기사가 동시에 여러 개의 ASSIGNED 배차를 가질 수 있는 중복 배차 버그 수정
  • 원인: Transporter 엔티티에 대한 동시성 제어 부재, 배차 할당 시 기사 상태 검증 누락
  • 해결: 비관적 락(Pessimistic Lock) + 애플리케이션 레벨 검증 + DB UNIQUE 제약조건 추가

주요 변경 사항

1️⃣ Repository 레이어

DispatchRepository.java

// 중복 배차 시 최신순으로 일관된 결과 반환
@Query("SELECT d FROM Dispatch d WHERE d.transporter.id = :transporterId AND d.status = :status ORDER BY d.assignedAt DESC LIMIT 1")
Optional<Dispatch> findFirstByTransporterIdAndStatusOrderByAssignedAtDesc(...)

TransporterRepository.java
// 동시성 제어를 위한 비관적 락 메서드 추가
@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints({@QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000")})
Optional<Transporter> findByIdWithPessimisticLock(Long transporterId)

2️⃣ Service 레이어

DispatcherService.assignDispatch()

  • Transporter 조회 시 비관적 락 적용
  • 이미 배차중(dispatchStatus == DISPATCH)인 기사에게 추가 배차 시도 시 예외 발생
  • 동시 요청에도 한 기사당 하나의 배차만 할당 보장

DispatcherService.getCurrentDispatch()

  • 정렬된 Repository 메서드 사용으로 비결정적 동작 제거
  • 중복 배차가 있어도 일관된 결과 반환 (최신 배차 우선)

3️⃣ DB 마이그레이션

20260215_fix_duplicate_assigned_dispatches.sql

  • 기존 중복 데이터 정리 (가장 최근 배차만 ASSIGNED 유지, 나머지 CANCELED)
  • 기사 상태(dispatch_status) 동기화
  • UNIQUE INDEX 생성: idx_unique_assigned_transporter
  • 백업 테이블 자동 생성 및 검증 로직 포함

4️⃣ 문서화

  • CONCURRENT_DISPATCH_ISSUE.md: 문제 분석, 해결 방안, 테스트 시나리오 상세 기술
  • db-migrations/README.md: 마이그레이션 실행 가이드, 롤백 방법, 체크리스트

✨ 기타 참고 사항

⚠️ 배포 전 필수 작업

프로덕션 DB 마이그레이션 실행 필요 (1회)

1. 사전 체크 (선택, 권장)

docker exec -i mobility-api-postgres psql -U user -d mobilitydb
< db-migrations/20260215_pre_migration_check.sql

2. 마이그레이션 실행 (필수)

docker exec -i mobility-api-postgres psql -U user -d mobilitydb
< db-migrations/20260215_fix_duplicate_assigned_dispatches.sql

변경 전후 비교

Before (문제 상황)
// Transporter에 락이 없어서 동시 요청 시 중복 할당 가능
Transporter transporter = transporterRepository.findById(transporterId)
.orElseThrow(() -> new GlobalException(ResultCode.NOT_FOUND_USER));

// 상태 검증 없이 바로 할당
dispatch.assignDispatch(transporter);
transporter.changeDispatchStatus(DispatchStatus.DISPATCH);

After (해결)
// 1. 비관적 락으로 동시성 제어
Transporter transporter = transporterRepository.findByIdWithPessimisticLock(transporterId)
.orElseThrow(() -> new GlobalException(ResultCode.NOT_FOUND_USER));

// 2. 상태 검증 추가
if (transporter.getDispatchStatus() == DispatchStatus.DISPATCH) {
throw new GlobalException(ResultCode.TRANSPORTER_ALREADY_DISPATCHED);
}

// 3. 안전하게 할당
dispatch.assignDispatch(transporter);
transporter.changeDispatchStatus(DispatchStatus.DISPATCH);

테스트 시나리오

  1. 중복 배차 방지 확인

기사 1번에게 배차 할당 (성공)

  curl -X PATCH http://localhost:8080/mobility/api/v1/transporter/dispatch-assign/100 \
    -H "X-Temp-User-Id: 1"

같은 기사에게 또 다른 배차 할당 시도 (실패해야 함)

  curl -X PATCH http://localhost:8080/mobility/api/v1/transporter/dispatch-assign/200 \
    -H "X-Temp-User-Id: 1"

예상 응답: {"code": 3004, "message": "이미 배차중인 오더가 있습니다."}

  1. 정상 흐름 확인
1. 배차 할당
  curl -X PATCH .../dispatch-assign/100 -H "X-Temp-User-Id: 1"
 curl -X GET .../current-dispatch -H "X-Temp-User-Id: 1"
  # 3. 배차 완료
  curl -X PATCH .../dispatch-complete/100 -H "X-Temp-User-Id: 1"
  # 4. 새로운 배차 할당 (성공해야 함)
  curl -X PATCH .../dispatch-assign/200 -H "X-Temp-User-Id: 1"

영향 범위

  • API 엔드포인트:
    • PATCH /api/v1/transporter/dispatch-assign/{dispatchId} (동작 개선)
    • GET /api/v1/transporter/current-dispatch (비결정적 동작 수정)
  • 데이터베이스:
    • dispatch 테이블에 UNIQUE INDEX 추가
    • 기존 중복 데이터 정리 (CANCELED 처리)
  • 성능: 비관적 락으로 인한 미미한 지연 가능 (3초 타임아웃 설정)

Breaking Changes

없음 (기존 API 동작 유지, 에러 케이스만 추가)

롤백 방법

문제 발생 시:
docker exec -i mobility-api-postgres psql -U user -d mobilitydb
< db-migrations/20260215_rollback_duplicate_fix.sql

추가 정보

  • 에러 코드: ResultCode.TRANSPORTER_ALREADY_DISPATCHED (code: 3004) - 기존에 정의됨
  • 백업 테이블: 마이그레이션 실행 시 dispatch_backup_20260215 자동 생성
  • 1주일 후: 백업 테이블 수동 삭제 권장

✅ 체크리스트

  • PR 템플릿에 맞추어 작성했어요.
  • PR에 적절한 라벨을 선택했어요. (bug, enhancement)
  • 변경 내용에 대한 테스트를 진행했어요.
  • application.yml 파일을 수정했다면, Notion에 업로드, github security 수정 및 공유했어요. (해당 없음)
  • 로컬 서버에서 정상 동작을 확인했어요. (main, test)
  • 불필요한 코드는 삭제했어요.

@Leesowon Leesowon self-assigned this Feb 19, 2026
@Leesowon Leesowon added the 🐞 Bug 뭐가 잘 안 돼요. label Feb 19, 2026
@Leesowon Leesowon requested a review from jsgjsg February 19, 2026 06:06
@Leesowon Leesowon merged commit 3b8d77a into dev Feb 20, 2026
1 check passed
@Leesowon Leesowon deleted the fix/#65 branch February 20, 2026 00:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞 Bug 뭐가 잘 안 돼요.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant