서버를 운영하다 보면,
여러 클라이언트에서 동시에 들어온 DB 자원 점유 요청을 순서대로 처리해야 할 때가 있다
예를 들면 놀이공원 입장권 예매 서비스가 그렇다 (필자는 실제 예매 서비스가 어떻게 구성되어 있는지 모른다. 이후 등장하는 내용은 모두 픽션..)
입장권 데이터를 관리하는 롯데월드 서비스와 입장권 예매를 담당하는 YES24, 인터파크, 지마켓 등이 있고
각 회사는 서로 독립된 회사와 환경에서 운영되고 있다고 가정한다
예를 들어 롯데월드 입장권 예매가 12시에 오픈한다 가정하면
12시가 땡 치면 여러 티켓 발급서비스로부터 티켓 발급 요청이 동시다발적으로 발생할 것이고,
티켓 관리 서비스에서 제대로 된 설정이 이루어지지 않았다면 위 흐름과 같은 위험한 상황이 발생할 수 있다
먼저 위 상황이 왜 위험한 상황일까?
문제의 가장 큰 요점은 지마켓, 인터파크에선 입장권이 유효한 것인지 확인할 길이 없다는 것이다.
위 상황은 정확히 말하자면 Write Skew 다
Write Skew 는 이전 글 에서 설명했으니 참고하길 바란다
결과적으로 입장권 1에 대한 소유권을 지마켓이 먼저 할당받았지만,
인터파크의 발급 요청이 입장권 1에 대한 정보를 덮어씀으로써 실제 지마켓의 입장권 1은 더 이상 유효하지 않은 입장권이 되어버린 셈이다
이를 실시간으로 인지하지 못할 경우 입장권 사용 당일 위 3사 간 엄청난 혼란과 갈등을 초래할 수 있다 (생각하기도 싫다)
위와 같은 대혼돈의 카오스를 피할 수 있는 가장 간단한 방법이 무엇일까?
정답은 Hibernate에서 제공하는 QueryHint를 통해 PostgreSQL의 SKIP Lock을 유도하는 것이다
먼저 PostGreSQL의 SKIP Lock 은 데이터를 조회할 때, 잠금을 수행할 수 없는 행을 건너뛴다
예를 들어 조회 당시에 입장권 1에 lock이 잡혀있다면, 그다음 입장권인 입장권 2를 조회하는 방식이다
이를 구현하는 방법 또한 심플하다
@Lock(value = LockModeType.PESSIMISTIC_WRITE)
@QueryHints(QueryHint(name = "jakarta.persistence.lock.timeout", value = "-2")) // SKIP LOCKED
fun 사용가능한_입장권_조회()
LockMode PESSIMISTIC_WRITE와 lock timeout -2 세팅을 조합하면 PostgreSQL의 SKIP LOCK 이 동작한다 (reference)
동시성 이슈를 해결하는 다양한 방법이 있지만
Postgres에서 제공하는 Skip Lock을 hibernate에서 이미 제공하고 있고, 이를 사용하는 것이 가장 합리적이라는 판단 하에 위 방식으로 구현을 진행했다
실제 생성된 쿼리
SELECT 컬럼 FROM 테이블 WHERE 조건절 order by id asc fetch first rows only for no key update skip locked
실제 동작 흐름도
추가로 첨언할 내용으로는
SKIP LOCKED는 ONLY 일 때만 동작하고, WITH TIES 와는 함께 사용할 수 없다고 한다
위 내용을 숙지하고, 상황에 맞게 잘 사용하도록 하자
'개발 > Database' 카테고리의 다른 글
[Join Series] 매번 눈에 밟히는 그 조인 시리즈 (0) | 2025.03.18 |
---|---|
RDB 성능 개선하기 - BETWEEN vs LIKE (0) | 2025.03.08 |
[Spring/Postgres] Exclusive Lock 제대로 사용하기 (동시성 문제) (0) | 2025.02.22 |
[macOS/Intellij/Oracle] ventura 13.0 업데이트 이후 로케일을 인식할 수 없습니다.(SQL Error: 17176) - 해결 (0) | 2022.11.08 |
In-Memory DB(Redis) vs RDB(Oracle) - Performance 비교 (0) | 2022.06.11 |
댓글