자원 공유 시 발생하는 문제인 동시성 문제(concurrency issue)에 대해 알아본다.
여러 스레드 및 애플리케이션에서 하나의 자원을 동시에 사용하려고 하면 경쟁 상태(race condition)가 발생한다. 이를 동시성 문제라고 하고 이런 상황이 발생되지 않는 성질을 스레드 안전성(thread-safety)라고 한다. 이를 달성하기 위한 방법에 대해 살펴보려고 한다.
Java API를 이용한 해결 방안
- synchronized - 메서드나 코드 블록에 synchronized 예약어를 사용하여 락을 걺
- 하나의 프로세스에서만 락이 가능하고 여러 프로세스 또는 앱에서 접근 시 사용 불가
- 따라서, 여러 애플리케이션 혹은 서버가 데이터에 접근해야 하는 실무 환경에서는 거의 사용되지 않음
Database(RDBMS)를 이용한 해결 방안
- pessimistic lock - 실제로 데이터에 락을 걸기
- 락을 걸면 다른 트랜잭션에서는 락 해제 전까지 접근 불가.
- 데드락 발생 가능성 있으므로 주의해서 사용해야 함
- SQL의 'for update' 또는 스프링 데이터 @Lock 애너테이션(LockModeType.PESSIMISTIC_WRITE)
- 충돌이 빈번하면 optimistic 보다 성능 좋음. 데이터 정합성 보장.
- 별도 락 생성으로 성능 감소
- optimistic lock - 실제 락이 아닌 "버전" 사용
- 버전이 다르면 수정 실패
- 버전 칼럼 추가하고 javax.persistence.Version(혹은 jakarta.persistence.Version) 애너테이션 사용. 쿼리 메서드에 스프링 데이터 @Lock 애너테이션(LockModeType.OPTIMISTIC)
- 별도의 락을 설정하지 않아 성능상 이점 있음
- 수정 실패 시 별도의 재시도 로직 필요
- 충돌이 빈번하게 일어나지 않을 것으로 예상되면 optimistic lock을 더 권장
- named lock - 이름을 가진 메타데이터 락
- 트랜잭션 끝나도 락이 해제되지 않으므로 별도로 해제해줘야 함
- 데이터가 아닌 별도의 공간에 락 생성
- MySql - get_lock() 명령어로 락 획득, release_lock()로 해제
- 락 생성 쿼리는 별도의 데이터 소스 사용해야 커넥션 풀 부족 현상 없음
- 주로 분산 락 구현 시 사용
- pessimistic lock보다 타임아웃 구현 용이
- 구현 방법 복잡
Redis를 활용한 해결 방안
- 분산 락 활용
- Lettuce 라이브러리
- setnx 명령어 - set if not exist. 기존 값이 없을 때만 set.
- 락 가능한지 확인하면서 시도
- spin lock 방식 -> 재시도 로직 작성 필요
- 구현이 간단한 편
- spin lock 방식이므로 Redis에 부하를 줄 수 있으므로 요청 간격 두어야 함
- Redisson 라이브러리
- pub-sub 기반 락 구현 - 채널을 생성하고 락 점유 중인 스레드가 락 획득 대기 중인 스레드에게 해제 알림
- Lettuce는 계속 락 획득 시도하지만, Redisson은 한 번 혹은 여러 번만 시도
- pub-sub 기반이므로 Redis 부하 줄여줌
- 구현이 복잡한 편
- 별도 라이브러리 사용 필요(스프링은 기본적으로 Lettuce 라이브러리 사용)
- 재시도가 필요하지 않다면 Lettuce가 유리, 재시도가 필요하면 Redisson이 유리
RDBMS vs. Redis
- RDBMS
- 이미 RDBMS 사용 중이라면 별도 비용 필요 없음
- 일정 수준 트래픽까지는 ok
- Redis 보다 성능이 좋지 않음
- Redis
- Redis 구축 비용 있음
- Redis 보다 성능 좋음
'👨🏫일문일답' 카테고리의 다른 글
AJAX 사용 시 CSRF 토큰 쿠키 httpOnly 옵션 사용 여부? (0) | 2024.03.17 |
---|---|
무중단 배포(zero-downtime deployment) (1) | 2024.03.04 |
프로세스 스케쥴링 방식 (0) | 2024.01.29 |
프로세스 스케쥴링 (0) | 2024.01.29 |
트랩 (0) | 2024.01.29 |