프로젝트 진행 중 발생한 동시성 문제에 대해 알아보고, 해결하는 과정을 정리해보았습니다. DB 락에 대한 내용이 많아서 이 글에 계속 추가할 예정입니다! 우선 간단하게 정리해보았습니다.
DB 동시성 문제?
진행 중인 리멤버미 프로젝트에서 같은 가족끼리는 추억 데이터를 공유하게 됩니다. 따라서 같은 가족이면 A 사용자가 저장한 데이터를 B 사용자가 조회/수정할 수 있습니다. 이때, 한 데이터를 동시에 여러 사용자가 수정할 수도 있기 때문에 DB 동시성 문제가 발생할 수 있습니다.
해당 서비스에서는 동시성 문제가 발생해도 크게 문제가 되지 않을 수 있으나, 다음과 같은 서비스에서는 동시성 문제가 큰 문제가 될 수 있습니다.
만약 여러 사용자가 공유하는 데이터가 추억 데이터가 아니라 은행 계좌라고 가정해보겠습니다. A 사용자가 입금(혹은 출금)을 했는 데, 이 변화가 아직 DB에 반영되지 않았을 때 B 사용자가 입금(혹은 출금)을 하게 될 경우, 계좌 잔액에 오류가 발생합니다. 예를 들어,
- 전체 만원에서 A가 -천원을 했는 데 아직 DB에 반영되지 않았을 때
- B가 조회하면, 전체 만원으로 조회됩니다.
- 이때 B가 -천원을 하면 결과적으로 9천원이 나옵니다.
- 하지만 실제로는 A와 B 모두 천원을 출금하였으므로 잔액은 8천원이어야 합니다.
은행 계좌같은 경우, 이러한 DB 동시성 문제를 발생 시 큰 손해가 발생하므로 반드시 해결해주어야 합니다. DB 동시성 문제를 해결(제어)하기 위해서는 비관적 락 혹은 낙관적 락으로 해결합니다. 각각에 대해서 알아보도록 하겠습니다.
비관적 락(Pessimistic Lock)
비관락은 같은 데이터를 동시에 여러 사용자가 수정한다고 가정합니다(충돌한다고 가정). 따라서 데이터에 Lock을 거는 방식입니다.
- 장점 : 데이터의 무결성이 확실하게 보장됨
- 단점 : 충돌하면 다른 요청은 대기하기 때문에 성능이 떨어짐
- 사용 방법
- SELECT FOR UPDATE 등으로 DB의 Lock 수준 변경
- JPA의 @Lock(LockModeType.PESSIMISTIC_WRITE)
낙관적 락(Optimistic Lock)
낙관적 락은 같은 데이터를 동시에 여러 사용자가 수정하지 않는 다고 가정합니다(충돌 안 한다고 가정). 따라서 Lock을 걸지 않고 충돌이 발생하면, 그때 해결하는 방식입니다(롤백 등으로 해결).
사용자가 데이터를 조회할 때(수정 전)의 버전과 수정할 때의 버전이 같아야만 수정할 수 있습니다. 만약 버전이 다르다면, 내가 수정하려는 사이에 누군가 수정한 것이므로 내가 쓴 내용이 반영되지 않습니다.
- 장점 : Lock을 걸지 않기 때문에 (충돌이 적을 경우) 성능이 좋음
- 단점 : 충돌이 많을 경우, 롤백 처리로 인한 성능 저하가 큼. 또한 롤백 처리를 개발자가 해주어야 함.
- 사용 방법
- 엔티티에 JPA의 @Lock 어노테이션 사용
비관락 vs 낙관락
비관락과 낙관락을 비교해보면,
우선 비관락은 데이터 접근 시 Lock을 걸기 때문에 일반적으로는 성능이 좋지 않습니다. Lock을 가지지 않은 스레드는 대기해야 하기 때문입니다.
반면 낙관락은 Lock을 걸지 않기 때문에 충돌이 적을 때는 성능이 좋지만, 충돌이 많을 때는 오히려 롤백 처리를 해주어야 하므로 성능이 훨씬 떨어집니다.
따라서 데이터의 무결성이 중요할 경우 비관락을 사용하고, 충돌이 적을 경우에는 낙관락을 사용하는 게 효율적일 것 같습니다. 해당 프로젝트에서는 (은행과 달리)데이터의 무결성이 크게 중요한 서비스가 아니고, 충돌이 많이 발생하지 않기 때문에 비관락이 아닌 낙관락을 사용해주기로 하였습니다.
낙관락 적용하기
- 엔티티 클래스에 @Version 어노테이션이 달린 필드 추가
아래처럼 @Version을 사용하면 0으로 초기화되고, 변경이 될 때마다 1씩 증가합니다. 그리고 동시성 문제 발생 시, ObjectOptimisticLockingFailureException
이 발생합니다.
낙관락의 구현 방법 자체는 위와 같이 해주면 됩니다. 다음 글에서는 실제로 동시성 문제를 해결하는 지 테스트 코드를 통해 자세히 알아보도록 하겠습니다!
Reference
- https://www.baeldung.com/jpa-optimistic-locking
- https://blog.mimacom.com/testing-optimistic-locking-handling-spring-boot-jpa/
- https://jaehoney.tistory.com/159
- https://hudi.blog/jpa-concurrency-control-optimistic-lock-and-pessimistic-lock/
- https://velog.io/@sgwon1996/JPA-낙관적-락-과-비관적-락
- https://jaehoney.tistory.com/159
- https://velog.io/@znftm97/동시성-문제-해결하기-V1-낙관적-락Optimisitc-Lock-feat.데드락-첫-만남#span-stylecolord9730d4-2-테스트-코드span