220425 TIL (동시성과 트랜잭션 격리)

Minseok-Choi·2022년 4월 25일
0

TIL

목록 보기
6/11

학습자료

https://www.youtube.com/watch?v=poyjLx-LOEU

동시성

  • 여러 클라이언트가 같은 데이터에 동시 접근하게 된다면?
  • 영상에서 설명해주는 예시로, 당직자를 최소한 1명을 유지해야 하는 조건이 있다. 그리고 그 당직자들이 각각 DB에 접근해서 자신의 당직을 확인하고, 당직을 설지말지를 결정해볼 수 있다고 해보자.
  • 오늘 당직자가 2명일 때, 당직자 2명 모두가 유사한 시점에 현재 당직자가 몇 명인지를 확인한다.
  • 각자 select 쿼리를 통해서 확인했을 때, 오늘 당직자 인원이 2명임을 확인한다.
  • 그리고서 자신을 당직자 인원에서 빼는 update 쿼리를 동시에 혹은 거의 유사한 시점에 보내게 된다면?
  • 당직자들은 남은 인원이 2명임을 확인했기에, 분명 잘못은 없지만 오늘 당직자는 0명이 되는 상황이 벌어지게된다.

경쟁 상태(Race Condition)

  • 여러 클라이언트가 같은 데이터에 접근할 때 문제가 발생한다.

트랜잭션 격리

  • 트랜잭션을 서로 격리해서 다른 트랜잭션이 영향을 주지 못하도록 함

  • 가장 쉬운 방법은 트랜잭션을 순선대로 실행하는 것이다.
    - 동시 접근 문제가 아예 없어지지만, 한 번에 하나의 트랜잭션만을 처리하기때문에 성능(처리량)이 저하된다.

  • 그래서 다양한 격리 수준을 지원한다.
    - Read Uncommitted
    - Read Committed
    - Repeatable Read
    - Serializable

동시성 관련 다양한 문제들

1. 커밋되지 않은 데이터 읽기 dirty read

  • 순차적으로 stock을 update하고 count를 update하는 사이에, 다른 사용자가 stock과 count를 read할 때, 커밋이 되지 않은 데이터를 읽게 되어서 stock과 count의 값이 불일치 하는 상황이 발생함.

2. 커밋되지 않은 데이터 덮어쓰기 dirty write

  • 두 사용자가 어떠한 데이터의 value를 update한다 할 때, 그 커밋이 되지 않은 데이터를 덮어쓰게 되면, 두 value가 덮어씌워져 사용자가 기대했던 결과와는 다르게 저장되는 상황이 발생함.

dirty read, dirty write를 해결하는 Read committed

  • 커밋된 데이터만 읽기
    - 커밋된 값과 트랜잭션 진행 중인 값을 따로 보관(트랜잭션이 진행중인 값은 읽지 않음)
  • 커밋된 데이터만 덮어쓰기
    - 행 단위 잠금 사용 : 같은 데이터를 수정한 트랜잭션이 끝날 때까지 대기

3. 읽는 동안 데이터 변경 1 read skew

  • 읽는 시점에 따라 데이터가 바뀜
  • 한 사용자가 A, B 두 데이터를 읽으려고 할때, A를 읽고 난 시점에 다른 사용자가 A와 B 데이터 모두를 수정한다. 그리고 그 이후에 데이터를 읽고 있던 사용자가 B를 읽게 되는데 A는 수정 전의 데이터, B는 수정 후의 데이터를 읽게 되는 상황이 발생한다.

read skew를 해결하는 Repeatable Read

  • 트랜잭션 동안 같은 데이터를 읽게 함
    - 구현 예: MVCC(Multi-Version Concurrency Control)
    • 읽는 시점에 특정 버전에 해당하는 데이터만 읽음
      - 여러 버전을 저장하는 개념
    • 위의 예시에서 읽는 사용자는 V1이라는 버전으로 수정전의 데이터만을 읽게 되는 것

4. 변경 유실 Lost Update

  • 같은 데이터를 쓸 때 발생: 예시로 count 증가, 위키 페이지 수정
  • Read committed, Repeatable Read를 사용해도 발생할 수 있음
  • count가 1인 데이터가 있을 때, 두 사용자가 모두 count를 1 증가시키려고 한다고 하자.
  • 두 사용자 모두 count가 1임을 확인하고 count=2로 update를 실행하려고 한다. 이 과정이 동시에 진행된다면 예상하는 count는 3이 되어야 하지만, 실제로는 count가 2로 update가 유실되는 상황이 발생한다.

변경 유실에 대한 몇 가지 처리 방법

원자적 연산

  • DB가 지원하는 원자적 연산 사용
    - 동시 수정 요청에 대해 DB가 순차 처리
    • update article set count = count + 1 where id = 1
    • DB가 원자적 연산을 지원하는지 확인해야함

명시적 잠금

  • 조회할 때 수정할 행을 미리 잠금
    - select ... for update
    • update할 행에 대해서는 잠금되면, 다른 사용자가 읽을 수 도 없음

CAS compare and set

  • 수정할 때 값이 같은지 비교
    - update article set count = 2 where id = 1 and count = 1

5. 읽는 동안 데이터 변경 2

  • 한 트랜잭션의 결과가 다른 트랜잭션의 쿼리 결과에 영향
    - 같은 데이터를 쓰지 않지만 실제로는 경쟁 상태
    • 맨 위에 당직자 문제가 이 예시이다.

Serializable

  • 인덱스 잠금이나 조건 기반 잠금 (where절) 등 사용
  • 당직자 예시에서 먼저 접근한 사용자가 잠금을 얻어서 update를 할 수 있지만, 이후에 접근한 사용자는 잠금을 얻지 못해서, update를 실패해서 Rollback하거나 실패한 채로 커밋을 하게된다.

정리

  • 동시성은 초보자가 놓치기 쉬운 문제이다.
  • 동시성 문제와 격리 수준을 이해해야 문제발생을 줄일 수 있다.
  • 잠금 시간을 최소화할 수 있도록 고민해야한다.
  • 동시성 문제를 다룰 때는 사용하는 DB의 기본 격리 레벨을 알아야한다.
  • DB의 격리 레벨 동작 방식이 DB마다 다를 수도 있다.

회고 및 TODO

  • 스프링에서 지원하는 @Transactional과 mysql에서 지원하는 격리레벨과 사용방법에 대해서 차근차근 공부해 보자
  • 아직 복잡한 sql 문법에 대해서도 학습을 하지 못한 상황이라, 아직 고민해야할 레벨이 아닐 수 있지만, 영상의 예시가 이해하기 쉬워서 30분가까이 되는 영상이 짧다고 느껴졌다. 영상을 키워드 삼아서 공부할 때 더 깊게 이해하고 활용할 수 있었으면 좋겠다.
profile
차곡차곡

0개의 댓글