동시에 같은 DB 테이블 row를 업데이트 하는 상황은 "DB의 동시성 이슈"으로 생각해보면 좋을 것 같다.
DB의 동시성 이슈
동시성이란 여러 요청이 동시에 동일한 자원(Data)에 접근하고 수정하려는 것을 말한다. 이로 인해, 발생하게 되는 문제를 동시성 문제라고 한다. 동시성 문제로 Data의 무결성이 깨지고 의도하지 않은 결과를 반환하게 되는 문제들이 발생한다.
해결방안
해결방안에는 DB수준에서의 락, 프레임워크 or 언어 수준에서의 동기화 등이 존재한다. 여기서는 DB수준에서의 락에 대해 알아보려고 한다.
- 테이블의 row에 접근 시, Lock을 걸고 다른 Lock이 걸려있지 않는 경우에만 수정을 가능하게 함
- 수정할 때 내가 먼저 수정했음을 명시하여 다른 곳에서 동일한 조건으로 값을 수정할 수 없게 함
위처럼, 자원 경쟁에 대한 관점으로 두 가지의 방법을 생각해볼 수 있다. 이는 비관적 락과 낙관적 락을 나누는 기준이 된다.
비관적 락(Pessimistic Lock)
현재 수정하려는 data가 언제든지 다른 요청에 의해 수정될 가능성을 고려하여 해당 data에 Lock을 거는 방식 트랜잭션이 시작될 때 Shared Lock 또는 Exclusive Lock을 걸고 시작한다.
공유락 (Shared Lock)
: Read Lock이라고 하는 공유락은 트랜잭션이 읽기를 할 때 사용하는 락이며, 데이터를 읽기만 하기 때문에 같은 공유락끼리는 동시에 접근이 가능하지만, 쓰기 작업은 막는다.베타락 (Exclusive Lock)
: Write Lock이라고 하는 배터락은, 데이터를 변경할 때 사용하는 락이다. 트랜잭션이 완료될 때까지 유지되며, 락이 끝나기 전까지 읽기/쓰기를 모두 막는다.
장점
- data의 무결성을 보존할 수 있다.
- 충돌 발생 미리 방지
단점
- Lock으로 인해 이후의 다른 요청은 대기 상태로 빠짐
- 기존 Lock의 트랜잭션이 commit/rollback으로 끝내면 이후 대기 요청을 실행
낙관적 락(Optimistic Lock)
자원에 락을 걸지 않고, 동시성 문제가 발생하면 그때 처리한다. 숫자/시간 컬럼을 만들어 수정 시 그 data를 증가/갱신함 -> data 수정 시 컬럼을 비교하여 일치하는지 확인
- Version과 같은 별도의 컬럼을 추가하여 충돌 발생을 막는다. Version -> hashcode / timestamp 등을 사용하여 상태를 보고 충돌 확인함
- 충돌 발생 시, DB가 아닌 애플리케이션 단에서 처리를 한다. 낙관적 락은 UPDATE에 실패해도 자동으로 예외를 던지지 않고, 단순히 0개의 row를 업데이트한다. 따라서 이때 여러 작업이 묶은 트랜잭션 요청 실패 시,우리가 직접 롤백 처리를 해줘야 한다.
장점
- 구현하기 용이함
- 지속적인 락으로 인한 성능저하를 막을 수 있음
단점
- Version Conflict 시, 처리해야 할 외부 요인이 존재함
성능 비교
비관적 락 < 낙관적 락
- 낙관적 락은 트랜잭션이 필요하지 않기 때문에 성능적으로 우수함
- 비관적 락은 데이터 자체에 락을 걸기 때문에 동시성이 떨어져 성능 저하가 발생하며, 서로의 자원이 필요할 경우에는 교착상태가 발생할 가능성 존재
충돌이 많이 발생하는 환경
충돌 발생 시, 비관적 락은 트랜잭션을 롤백하면 끝남. 하지만 낙관적 락은 까다로운 수동 롤백 처리와 성능 측면에서도 Update를 한번씩 더 해줘야 하기 떄문에, 성능 저하가 발생할 수 있음
데이터의 무결성 + 데이터의 충돌이 많이 발생할 것 같은 경우 -> 비관적 락 데이터 충돌이 적을 것 같은 경우 + 조회 작업이 많아 동시 접근 성능이 중요 -> 낙관적 락