Last updated
Last updated
이 글은 를 시청하고 작성되었습니다.
동시성은 초심자가 놓치기 쉬운 문제를 발생시킴
ex) 당직 담당자를 최소 1명은 유지해야 한다. 2명의 담당자 A, B가 존재한다.
A가 담당자 수를 SELECT ⇒ 결과: 2
B가 담당자 수를 SELECT ⇒ 결과: 2
A는 자신이 빠져도 최소 1명을 만족하므로 자신을 당직에서 제거하도록 UPDATE
B가 자신이 빠져도 최소 1명을 만족하므로 자신을 당직에서 제거하도록 UPDATE
결과적으로 남은 당직자는 0명. 최소 1명을 유지해야 한다는 비즈니스 로직 위반
왜? A와 B가 동시에 같은 데이터(당직자 수)에 접근했기 때문
따라서 트랜잭션을 격리하여 이러한 동시성 문제를 해결해야 함
가장 간단한 방법은 각 트랜잭션이 순서대로 실행되도록 하는 것
그러나 이 방법은 성능(처리량) 저하
⭐ 격리 정도와 성능은 Trade-off이기 때문에, 적절한 격리 수준을 설정하는 것이 중요하다.
사용하는 DB에서 Default로 설정된 격리 수준이 무엇인지, 어떤 격리 수준을 지원하는지 아는 것 또한 중요하다.
격리 수준은 다음의 4가지가 지원된다.
Read Uncommitted
Read Committed
Repeatable Read
Serializable
동시성과 관련하여 발생할 수 있는 주요한 문제로는 아래의 것들이 있다.
커밋되지 않은 데이터 읽기 (dirty read)
트랜잭션 중인 데이터를 누군가 읽은 경우
커밋되지 않은 데이터 덮어쓰기 (dirty write)
트랜잭션 중인 데이터에 누군가 쓴 경우
읽는 동안 데이터 변경 (1)
같은 데이터를 여러 곳에서 읽고 수정하여 읽는 시점에 따라 값이 바뀌는 경우
읽는 동안 데이터 변경 (2)
같은 row는 아니지만 같은 table 내 데이터를 수정해 특정 로직(count, sum)이 비정상적으로 수행되는 경우
변경 유실
누군가 한 데이터에 거의 동시에 쓴 경우 나중에 쓰인 것이 반영되어 먼저 쓰인 데이터가 유실 ex) 위키 내용 수정
이 문제들을 해결하는 데에 다음의 격리 수준을 이용할 수 있다.
커밋되지 않은 데이터 읽기 (dirty read) ⇒ Read Committed
커밋되지 않은 데이터 덮어쓰기 (dirty write) ⇒ Read Committed
읽는 동안 데이터 변경 (1) ⇒ Repeatable Read
읽는 동안 데이터 변경 (2) ⇒ Serializable
변경 유실
커밋되지 않은 데이터도 읽을 수 있음
동시성 문제를 전혀 해결할 수 없기 때문에 잘 사용되지 않는다.
커밋된 데이터만 읽을 수 있음
커밋된 값, 트랜잭션 진행 중인 값을 별도로 보관
트랜잭션 진행 중에는 커밋된 값만 읽을 수 있도록 함
커밋된 데이터만 덮어쓸 수 있음
행(row) 단위 잠금을 사용
특정 트랜잭션이 수정 중인 행은 수정이 끝날 때까지 잠금 처리
이를 수정하려는 다른 트랜잭션은 해당 트랜잭션의 수정이 끝난 뒤 수행될 수 있음
트랜잭션 진행 중에는 시작 시점의 데이터만을 읽도록 함
해당 데이터가 트랜잭션 진행 중 외부에서 변경되었다 하더라도 트랜잭션 내에서는 해당 데이터를 트랜잭션 시작 지점의 상태(변경되지 않은 데이터)로 다룸
MVCC(Multi-Version Concurrency Control)을 이용
트랜잭션 시작 시 데이터를 특정 버전으로 저장하고, 트랜잭션 중 데이터를 읽게 되면 해당 버전의 데이터를 읽음
다른 트랜잭션에서는 같은 행이라도 다른 버전으로 다루므로 읽는 시점에 따라 데이터가 달라지지는 않음
정의 그 자체로는 모든 트랜잭션을 순차적으로 실행하는 격리 수준
그러나 이는 사실상 최악의 퍼포먼스를 보이므로 잘 사용하지 않으며, 대신 인덱스 기반 잠금, 조건(WHERE 절) 기반 잠금 등의 방식을 사용
위에서 언급한 당직 담당자 문제(동일 table 내 데이터 수정)의 해결을 위해 당직 일자를 인덱스로 설정하고 인덱스 잠금을 사용하면, A와 B가 동일 인덱스 데이터를 수정하려 하므로 A→B 순서로 실행되게 되고, B는 Lock에 의해 데이터를 변경할 수 없게 함
원자적 연산 사용
A = 1; A = 2; 이렇게 증가하도록 반영하면 앞의 할당이 묻히니까 A = A+1; 이렇게 변수에 특정 연산을 하여 할당하는 방식을 사용
명시적 잠금
누군가 쓰기 트랜잭션을 수행 중이라면 해당 데이터에 접근할 수 없도록 함
CAS (Compare And Set)
수정할 때 값이 반영되어도 되는지 비교 후 반영
ex) version이라는 column을 두어 수정 시마다 version을 1씩 증가시킴. A가 조회하고(버전1), B가 조회하고(버전1), A가 수정을 시도하고(WHERE 버전=1인 행을 수정하고 버전=2로 업데이트), B가 수정을 시도(WHERE 버전=1인 행을 수정하지만 버전=2로 업데이트되어 없음)