전자 결재 시스템
상급자가 모바일 앱에서 승인·반려하는 전자 결재 시스템
아키텍처
개요
- 기안자가 결재를 상신하고 승인권자가 모바일에서 승인·반려하는 전자 결재 시스템
- 동일 결재에 승인·반려 요청이 동시에 들어와도 하나만 상태를 변경하도록 DB에서 충돌 제어
문제
- 여러 승인권자가 동일 결재를 동시에 처리하면서 Lost Update 발생
- SELECT로 상태를 조회한 뒤 UPDATE하는 사이 다른 요청이 상태를 변경할 수 있는 구조
해결 전략
WHERE status = '대기'조건을 포함한 단일 UPDATE 사용- MySQL UPDATE가 조건에 맞는 row에 Record Lock을 걸어 동시 요청을 직렬화
- affected rows가 0이면 이미 처리된 결재로 판단
기술 선택 이유
-
Redis 분산 락 — 제외
- 락 획득·해제·TTL 관리가 추가되고 일관성 제어 지점이 DB와 Redis로 분산됨
-
비관적 락 (
SELECT ... FOR UPDATE) — 제외- 행을 미리 선점해 확실하지만, 경합 시 대기 시간이 길어지고 커넥션 점유가 늘어남
-
낙관적 락 — 제외
- version 컬럼 추가와 충돌 시 재시도·예외 변환 비용이 필요
-
조건부 단일 UPDATE — 채택
- 별도 락 저장소나 컬럼 없이 한 쿼리의 affected rows로 충돌 판정
검증
테스트 설정
- 단일 애플리케이션 프로세스의 CountDownLatch 기반 통합 테스트로 동일 결재 건에 다중 스레드 동시 진입 재현
- 승인·반려 요청을 별도 트랜잭션으로 같은 시점에 실행해 DB 경합 재현
측정 결과
- 두 요청 중 먼저 상태를 변경한 1건만 성공, 후속 요청은 affected rows 0으로 예외 처리
- 최종 상태가 승인 또는 반려 중 하나로 남아 후속 요청의 덮어쓰기 차단 확인