먼저 비관적 락 과 낙관적 락을 살펴보기에 앞서 락이란 무엇인지와 장,단점에 대해서 먼저 살펴보겠습니다. 데이터베이스는 MySQL을 기준으로 설명합니다.
락(Lock)은 동시에 여러 프로세스나 스레드가 공유하는 리소스에 대한 접근을 조절합니다. 여러 프로세스나 스레드가 동시에 같은 리소스를 변경하려고 할 때, 락을 사용하여 동시성 문제를 방지 및 데이터의 일관성을 유지할 수 있습니다.
위 설명에서 보았듯이 데이터베이스에서 일을 하고있는 자원은 여러개 입니다. 이렇게 여러개의 노동자원이 데이터에 접근을 할때 동시다발적으로 특정 데이터를 조작한다면 데이터가 원하는 방향으로 바뀌지 않는 상황이 생기게 됩니다. 이러한 문제를 동시성 문제 라고 하는데요.
그렇기에 뮤텍스나 세마포어같은 동기화 기법을 통해 특정 데이터에 대한 락(키)을 얻고 해당 데이터를 조작함으로써 일관성 있는 데이터를 유지할 수 있게 됩니다.
락을 통해 동시성 문제를 해결할 수 있는 만큼 그만큼의 트레이드 오프가 있기 마련입니다.
예를들어 락을 거는 데이터의 스코프를 줄이지 않아 여러 데이터에 자주 락이 걸린다면 특정 데이터를 얻기위한 쓰레드들이 대기큐에서 오랫동안 기다리는 경우가 발생하며 응답속도가 느려지는 단점이 있습니다.
그리고 데드락(교착상태) 이라고 불리는 문제가 생길수도 있는데요
상황
트랜잭션A: Low 1의 데이터의 락을 획득 후 Low 2의 데이터에 접근을 시도
트랜잭션B: Low 2의 데이터의 락을 획득 후 Low 1의 데이터에 접근을 시도
서로 트랜잭션이 끝나지 않은 상황에서 락을 획득하지 못하고 무한히 대기를 타게 되면서 해당 쓰레드들은 쓰레드풀에 반환되지 않고 이 쓰레드를 제외한 나머지 쓰레드만 일을 하게 되어서 가용성이 낮아지게 됩니다.
낙관적 락은 데이터베이스에 락을 즉시 설정하지 않고, 데이터의 커밋 전 충돌을 감지하는 방식입니다.
데이터베이스 레벨이 아닌 어플리케이션 레벨에서 락을 핸들링 함으로써 동시에 여러 트랜잭션이 데이터를 읽을 수 있으며, 성능이 향상될 수 있습니다.
버전 관리를 통해 동작한다. 엔티티의 상태가 변경될 때마다 증가하며 따라서 엔티티의 버전을 통해 충돌을 감지하고 처리합니다.
충돌이 감지되었을때 특정 예외가 발생하며 예외에 대한 후 처리가 가능하다.
1) 데이터 읽기
트랜잭션 A: 데이터를 읽으며 이때 버전 정보를 함께 불러온다.
(id: 1), (version: 1)
2) 데이터 수정 및 업데이트
트랜잭션 A:읽은 데이터를 기반으로 수정 작업을 진행
(id: 1), (version: 1)
트랜잭션 B: 동일한 데이터를 읽고 수정 작업을 진행 후 트랜잭션 A보다 먼저 커밋 그러면서 version 올라감
(id: 1), (version: 2)
3) 업데이트 시도 및 충돌 감지
트랜잭션 A: 데이터를 업데이트하기 전에 버전을 확인하는데 version이 바뀌어서 충돌로 인식, 충돌이 발생하면 보통 예외 발생 및 처리
기존 데이터 -> (id: 1), (version: 1)
확인 데이터 -> (id: 1), (version: 2)
충돌이 발생하지 않고 버전이 일치하는 경우, 트랜잭션A는 데이터를 업데이트하고 커밋 이때 데이터의 버전이 증가하여 다른 트랜잭션과 충돌이 발생하지 않도록 합니다.
비관적 락은 데이터를 읽을 때 즉시 데이터베이스 테이블 or 레코드에 락을 설정합니다. 따라서 다른 트랜잭션이 해당 데이터를 읽거나 수정되어 락키를 획득 할때까지 대기합니다.
공유 락: 데이터를 읽을 때 사용되며, 다른 트랜잭션도 해당 데이터를 읽기 가능
배타 락: 데이터를 수정할 때 사용되며, 해당 데이터에 대한 다른 트랜잭션의 접근을 차단
공유 락
SELECT * FROM FEEDS WHERE id=1 FOR SHARE;
베타 락
SELECT * FROM FEEDS WHERE id=1 FOR UPDATE;
개인적인 생각으로는 동시성 문제를 해결함에 있어 락 뿐만 아니라 트랜잭션 격리레벨 등 여러 개념을 조합해서 문제를 해결할 수 있어야 할것 같습니다.
그리고 비관적 락과 낙관적 락의 경우 동시성 문제를 해결할 수 있는 방법인건 같으나 요구사항에 따라 쓰임새가 나뉘는것 같은데요
이부분은 GPT형의 의견을 참고하자면
낙관적 락(Optimistic Locking)
데이터 충돌이 발생할 가능성이 낮거나, 충돌이 발생해도 비교적 간단하게 해결될 수 있는 상황에서 낙관적 락을 사용합니다.
예를 들어, 동시에 여러 사용자가 동일한 데이터를 읽고 수정하지 않는 경우에 적합합니다.
낙관적 락은 데이터를 읽을 때 락을 설정하지 않으므로 읽기 작업이 많은 상황에서 성능을 향상시킬 수 있습니다.
비관적 락(Pessimistic Locking)
여러 사용자가 동시에 동일한 데이터를 읽고 수정하는 경우에는 데이터 일관성을 유지하기 위해 비관적 락을 사용합니다.
데이터 수정이 경쟁적이고 충돌이 자주 발생하는 경우에 적합합니다.
데이터 일관성을 강제해야 할 때 사용됩니다:
데이터 일관성을 강제해야 하고 충돌을 방지해야 할 때 비관적 락을 사용합니다.
예를 들어, 재고 수량이나 금액과 같이 데이터 일관성이 매우 중요한 경우에 적합합니다.
이 외에도
MySQL의 경우 테이블이 아닌 인덱스 기반의 Record 락을 사용하는데요
Insert시에 갭 락(Gap Lock)과 넥스트 키 락(Next Key Lock) 을 통해 PK Increament시에 순차적으로 값이 증가되게끔 하는 방식을 사용하고 있습니다.
데이터의 일관성과 정합성이 보장되는 대신 데드락이 생길수도 있고 무분별하게 사용시 응답속도의 저하라는 트레이드 오프가 생길 수 있으니 잘 알아보고 사용을 해야할것 같습니다.