개요
웹 애플리케이션에서 데이터 무결성을 보장하기 위해서는 동시성 제어와 중복 요청 방지가 필수적입니다. 이를 위해 락(Lock)과 멱등성 키(Idempotency Key)라는 두 가지 접근 방식이 사용되는데, 각각이 해결하는 문제와 적용 시점이 다릅니다. 이번 글에서는 두 방식의 차이점과 적절한 활용 방안을 소개합니다.
락(Lock)과 멱등성 키의 기본 개념
락(Lock): 동시성 제어
락은 여러 사용자가 동시에 같은 자원에 접근할 때 데이터 일관성을 보장하는 메커니즘입니다.
해결하는 문제:
- 여러 사용자의 동시 접근으로 인한 데이터 무결성 위반
- 공유 자원의 상태 불일치
- Race Condition 방지
멱등성 키(Idempotency Key): 중복 요청 방지
멱등성 키는 동일한 요청이 여러 번 수행되어도 결과가 동일하도록 보장하는 메커니즘입니다.
해결하는 문제:
- 클라이언트의 실수로 인한 중복 요청
- 네트워크 타임아웃으로 인한 자동 재시도
- 사용자의 중복 클릭
실제 시나리오를 통한 비교
시나리오 1: 모임 정원 마감 상황 (락이 필요)
상황: 정원 10명 모임에 현재 9명이 참가한 상태
시점: 10:00:00에 A, B 사용자가 동시에 참가 신청
락 없는 경우:
- A 사용자: 현재 인원 9명 확인 → 참가 승인
- B 사용자: 현재 인원 9명 확인 → 참가 승인
- 결과: 정원 초과 (11명)
락 있는 경우:
- A 사용자: 락 획득 → 참가 승인 → 락 해제
- B 사용자: 락 대기 → "정원 초과" 오류
- 결과: 정원 준수 (10명)
시나리오 2: 사용자 중복 클릭 (멱등성 키가 필요)
상황: 사용자가 모임 참가 버튼을 빠르게 3번 클릭
멱등성 키 없는 경우:
- 1번째 요청: 참가 성공
- 2번째 요청: "이미 참가한 모임" 오류 응답
- 3번째 요청: "이미 참가한 모임" 오류 응답
- 결과: 클라이언트가 오류 메시지 처리 필요
멱등성 키 있는 경우:
- 1번째 요청: 참가 성공
- 2번째 요청: 1번째와 동일한 성공 응답 반환
- 3번째 요청: 1번째와 동일한 성공 응답 반환
- 결과: 일관된 성공 응답
각 방식의 구현 접근법
락(Lock) 구현
@Transactional
public ParticipantResponseDto createParticipant(Long userId, Long meetingId) {
// 비관적 락으로 Meeting 엔티티 잠금
Meeting meeting = meetingRepository.findByIdWithLock(meetingId);
if (meeting.getCurrentParticipantsCount() >= meeting.getMaxParticipantsCount()) {
throw new MeetingIsFullException();
}
// 원자적으로 참가자 수 증가
meeting.addMeetingParticipant();
return addParticipant(meetingId, userId);
}
멱등성 키 구현
@PostMapping("/{meetingId}/participants")
@Idempotent(keyHeader = "Idempotency-Key")
public ResponseEntity<ParticipantResponseDto> createParticipant(
@PathVariable Long meetingId,
@AuthenticationPrincipal AuthUser authUser,
HttpServletRequest request) {
String idempotencyKey = request.getHeader("Idempotency-Key");
// 이전 요청 결과가 있으면 그대로 반환
if (hasProcessedBefore(idempotencyKey)) {
return getPreviousResult(idempotencyKey);
}
// 새로운 요청 처리
return processNewRequest(meetingId, authUser.getId(), idempotencyKey);
}
두 방식의 조합 활용
완전한 데이터 무결성 보장
실제 운영 환경에서는 두 방식을 함께 사용하여 완벽한 데이터 무결성을 보장할 수 있습니다:
@PostMapping("/{meetingId}/participants")
@Idempotent // 중복 요청 방지
public ResponseEntity<ParticipantResponseDto> createParticipant(...) {
// 비관적 락으로 동시성 제어
return executeWithRetry(() -> addParticipantWithLock(meetingId, userId));
}
@Transactional
public ParticipantResponseDto addParticipantWithLock(Long meetingId, Long userId) {
Meeting meeting = meetingRepository.findByIdWithLock(meetingId);
// 비즈니스 로직 처리
return processParticipation(meeting, userId);
}
각 방식의 적용 기준
락(Lock)이 필요한 경우
- 공유 자원의 동시 접근 제어가 필요한 상황
- 데이터의 일관성이 비즈니스 로직에 중요한 경우
- 카운터, 재고 관리, 정원 관리 등
멱등성 키가 필요한 경우
- 사용자 인터페이스에서 중복 클릭이 발생할 수 있는 상황
- 네트워크 불안정으로 재시도가 필요한 환경
- 결제, 주문 등 중복 수행되면 안 되는 중요한 작업
성능과 복잡성 고려사항
락(Lock)의 특징
- 성능: 동시성 제어로 인한 대기 시간 발생 가능
- 구현: 데이터베이스 수준에서 지원되어 상대적으로 단순
- 확장성: 트래픽 증가시 병목 지점이 될 수 있음
멱등성 키의 특징
- 성능: 매 요청마다 키 검증을 위한 추가 조회 필요
- 구현: 별도 저장소와 키 관리 로직 필요
- 확장성: 키 저장소 용량 관리 및 만료 정책 필요
실무 적용 권장사항
우선순위 기반 접근
- 필수 적용: 데이터 무결성이 중요한 핵심 기능에 락 적용
- 선택적 적용: 사용자 경험 개선을 위해 주요 API에 멱등성 키 적용
- 점진적 확장: 모니터링 결과를 바탕으로 적용 범위 확대
비용 대비 효과 분석
- 락: 데이터 정합성 보장이라는 명확한 비즈니스 가치
- 멱등성 키: 사용자 경험 개선과 고객 지원 비용 절감 효과
대안적 접근 방법
멱등성 키 구현이 복잡한 경우, 클라이언트 사이드에서의 중복 방지 로직도 고려할 수 있습니다:
- 버튼 비활성화를 통한 중복 클릭 방지
- 요청 상태 관리를 통한 중복 요청 차단
- 로딩 상태 표시를 통한 사용자 가이드
결론
락과 멱등성 키는 각각 다른 문제를 해결하는 독립적인 메커니즘입니다. 락은 동시성 제어를 통한 데이터 무결성 보장이 주목적이며, 멱등성 키는 중복 요청 방지를 통한 사용자 경험 개선이 주목적입니다.
실무에서는 비즈니스 요구사항과 시스템의 특성을 고려하여 적절한 방식을 선택하거나 두 방식을 조합하여 사용하는 것이 권장됩니다. 특히 데이터 일관성이 중요한 시스템에서는 락을 우선적으로 적용하고, 사용자 경험 개선이 필요한 부분에서는 멱등성 키를 추가로 고려하는 단계적 접근이 효과적입니다.
'Spring 7기 프로젝트 > 모임 플렛폼 프로젝트' 카테고리의 다른 글
| 모놀리식 아키텍처에서 MSA로의 전환: ALB를 활용한 서비스 분리 과정 (1) | 2025.07.31 |
|---|---|
| AWS EC2 vs ECS: 컨테이너 배포 전략과 구현 (2) | 2025.07.31 |
| Elasticsearch를 활용한 모임 검색 서비스 설계 (0) | 2025.07.28 |
| ELK 스택을 활용한 애플리케이션 모니터링 시스템 구축 (1) | 2025.07.28 |
| 로드밸런서 완벽 가이드: 동작 원리부터 MSA 전환 전략까지 (3) | 2025.07.25 |