이전 글 : Recoverability 란? [ https://techforme.tistory.com/58 ]
이전까지 동시에 요청된 Transaction 의 처리 Schedule 과 관련한 두가지 특성인 Serializability, Recoverability 를 알아보았다. 이는 동시성 제어(Concurrency Control) 시 고려해야할 두가지 조건이라고 할 수 있으며, 두 특성을 보장하지 못하는 경우 Transaction 이 의도치 않은 결과를 일으킬 가능성이 있게 된다.
이번 글에서는 이러한 Scheduling 을 위한 전략인 Lock 에 대해서 알아본다.
1. Lock
1) lock 이란?
DB 는 Transaction 을 Scheduling 하는 전략으로 'Lock' 이라는 매커니즘을 사용합니다. (이는 Scheduling 을 위한 기술적 방법을 말합니다. 지난 글 까지 Serializability 와 Recoverability 를 보장하기 위한 논리적 조건에 대해서 이야기했는데, Lock 은 이를 실제로 "구현" 하는 구체적 방법인 셈 입니다.) 'Lock' 이란 특정 공유 자원에 대한 Transaction 의 접근을 제한하는 방법으로, Lock 이 걸려있는 경우 Transaction 이 공유 자원에 접근 하기 위해서는 Lock 이라는 별도의 자원을 획득하여야 합니다. 만약 Transaction 해당 자원에 대한 Lock 을 얻지 못하는 경우 Lock 을 획득하기 전까지 대기하여야 하며, 앞서 Lock 을 획득한 Transaction 이 모든 작업을 완료하고 Lock 을 반환하고 나서야 Lock 을 획득하고 작업을 수행할 수 있도록 하는 것입니다.이는 CPU 에서 공유 자원에 대한 Process 의 접근을 컨트롤 하기 위한 전략과 유사합니다.
2) Read Lock / Write Lock
Lock 은 Transaction 이 수행하는 작업의 종류에 따라 Read Lock, Write Lock 으로 나뉩니다. 수행 목적에 따라서 Lock 을 구분해야 여러 상황에서 적절한 Scheduling 이 가능하도록 할 수 있기 때문입니다.
Tx 1 이 특정 공유 자원의 값을 두 번 읽는 작업을 수행하고, Tx 2 는 해당 자원을 변경하는 작업을 수행하는 상황을 생각해봅시다. 만약 Tx 1 이 대상 자원의 Read Lock 을 획득하여 값을 읽었을 때 Tx 2 가 Write Lock 을 획득하여 해당 값을 변경하게 된다면, Tx 1 이 그 다음 읽은 값은 처음 읽은 값과 서로 다르게 되는 오류가 생길 수 있습니다.(Non-Repeatable Read) 이를 방지하기 위해서는 Read Lock 이
그러나 Read Lock 이 걸려있는 자원이라고 해도, 다른 Transaction 이 단순 Read 만을 수행하는 경우에는 전혀 문제가 발생하지 않을 것 입니다. 위와 같이 개발자는 전략에 따라 Lock 과 Lock 사이 의 배타적 성질을 달리할 수 있으며, 이를 통해 동시성을 개발자가 시나리오에 따라 제어할 수 있습니다. 기본적으로는 Lock 의 배타적 성질은 다음과 같이 설정됩니다.
Read Lock | Write Lock | |
Read Lock | O | X |
Write Lock | X | X |
2. 2-Phase-Locking
1) 2PL(2-Phase-Locking) 이란?
지금까지 Lock 이라는 전략을 알아보았습니다. 그렇다면 Lock 을 이용하면 Serializable 한 스케줄링이 가능해질까요? 그렇지 않습니다. Lock 을 걸어 공유 자원의 동시 접근을 막을 수 있었지만 그렇다고 해서 Transaction 이 뒤엉킬 여지는 남아있기 때문입니다. 이런 경우를 생각해 봅시다.
Tx 1 은 B = A + B, Tx 2 는 A = A + B 작업을 수행합니다.
Tx 1 : --> Read A = 10 --(Lock 반환)--> Read B = 20 --> Write B = 30 -- |
Tx 2 : --> Read B = 20 --(Lock 반환)--> Read A = 10 --> Write A = 30 -- |
위 연산의 결과로 A = 30, B = 30 이 나왔습니다. 그러나 이 작업의 Serial Schedule 의 결과는 A = 30 B = 50 또는 A = 40 B = 30 입니다. 이 문제는 근본적으로 Lock 이 모든 Transaction 을 커버하지 않아 Transaction 간의 작업 순서가 뒤엉켜버리기 때문입니다. 이런 문제는 Lock 의 반환과 획득 순서를 배치하는 방식에 따라 해결할 수 있습니다.
Lock의 획득과 반환 매커니즘 또한 동시성을 제어하는 수준에 따라서 달리 설정됩니다. 극단적으로는 Transaction 의 수행과 동시에 모든 공유 자원에 대하여 Lock 을 획득하고, Commit 이후에 Lock 을 반환하면 Transaction 간의 모든 작업을 배타적으로 수행할 수 있기 때문에(Read - Read 제외) 위와 같은 오류가 일어나지 않게 됩니다.
기본적으로는 모든 Lock 반환 작업을 Lock 획득 작업 이후에 배치하게 되면 위와 같은 오류가 해결되며, 이는 Transaction을 Lock 을 습득하는 Phase / Lock 을 반환하는 Phase 로 구분한다는 의미에서 2-Phase-Locking 이라고 불립니다.
2) Conservative 2PL / Strict 2PL / String Strict 2PL
Lock 의 획득 반환 매커니즘에 따라 아래와 같이 분류가 가능합니다.
Lock 획득, 반환 조건 | 특징 | |
2PL | Lock 반환 작업을 모든 Lock 획득 이후에 함 | Deadlock 발생 가능 |
Conservative 2PL | 모든 Lock 획득을 Transaction 시작시 수행함 | Deadlock-free / 실용성 떨어짐 |
Strint 2PL (S2PL) | 모든 Write Lock 반환을 Commit 이후로 함 | Recoverability |
String Strict 2PL (SS2PL) | + 모든 Lock 반환을 Commit 이후로 함 | Recoverability + 구현이 쉽다. |
3. 결론
이번 글에서는 Lock 에 대해서 알아보았습니다. Lock 은 Transaction 의 Isolation 수준을 컨트롤 하는 핵심적 매커니즘입니다. 또한 Lock 의 획득과 반환을 적절히 배치함으로써 Transaction 의 고립 수준을 조절할 수 있다는 것도 알아보았습니다.
이번 글에서는 Read Lock 과 Write Lock 이 배타적 관계에 있는 상황만을 고려하였는데, 사실 이는 근본적으로 Concurrency 를 떨어뜨리는 방식입니다. 다음 글에서는 Write 와 Read Lock 을 상호 허용함으로써 Concurrency 를 올리고 (병렬적 연산이 가능하기 때문에 성능이 올라갑니다.) 그에 따른 문제를 해결하기 위해 도입한 MVCC(Multi Version Concurrency Control) 에 대해서 알아보겠습니다.