Service
Cabi - 사물함 대여 서비스

동시성 문제 제어

진행일
2022/11/07
태그
Spring
Spring Boot
MariaDB
InnoDB
DB
Transaction
Isolation Level
Concurrency Control
기여도
33%
상태
완료
역할
백엔드 개발자

배경 및 문제

※ 테이블 관계: user 1 n lent n 1 cabinet
Cabi 서비스에서는 사용자 증가에 따른 사물함 수요 증가 문제에 직면했습니다.
여러 사용자가 하나의 사물함을 공유할 수 있도록 정책을 변경하면서, 대여와 반납 과정에서 동시 요청 처리 시 발생하는 동시성 문제가 나타났습니다.
여러 사용자가 동시에 요청을 보내는 경우, 정원을 넘어선 요청이 처리되거나 상태 값이 잘못 변경되는 동시성 문제가 발생했습니다.
이는 A와 B가 차례대로 동시에 가깝게 요청을 보냈을 때, A의 트랜잭션이 커밋되기 전에 B의 트랜잭션이 잔여 인원을 조회하여 A의 트랜잭션 처리 결과를 반영하지 못한 채로 로직이 동작하여 발생한 문제였습니다.

해결 과정

처음에는 가장 높은 격리 수준인 SERIALIZABLE을 적용하고 비관적 락을 이용하여 동시성 문제를 관리했습니다. 데드락 문제가 발생하기도 했지만 X Lock을 사용한 후, 다른 리소스에 대해 추가적인 X Lock을 하지 않도록 로직을 개선함으로써, 상호 배제를 통해 문제를 해결할 수 있었습니다.
이 접근은 동시 대여/반납 상황이 빈번하지 않았기 때문에 효과적이었습니다. 그러나 사물함을 정기적으로 동시에 개방하는 새로운 티켓팅 방식 도입이 예정되어 있었기 때문에, 높은 트래픽 상황에서 데이터베이스에 병목 현상이 발생할 가능성이 높아졌습니다.

최종 해결 방안

이에 격리 수준을 낮추면서도 동시성 문제를 제어할 수 있는 방안을 모색했습니다.
격리 수준을 Repeatable Read로 낮추고 버전 컬럼을 추가하여 낙관적 락을 도입함으로써 잠재적인 성능 문제를 해결할 수 있을 것으로 기대했습니다.
lent 테이블에 버전용 필드인 user_count (대여 중인 유저 수)를 추가하고, 커밋 시점에 버전 충돌이 발생하면 트랜잭션을 롤백 시키도록 구현했습니다.
cabinet과 lent 테이블의 연관관계를 바탕으로 lent 테이블의 row 개수를 count 할 수 있기 때문에 user_count 필드를 추가하는 것은 정규화에 위배되는 방식이지만 낙관적 락을 도입함으로써 얻는 이점이 더 크다고 판단했습니다.
결론적으로 동시성 문제를 제어하면서도 성능 문제를 최소화시킴으로써 공유 사물함 서비스를 안정적으로 운영할 수 있었습니다.

느낀점

최종적으로 낙관적 락을 통해 문제를 해결했음에도 불구하고, 티켓팅 방식이 도입될 경우 특정 리소스에 대한 경쟁이 심화되어 충돌 해결이나 요청 재시도로 인한 오버헤드가 발생할 수 있어, 성능 저하 문제를 완전히 회피하기는 어려운 해결책이었던 것 같습니다.
동시성 문제를 제어하면서도 특정 리소스에 대한 트래픽이 집중되는 것을 효과적으로 관리하기 위해서는 Redis 등을 활용해 대기열을 직접 구현하는 것이 더 나은 해결책이었을지도 모른다는 아쉬움이 남습니다.

관련 아카이브

동시 대여 문제와 해결 삽질기
개요
Spring 마이그레이션 이후, 다른 팀원 분이 제시한 솔루션