Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[오치현] 4차 과제 제출 #20

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open

Conversation

ch1hyun
Copy link
Member

@ch1hyun ch1hyun commented Dec 26, 2024

노션 링크

스프링에서의 동시성 제어

스프링 코드 레벨에서의 동시성 제어에 관해 알아보는 시간을 가졌습니다.

모두 공통적으로 “/api/posts/1” 으로 1000번의 GET 요청을 수행했습니다. 이때 조회 수가 5000이어야 정상입니다.

1. Synchronized

자바의 synchronized 키워드는 N 개의 스레드가 공유 자원에 접근하는 것을 제어해 동시성 제어 메커니즘을 제공한다. 이때 synchronized 키워드가 적용된 곳을 Critical Section 이라고 부르는데, Monitor Locking 메커니즘을 이용해서 제어한다.

자바의 모든 객체는 Monitor Lock 을 가지고 있고, 이를 통해 스레드 동기화를 수행할 수 있다. synchronized 키워드로 상호 배제(Mutual Exclusion) 을 보장한다.

→ 한 번에 하나의 스레드만 Critical Section에 접근할 수 있게 만든다.

synchronized 키워드 적용

image

image

synchronized 키워드를 적용했음에도 조회수가 530 밖에 되지않는다.

이유는 트랜잭션의 커밋 시점과 모니터 언락 시점의 불일치 때문이다. @Transactional 어노테이션이 붙은 클래스는 GCLIB에 의해 런타임에 기반 프록시가 생성되고, 로직으로 진입 전/후에 Transaction Begin과 Commit/Rollback이 처리된다.

image

따라서 모니터 락의 언락 시점을 트랜잭션 커밋 이후로 바꾸면 된다. 파사드 계층을 추가해 해결할 수 있다.

synchronized + facade layer 적용

image

image

정상적으로 1001개 (1000번 요청 + 마지막 1번 조회)가 조회되는 것을 볼 수 있다.

단점은 분산 환경에서 동시성 문제가 해결되지 않는다. (Scale Out)

→ 분산 환경에서의 동시성 제어는 다음 항목으로 해결 가능하다.

  1. 데이터베이스의 레코드에 직접적으로 Lock 적용 (Pessimistic Lock)
  2. 애플리케이션 레벨에서 버전을 통해 갱신 시점에 동기화 (Optimistic Lock)
  3. 별도의 영역에서 Lock이라는 “개념”을 관리
    • MySQL Named Lock
    • Redis
    • Zookeeper

출처 : https://sjiwon-dev.tistory.com/20

2. Pessimistic Lock

DB의 Lock 기능을 이용하고, SELECT FOR UPDATE 구문이 사용된다.

트랜잭션 커밋 전 미리 트랜잭션 충돌을 감지할 수 있다. 또한 Lock을 획득할 때까지 트랜잭션은 대기하므로, Timeout을 설정할 수 있다.

SELECT FOR UPDATE 를 사용할 경우 Gap Lock이 사용된다.

이는 MySQL의 특별한 형태의 잠금인데, 실제 존재하지 않는 레코드 공간(간격)을 잠근다.

나중에 더 깊게 공부 : https://medium.com/daangn/mysql-gap-lock-%EB%8B%A4%EC%8B%9C%EB%B3%B4%EA%B8%B0-7f47ea3f68bc + 리마큐

Pessimistic Lock 적용

image

image

정상적으로 조회되는 모습을 볼 수 있다.

단점은 다음과 같다.

  1. 항상 Exclusive Lock을 걸고 사용하기 때문에 수행 시간이 늘어난다.
  2. MVCC를 통해서 데이터를 읽을 때 데드락이 발생할 확률이 높다.
  3. 스레드 고갈이 일어날 수 있다. (DB 커넥션 기다리는 시간이 늘어남) → 늘리면 되긴 함(물리적 한계)

3. Optimistic Lock

MVCC와 비슷한 원리로 동작하고, 애플리케이션 레벨에서 버전 컬럼을 사용해 읽어온 시점과 트랜잭션이 커밋되는 시점의 데이터가 같은지 정합성을 비교한다.

MVCC?

동시성 제어 방법 중 하나인 Locking 의 성능 저하 문제점을 해결하기 위해 나온 개념이다.

Multi-Version Conccurency Control 의 약자로 원본의 데이터와 변경중인 데이터를 동시에 유지하는 방식을 채택한다. 사용자가 데이터에 접근하면 데이터베이스의 스냅샷 을 읽고, 변경 중에 롤백되면 원본 데이터의 스냅샷 으로 복구한다. 변경에 성공하면 스냅샷 을 디스크에 반영한다.

버전 관리의 경우, 기존 데이터를 덮어 씌우는 방식이 아닌, 기존 데이터를 바탕으로 이전 버전과 비교해 변경된 내용을 기록한다. 하나의 데이터에는 여러 버전의 데이터가 존재하고 사용자는 마지막 데이터의 버전을 읽는다.

Optimistic Lock 적용

image

Post 엔티티 클래스에 @Version 어노테이션을 추가한다.

이를 통해 손쉽게 버전을 자동으로 비교 및 증가시켜준다.

image

image

Optimistic Lock은 동시성 이슈만 감지 가능하므로 정상적으로 처리하길 기대한다면, 예외가 감지됐을 때 재시도를 해야한다. 이를 위해 파사드 클래스를 사용해 Service의 조회 로직을 재귀 처리했다.

image

정상적으로 조회되는 모습을 볼 수 있다.

단점은 다음과 같다.

  1. 충돌이 발생하면 기다리는게 아니라 재시도 하기 때문에 느리다.
  2. 낮은 확률로 스택 오버플로우가 발생할 수 있다. (굳이 단점은 아니라고 한다.)

더 공부해볼 것

  1. 이외의 Lock 방식
  2. 근본적인 동작 원리
  3. MVCC
  4. Gap Lock
  5. 각 Lock의 데드락 발생 상황, 해결 방법
  6. Named Lock, Redis 활용 Lock, 실제 분산 환경에서 테스트
  7. 리마큐

출처

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant