동시성 이슈

개나뇽·2024년 5월 11일
0

상황

개인 프로젝트에서 상품 구매API에 동시 요청시 예상한 결과가 아닌 다른 결과가 도출되는 상황이 발생 되었다.

위 사진부터 컨트롤러> 서비스> 재고감소 로직> 기존 재고> 동시 호출> 호출 후 재고이다. 코드들 보면 '-'재고에 대해 별도의 예외처리를 하지않아 만약 재고보다 많은 수의 요청이 들어올 경우 부족한 만큼 -n개의 재고로 db에 기록되어야 한다. 하지만 호출 후 db에 재고는 0개로 남아있는 현상이 발생했다.

동시성(Concurrency)

두 사건이 같은 시간에 일어나는 것을 이르는 말

우리가 이용하는 웹과 앱 서비스는 대부분 여러명의 사용자가 보내는 요청을 동시에 수행이 가능하다.
이는 같은 코드가 동시에 수행이 가능하다는 것을 의미한다.

현상

  1. 잘못된 읽기(Dirty Reads): 트랜잭션A,B가 있다. A가 (ID = 1, VAL = 'Jo')이라는 자원(데이터)을 VAL = 'Cho' 변경 중일 때 B ID = 1을 조회시 아직 완료되지 않은 변경 사항인 VAL = 'Cho'를 읽어올 수 있어 데이터의 일관성이 깨진다.

  2. 반복 불가능한 읽기(Non-Repeatable Reads): 하나의 트랜잭션이 같은 값을 조회할 때 다른 값이 검색되는 현상이다.

  3. 팬텀 리드(Phantom Reads): 데이터가 사라지거나 없던 데이터가 생기는 현상이다.

  4. 데드락(Deadlock): 교착 상태 즉 두개 이상의 트랜잭션이 락을 흭득하기 위해 서로 기다리는 형태로 트랜잭션이 롤백되거나 타임아웃되어야만 정상적인 처리가 가능하다.

  5. 경합 상태(Race Conditions): 여러 프로세스 및 스레드가 동시에 동일한 자원을 조작할 때 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 현상이다.

  6. 데이터 불일치(Data Inconsistency): 동시에 여러 트랜잭션이 데이터를 변경시, 각 트랜잭션이 일관된 상태를 유지하지 못하고 데이터의 일관성이 깨지는 현상이다.

즉, 쉽게 말해서 로직의 예상 결과와 전혀 다른 결과 도출되어 데이터의 불일치가 나타는 현상이다.

원인

트랜잭션 격리 수준 설정 미흡, 동시성 제어 부재, 경쟁 조건 등으로 발생할 수가 있다.

해결책

  1. 트랜잭션 격리 수준 Serizlizeable 사용: 트랜잭션 격리 수준에서 제일 높은 단계의 격리 수준으로 한 트랜잭션이 특정 테이블을 읽으면 다른 트랜잭션은 해당 테이블의 데이터를 추가/변경/삭제할 수 없다. 하지만 처리 성능이 큰 폭으로 떨어진다는 단점이 있다.

    큰 폭의 성능저하로 배제

  2. 메서드에 synchronized 사용: 메서드가 한 번에 하나의 스레드에서만 실행할 수 있도록 락을 걸어두는 방법으로 모니터(Monitor)라는 개념으로 동기화가 이뤄진다. 모니터는 공유 자원에 대한 접근을 제어하는 객체로 한 번에 하나의 스레드만 자원에 접근토록 공유 자원에 대한 접근을 동기화 하는데 사용되는 기술이다. 특징적으로 동일한 프로세스 내의 스레드 단위에서만 동시성을 보장한다. 여러대의 서벌를 활용하면 동시성을 보장할 수 없다.

    현재 한대의 서버로만 진행되어 고려할 수 있으나 추후 서버를 스케일 아웃해볼 예정이기에 제외하였다.

  3. 낙관적 락 (Optimistic Lock): 데이터 read시 락을 걸지 않고 update시 이전 데이터와 현재 데이터를 비교해 충돌 여부를 판단한다. 장점으로는 성능이 좋고, 데이터를 읽는 동안 다른 트랜잭션이 해당 데이터를 변경 가능하기에 데드락 발생 가능성이 낮다.

    서비스의 특성상 데이터 변경(재고의 감소등)이 빈번하기에 제외

  4. 네임드 락 (Named Lock): 테이블/레코드/데이터베이스 객체가 아닌 개발자가 사용자가 지정한 문자열에 대해 락 획득-반납하는 방법으로 한 트랜잭셕인 락 획득시, 다른 트랜잭션은 해당 트랙잭션이 락을 해제하 이후 획득 가능하다. 락에 이름을 지정해 어플리케이션 단에서 제어가 가능 장점으로는 Rdis를 사용하기 위한 인프라 설정, 유지보수 비용이 발생하지 않고, MySQL을 이용해 분산락 구현이 가능하다. 단점으로는 락이 자동으로 해제되지 않기에 별도의 명령어 수행 또는 선점시간이 끝나야 락이 해제하는 등 락의 획득-밥납에 대한 로직에 신경을 써야한다.

    유지보수의 비용이 큰것에 비해 비관적 락 방식이 더 이점이 많아 제외

  5. 비관적 락(Pessimistic Lock): 데이터 read시 부터 락을 걸어 다른 트랜잭션이 해당 데이터를 변경할 수 없게 한다. 장점으로는 데이터를 읽는 동안 다른 트랜잭션이 해당 데이터를 변경이 불가능하기에 데이터의 일관성을 보장한다., 단점으로는 성능 저하와 데드락 발생 가능성이 높다.

    재고 데이터의 경우 구매자, 판매자에게 모두 우선 순위에 있는 데이터로 성능 저하가 우려스러우나 비관적 락 선택

고려중인 사항

스핀락 (Spin Lock): 락을 얻을 때까지 계속하여 요청하며 대기한다. 이 과정에서 대기 시간이 락을 얻는데 비용이 적다. 지속적으로 요청을 보개기에 cpu자원을 계속해서 사용해 부하를 유발하며 락의 시간이 긴 경우에는 적합하지 않다.

Redis를 도입 예정이기에 Redis + spin lock을 추후 비관적 락 대신에 사용해볼 예정이다.

profile
정신차려 이 각박한 세상속에서!!!

0개의 댓글