일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- AVL트리
- 연결리스트
- 백준 장학금
- 엔티티 그래프
- heapq
- 이분탐색이란
- 강화학습
- 프로세스
- 운영체제
- 힙트리
- posix
- Kruskal
- 스케줄링
- SpringSecurity
- 멀티프로세서
- JPA
- 최소힙
- python
- JVM
- HTTP
- 완전이진트리
- 자료구조
- 연결리스트 종류
- 백준장학금
- 점근적 표기법
- jpa n+1 문제
- spring
- MSA
- 최대 힙
- 알고리즘
- Today
- Total
KKanging
[데이터베이스] 동시성 제어? 락? 2PL? 그게 뭔데 본문
동시성 제어란
동시성 제어가 왜 필요할까?

위 이미지 처럼 2개의 트랜잭션이 실행되었다고 가정하자
트랜잭션 A 는 x와 y를 더해 y값을 수정하고
트랜잭션 B는 x와 y를 더해 x값을 수정한다.
위 예시는 정상적인 흐름의 실행 예시이다.
하지만 트랜잭션이 동시에 실행됐다고 가정해보자.

위 이미지 과정처럼 트랜잭션 A가 실행하다가 다른 트랜잭션에 switching 될 수도 있고 꼭 트랜잭션이 동시에 시작하지 않더라도 동시에 실행되는 트랜잭션은 어떻게 switching되어 어떤 실행 결과가 나올지 예상할 수 없다.(이러한 동시성 문제를 운영체제에선 경쟁조건:race condition 이라고 부른다)
데이터베이스에서 동시성 문제를 어떻게 해결할까?

데이터베이스에서 이러한 동시성 문제를 락으로 해결한다(운영체제에서도 존재하는 그 락이다)
데이터베이스에서 락은 크게 2가지 종류가 있는데,
읽기를 위한 락과 읽고 쓰기를 위한 락이 있다.
(낙관적락/비관적락이라는 종류로 나뉘어지긴 하지만 낙관적 락은 DB에서의 락이 아닌 애플리케이션 레밸의 락이므로 여기선 따로 설명하지 않겠다.
여기선 DB의 락인 비관적 락만을 설명한다)
공유락(Shared Lock) 과 베타락(Exclusive Lock)
공유락 : read 하기 위한 lock, 다른 트랜잭션의 read나 공유락을 허용한다.
베타락 : read/write 하기 위한 lock, 다른 트랜잭션의 read/write를 전부 허용하지 않는다.

공유락을 취득한 상태에서는 다른 트랜잭션이 read하는 작업을 허용한다.
동시에 공유락을 거는 것 또한 허용한다.
하지만 어느 한 트랜잭션이 베타락을 걸었다면 다른 트랜잭션은 S/X LOCK 을 모두 걸지 못하고 read/write(insert,delete,update) 의 실행도 허용하지 않는다.
락을 걸기만 하면 동시성이 확보가 되는 것일까?

위 예시를 보면 락을 사용했을 때와 안했을 때 예시가 동일한 결과인 것을 볼 수 있다.
이러한 상황이 발생하는 이유는 다음과 같다.

read lock으로 x를 락을 걸었지만 읽자마자 바로 락을 해제했다.
해제한 후 switching이 되어 다른 트랜잭션에서 x의 값을 수정한 것이다.
write_lock(y) 과 unlock(x)를 바꾸면 어떻게 될까?

위처럼 x에 대해 락을 걸고 해당 데이터를 사용할 때까지 다른 트랜잭션에 대한 고립을(isolation)을 보장해준다면 serializable 하게 동작하게 된다.
그래서 락을 어떻게 사용하라는 거야?
위 예시를 보면 이해가 가지만 막상 다른 도메인에서 락을 사용하여 동시성을 제어 하라고 하면 막막할 것이다(필자가 그랬다.)

현재 serializable 하게 동작하도록 하는 락의 사용은 모든 락 동작은 최초의 unlock 동작보다 앞에서 실행된다.
2-Phase-Locking-Protocal
다수의 트랜잭션이 동시에 실행될 때 conflict serializability를 보장하는 동시성 제어 방법이며,
직렬 가능성(serializability)을 보장한다
락의 사용은 모든 락 동작은 최초의 unlock 동작보다 앞에서 실행되게 하는 것이다.
2PL 은 2가지의 Phase(단계)로 구성되어 있다.

확장 단계(Growing Phase)
확장 단계는 락을 취득하기만 하는 단계이며, 반환하지 않는다.
축소 단계(Shrinking Phase)
취득했던 락을 반환하는 단계이면, 취득하지 않는다.
직렬 가능성은 보장! 데드락은…
추가로 2PL 은 직렬 가능성은 보장하지만 DeadLock으로 부터의 안전은 보장하지 않는다.

위처럼 A 트랜잭션도 Y의 락을 대기하고 B 트랜잭션도 X의 락을 대기하며 wait 상태로 변한다.
2PL을 지켰지만 데드락을 피하지 못한 것이다.
데드락을 해결하는 방법은 DBMS마다 다를 수 있으며 MySQL은 데드락 탐지를 통해 데드락을 검사하고 강제종료하거나, timeout 을 통해 데드락을 해결한다.
2PL은 어떻게 구현하냐에 따라 장단점이 있다.
다음은 데드락 발생율을 떨어트리면서 2PL 구현 방법을 설명한다.
conservative 2PL

모든 lock을 취득한 뒤 transaction 시작하는 2PL
처음 트랜잭션을 시작할 때 락을 취득한다.
- deadlock으로 부터 안전
- 실용적인 방법은 아님 (이유는 lock을 한꺼번에 점유하기 때문에 트랜잭션을 시작하기 쉽지 않음)
strict 2PL

write lock은 무조건 commit 이후에 반환한다.
- recoverablity 보장
- strict schedule을 보장하는 2PL
strong strict 2PL

read/write lock은 무조건 commit 이후에 반환한다
- recoverablity 보장
- strict schedule을 보장하는 2PL
- 구현이 쉬움
락은 만능인가?
락을 사용하면 멀티 트랜잭션 상황에서의 동시성 문제를 해결할 수 있다.
하지만 다른 방향의 문제가 생긴다.

멀티 트랜잭션 환경에서 모든 트랜잭션이 읽기만해야 동시에 처리가 가능하다.
이말은 즉 락은 동시성 성능이 매우 떨어지게 된다.
현대의 RDBMS는 데이터의 일관성도 중요하게 생각하지만 동시성 성능을 매우 중요시한다.
따라서 (쓰기,쓰기) 작업은 비록 심각한 위험을 초래할 수 있기 때문에 락을 사용한다고 해도, (읽기,쓰기)
만큼은 락을 사용하지 않은 읽기를 보장해주고 싶었다.

따라서 MVCC 라는 Multi-Version-Concurrency-Control 이라는 기능을 도입했고 현대의 RDBMS 대부분은 이 기능을 지원한다.
요약
웹서버는 멀티 테스킹 환경이기 때문에 DB의 쿼리 처리 환경은 항상 동시적인 상황이란걸 인지해야한다.
멀티 트랜잭션 환경에서 DB는 동시성 문제를 해결하여 데이터의 일관성을 지켜야하고 트랜잭션사이의 고립을 보장해야한다.
이런 동시성 문제를 해결하는 수단은 Lock이고 Lock으로 2PL의 방식으로 직렬 가능성을 보장한다.
하지만 Lock을 사용한다면 데드락의 위험에 고민을 해야한다.
또한 무엇보다 Lost Update 같은 (쓰기,쓰기) 상황에서는 락을 사용 해야하지만 읽기,쓰기 작업까지 락을 사용한다면 동시성 성능이 매우 떨어지므로 다른 MVCC 기술을 생각 해봐야한다.
'백엔드 > DB' 카테고리의 다른 글
[데이터베이스] InnoDB 스토리지 엔진 아키텍처 딥다이브 (3) | 2024.10.17 |
---|---|
[데이터베이스] MySQL 아키텍처 (2) | 2024.10.17 |