동시성 문제 테스크 도구
하나의 트랜잭션에 동시에 접근한다면?
Talend API Tester
https://chromewebstore.google.com/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm
Talend API Tester - Free Edition - Chrome 웹 스토어
Visually interact with REST, SOAP and HTTP APIs.
chromewebstore.google.com
동시성을 테스트 하기 위해 두개의 http 요청 프로그램을 사용하였습니다.
그리고 인텔리제이의 디버깅 시스템을 이용해서 각 스레드별 브레이크 포인트 실행
두 개의 요청을 보내고 순차적으로 각 스레드에 걸린 브레이크 포인트를 실행하면 동시성 상황을 만들 수 있습니다.
결과는 2개의 count 가 되어야 하는데 1로 나옵니다.
해결 방법 1 - 자바 문법
자바 문법만으로 해결하는 방법
단점은 성능이 많이 떨어짐
이 네모 영역을 하나의 스레드만 실행할 수 있도록 자바 문법을 사용해주는 것입니다.
결과
해결 방법 2 - 낙관적 락
✅ 1. 낙관적 락(Optimistic Lock)
🔹 개념
- **"충돌 가능성이 적을 것"**이라고 가정하고 사용.
- 데이터 충돌이 발생하지 않는다면 별다른 락을 사용하지 않으므로 성능이 좋음.
- 트랜잭션 완료 시점에서 버전(@Version)을 체크하여 충돌을 감지.
- 충돌이 발생하면 예외(OptimisticLockException)가 발생하고 재시도가 필요.
🔹 사용 사례
✔ 동시 수정 가능성이 낮은 경우
✔ 읽기 비율이 높고, 쓰기 비율이 낮은 경우
✔ API 트래픽이 많아 DB 락을 최소화해야 하는 경우
✔ 쇼핑몰에서 상품 재고 수정 (사용자가 장바구니에 담고 일정 시간 후 결제하는 경우)
✅ @Version 필드를 활용하여 트랜잭션 완료 시 충돌을 감지.
🔹 낙관적 락 사용 예제
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
....
@Version // ✅ 낙관적 락 적용
private Integer version;
}
이것만 추가해 주시면 됩니다.
결과
업데이트할 때 버전 번호를 함께 저장하고, 변경 시에도 동일한 버전인지 확인하는 방식입니다.
충돌이 발생하면 예외를 던지고, 재시도를 하도록 유도하는 방식입니다.
성공
실패
즉 둘 중 하나만 성공하게 하는 것!
🔹 장점
✅ 데이터 충돌이 적다면 성능이 매우 우수
✅ 락을 걸지 않아도 되므로 데드락(Deadlock) 위험 없음
✅ DB 부하가 낮아 트래픽이 많은 서비스에서 유리
🔹 단점
❌ 동시 수정이 발생하면 재시도가 필요함
❌ 충돌이 자주 발생하는 환경에서는 비효율적
해결방법 2 - 비관적 락
✅ 2. 비관적 락(Pessimistic Lock)
🔹 개념
- **"충돌 가능성이 많을 것"**이라고 가정하고 사용.
- 트랜잭션이 시작될 때 즉시 DB에 LOCK을 걸어 다른 트랜잭션이 접근하지 못하도록 차단.
- 충돌이 발생할 가능성이 높거나, 데이터 정합성이 중요한 경우 사용.
🔹 사용 사례
✔ 동시에 여러 사용자가 같은 데이터를 수정할 가능성이 높은 경우
✔ 데이터 일관성이 절대적으로 필요한 경우 (은행 계좌, 포인트 차감 등)
✔ 낙관적 락에서 충돌이 너무 자주 발생하여 성능이 나빠지는 경우
비관적 락의 주요 종류:
- PESSIMISTIC_READ → 다른 트랜잭션이 읽기/쓰기 모두 불가능
- PESSIMISTIC_WRITE → 다른 트랜잭션이 쓰기 불가능, 읽기 가능
- PESSIMISTIC_FORCE_INCREMENT → PESSIMISTIC_WRITE + 버전 증가
🔹 비관적 락 사용 예제
public interface PostRepository extends JpaRepository<Post, Long> {
//비관적 락 : DB 단에서 LOCK
//PESSIMISTIC_WRITE : 다른 트랜잭션이 읽기 O , 쓰기 X
//PESSIMISTIC_READ : 다른 트랜잭션이 읽기 X, 쓰기 X
//다른 트랜잭션이 읽지 못하도록
@Lock(LockModeType.PESSIMISTIC_READ)
Optional<Post> findById(Long id);
...
이렇게만 추가해주시면 됩니다!
JPA 에서 비관적 락과 관련된 락 모드 3가지를 지원해주고 있습니다.
성공
실패
콘솔 화면
🔹 장점
✅ 데이터 정합성이 보장됨
✅ 충돌 발생 시 재시도 로직이 필요 없음
🔹 단점
❌ 트랜잭션이 길어질수록 DB 성능 저하
❌ 데드락(Deadlock) 위험 존재
❌ 동시 처리 성능이 낮음
✅ 3. 낙관적 락 vs 비관적 락 비교
낙관적 락 (Optimistic Lock)비관적 락 (Pessimistic Lock)
락 거는 시점 | 트랜잭션 완료 시점(버전 체크) | 트랜잭션 시작 시점(DB에서 락) |
성능 | 우수함 (락을 사용하지 않음) | 낮음 (DB에서 락을 걸어야 함) |
충돌 발생 시 | OptimisticLockException 발생 후 재시도 필요 | 충돌 발생하지 않음 (다른 트랜잭션 차단) |
데드락 위험 | 없음 | 있음 |
사용 예시 | 쇼핑몰 상품 재고 업데이트, 게시글 수정 | 은행 계좌 이체, 포인트 차감 |
✅ 4. 락보다 더 빠른 동시성 제어: 캐시 서버 활용
🔹 락 방식의 한계
- 낙관적 락: 충돌이 자주 발생하면 재시도로 인해 성능이 저하됨.
- 비관적 락: 동시 요청이 많을수록 성능 저하가 심함.
- 해결 방법: Redis 같은 캐시 서버를 활용하여 동시성을 제어.
🔹 Redis 분산 락 사용
Redis의 SETNX(Set if Not Exists) 기능을 이용하면, DB가 아닌 메모리 캐시에서 락을 관리하여 성능을 크게 개선할 수 있음.
✅ 캐시를 활용하면 DB 부하를 줄이면서도 동시성 문제를 해결 가능.
결론
✅ 1. 낙관적 락 (Optimistic Lock)
- 충돌 가능성이 적다면 낙관적 락이 더 유리
- 트랜잭션 종료 시 @Version 필드로 충돌 감지
- 읽기 트래픽이 많고 쓰기 트래픽이 적은 경우 사용
✅ 2. 비관적 락 (Pessimistic Lock)
- 충돌 가능성이 많다면 비관적 락이 필요
- 트랜잭션 시작 시 DB에서 강제로 락을 걸어 다른 트랜잭션 차단
- 은행 계좌 이체, 포인트 차감 등 강력한 데이터 정합성이 필요한 경우 사용
✅ 3. 캐시 서버 (Redis)
- 락 방식은 기본적으로 DB를 사용하므로 성능 한계가 있음
- 트래픽이 많은 경우, Redis 같은 캐시 서버를 사용하여 동시성 문제를 해결
- SETNX를 이용한 분산 락을 적용하면 DB 부하를 줄이면서 성능을 크게 개선 가능 🚀
'Server' 카테고리의 다른 글
PinPoint (0) | 2025.02.21 |
---|---|
CQRS (0) | 2025.02.19 |
웹 소켓(Web Socke!) (0) | 2025.02.17 |
멀티파트 폼 데이터(Multipart Form Data) (0) | 2025.01.11 |
Spring boot 애플리케이션 ec2에 jar로 배포 (0) | 2023.07.23 |