전자 결재 시스템
Lost Update 문제 해결기안자가 결재를 상신하고, 상급자가 모바일 앱에서 승인·반려하는 전자 결재 시스템
JavaSpringMySQL
아키텍처
개요
- 기안자가 결재를 상신하고 승인권자가 모바일에서 승인·반려 처리하는 전자 결재 시스템
- 동시에 들어온 결재 처리 요청의 정합성 보장
문제
- 현장 테스트 중 여러 승인권자가 동일 결재를 동시에 처리하자, 먼저 반영된 승인·반려 결과가 나중 요청에 덮어써지는 Lost Update가 실제로 발생
- 원인은 결재 상태 확인(SELECT)과 상태 변경(UPDATE)이 분리되어 있어, 그 사이 다른 요청이 끼어들 수 있는 구조
해결 전략
- 현재 결재 상태 조건을 포함한 단일 UPDATE 사용
- MySQL UPDATE가 조건에 매칭된 row에 Record Lock을 걸어 동시 요청을 직렬화
- UPDATE affected rows가 0이면 이미 처리된 결재로 판단
- 충돌 상황을 비즈니스 예외로 처리해 중복 승인·반려 차단
기술 선택 이유
-
조건부 UPDATE
- 비관적 락보다 Lock 점유 시간과 대기 비용 감소
WHERE status = 'WAITING'조건으로 DB의 원자적 갱신 결과 활용
-
affected rows 검증
- 별도 조회 없이 UPDATE 성공 여부로 동시성 충돌 판단 가능
- DB가 보장하는 원자성을 애플리케이션 로직에 직접 활용
-
Version 기반 낙관적 락 제외
- 별도 version 컬럼 추가와 예외 변환 비용 대비 상태 전이 조건만으로 충분
- 결재 상태 변경 대상이 단일 row라 조건부 UPDATE가 더 단순
검증
테스트 설정
- CountDownLatch 기반 테스트 코드로 동일 결재 건에 다중 스레드 동시 진입 재현
- 승인 요청과 반려 요청을 같은 시점에 출발시켜 동시 처리 상황 재현
- 먼저 도착한 요청만 성공 처리되는지 확인
- 후속 요청의 affected rows 0 반환과 예외 처리 확인
측정 결과
- Lost Update 재현 시나리오 차단
- 처리 완료 결재에 대한 중복 상태 변경 방지
- 낮은 빈도의 충돌도 승인 결과 오염으로 이어지지 않는지 확인
배운 점
- 상태 확인(SELECT)과 변경(UPDATE)을 분리하면 그 사이 다른 요청이 끼어들어 Lost Update가 발생할 수 있음
- 상태 조건을 WHERE에 담은 단일 UPDATE는 MySQL이 매칭된 row에 Record Lock을 걸어, 별도 조회 없이 원자적으로 동시성 충돌을 차단