[Concurrency] 비관적 동시성과 낙관적 동시성

비관적(Pessimistic) 동시성과 낙관적(Optimistic) 동시성은 동시성의 엄격한 수준을 정의하는 주요 개념이다.

DB 하나로 굴러가는 간단한 서버를 짠다 해도, 이정도는 알아둬야 동시성 문제로 발생하는 버그들을 방지할 수 있다.




비관적 동시성(Pessimistic Concurrency)

비관적 동시성은 말 그대로 상황을 비관적으로 본다는 가정하에 설립되는 구조다.

여러개의 스레드가 동시에 한 자원을 건드릴 수 있다고 가정하고, 공유 자원에 대해 엄격한 Lock을 통해 제약을 건다.
공유 자원은 무조건 하나의 스레드만 접근할 수 있고, 나머지는 대기토록 하는 것이다.

데이터의 무결성과 신뢰성을 지킬 수 있다는 점에서는 좋으나, 성능상으로 비효율이 많다는 단점이 있다.

보통 프로그램에서 동시성과 동기화를 적용한다고 하면, 특별한 이유가 없거든 Mutex 등을 통해 비관적인 동시성을 적용하게 된다.

RDB에서
SQL을 사용할때 비관적 동시성을 적용하려면,
수정하려는 행을 조회할때, 아래와 같은 쿼리로 조회 시점에서 Lock을 걸어버리는 방법이 있다.
select * from foo** for update;**

혹은 트랜잭션을 생성해서 필요사항에 맞게 isolation level을 잘 정의해도 된다.

참고
https://m.blog.naver.com/sssang97/222910699714




낙관적 동시성 (Optimistic Concurrency)

낙관적 동시성은 그에 비해 굉장히 느슨한 접근법을 취하는 관점이다.

한번에 하나의 스레드만 공유 자원에 접근할 것이라고 가정하고, 비관적 동시성과 다르게 읽기 시점에서는 Lock을 사용하지 않는다.
그리고 동시에 쓰기가 발생했을 경우는 오류로 간주하고 오류처리로 해결한다.

때문에 성능면에서는 훨씬 나아진다는게 장점이나... 역시 얻는게 있으면 잃는 것도 있다. 손으로 직접 처리해주고 검증해줘야 할게 좀 많이 생긴다.

프로그램에서 직접 낙관적 동시성을 구현할 경우에는, 공유 자원을 atomic 리소스로 정의해서 사용하는 것이 보통이다.
그래서 값의 변경시에도 Compare And Swap 같은 lock-free 기법 등을 사용한다.

참고
https://blog.naver.com/sssang97/224054767242


RDB에서
SQL에서 낙관적 동시성을 적용할 때는 값을 변경할때 낙관적 동시성에 대한 위반 테스트를 하는 것이 안전하다.

구현법이 정해져있지는 않지만 보통 이러한 접근법을 취한다.

  1. 테이블에 전용 타임스탬프열을 추가한다. 편의상 opt_chk라고 부르겠다.
  2. 수정할 데이터를 읽어온다. 당연히 opt_chk도 함께 가져온다.
  3. update 쿼리로 데이터를 변경할때, SET절로 opt_chk을 현재 시간으로 변경하고, WHERE에는 읽어왔던 opt_chk으로 동등 비교를 추가한다.
  4. 그래서 만약 다른 스레드에서 값을 먼저 바꿔서 opt_chk가 바뀌었다면, 현재 쿼리는 실패한다. (정확히는, 아무것도 변경되지 않는다.)
  5. 따라서 update 쿼리에서 변경된 row가 0인 경우는 update 오류다. 이를 기반으로 예외처리를 한다.


참조
https://learn.microsoft.com/ko-kr/dotnet/framework/data/adonet/optimistic-concurrency