-
[데이터베이스] 회복과 병행제어 (병행제어)IT/데이터베이스 2022. 10. 11. 22:35
본 포스팅은 "데이터베이스 개론 [2판]" 도서로 공부한 내용을 요약하기 위한 포스팅입니다.
1. 병행 수행과 병행 제어 (Concurrency & Concurrency control)
데이터베이스 관리 시스템은 여러 사용자가 데이터베이스를 동시에 공유할 수 있도록 여러 개의 트랜잭션이 동시에 수행되는 병행 수행(Concurrency)을 지원한다. 병행 수행은 실제로 여러 트랜잭션이 차례로 번갈아 수행되는 인터리빙(Interleaving) 방식으로 진행된다.
그런데 병행 수행되는 트랜잭션들이 서로 다른 데이터를 사용하여 연산을 실행하는 경우에는 괜찮지만 동시에 같은 데이터에 접근하여 변경 연산을 실행하려고 하면 예상치 못한 결과가 나타날 수도 있다. 그러므로 병행 수행을 하더라도 각 트랜잭션이 다른 트랜잭션의 방해를 받지 않고 정확한 수행 결과를 얻을 수 있도록 제어해야 한다.
여러 개의 트랜잭션이 병행 수행되면서 같은 데이터에 접근하여 연산을 실행하더라도, 문제가 발생하지 않고 정확한 수행 결과를 얻을 수 있도록 트랜잭션의 수행을 제어하는 것을 병행 제어(Concurrency control) 또는 동시성 제어라고 한다.
2. 병행 수행의 문제
병행 수행은 여러 트랜잭션들을 순차적으로 수행하지 않고 번갈아 수행되는 인터리빙 방식으로 트랜잭션을 수행하기 때문에 병행 수행에 대한 특별한 제어가 없다면 대표적으로 다음과 같은 문제점들이 발생한다.
2-1. 갱신 분실 (Lost update)
갱신 분실(lost update)은 하나의 트랜잭션이 수행한 데이터 변경 연산의 결과를 다른 트랜잭션이 덮어써 변경 연산이 무효화되는 것이다.
아래 예시와 같이 트랜잭션 T1, T2가 병행 수행되며 동일한 X에 접근하고 변경 연산을 수행하는 경우를 살펴보자.
1. 트랜잭션 T1은 기존 데이터베이스에 있는 X값을 불러들여 변경 연산을 수행한다.
2. 트랜잭션 T2는 T1에서 아직 write(X) 연산이 수행되지 않았기에 기존 데이터 베이시스에 있는 X 값을 불러들인다.
3. 트랜잭션 T1의 연산 결과가 write(X) 연산에 의해 데이터베이스에 저장된다.
4. 트랜잭션 T2의 연산 결과가 write(X) 연산에 의해 데이터베이스에 저장된다.
5. 최종적으로 트랜잭션 T1의 연산 결과는 덮어지고 T2의 연산결과만 남게 된다.
* 해당 트랜잭션들이 순차적으로 진행되었다면 최종 X 값은 (3000+1000) * 0.5 = 2000이 된다.2-2. 모순성 (Inconsistency)
모순성(inconsistency)은 하나의 트랜잭션이 여러 개의 데이터 변경 연산을 실행할 때 일관성 없는 상태의 데이터베이스에서 데이터를 가져와 연산을 실행함으로써 모순된 결과가 발생하는 것이다.
다시 말해, 어떤 연산은 현재의 트랜잭션이 실행되기 전 상태의 데이터베이스에서 데이터를 가져와 실행하고, 또 다른 연산은 다른 트랜잭션이 변경한 데이터베이스에서 데이터를 가져와 실행하면 모순성의 문제가 발생할 수 있다.
아래 예시와 같이 트랜잭션 T1, T2가 병행 수행되며 동일한 X,Y에 접근하고 변경 연산을 수행하는 경우를 살펴보자.
1. 트랜잭션 T1은 기존 데이터베이스에 있는 X값을 불러들여 변경 연산을 수행 후 write(X) 한다.
(아직 처리되지 못한 Y값에 대한 변경 연산이 남아있다.)
2. 트랜잭션 T2는 데이터베이스에 있는 X 값과 Y 값을 각각 읽어들여 변경 연산 수행 후 write(X,Y) 한다.
3. 트랜잭션 T1은 T2로 인해 변경되어 저장된 Y값을 읽어와 변경 연산을 수행한다.
4. 최종적으로 T1의 Y 값 연산은 일관성 없는 상태의 데이터베이스에서 데이터를 가져와 연산을 수행한 결과 발생함.
* 해당 트랜잭션들이 순차적으로 진행되었다면 최종 X,Y 값은 (3000+1000) * 0.5 = 2000이 된다.2-3. 연쇄 복귀 (Cascading rollback)
연쇄 복귀(cascading roolback)는 트랜잭션이 완료되기 전에 장애가 발생하여 rollback 연산을 수행하면, 이 트랜잭션이 장애 발생 전에 변경한 데이터를 가져가 변경 연산을 실행한 또 다른 트랜잭션에도 rollback 연산을 연쇄적으로 실행해야 한다는 것이다. 그런데 장애가 발생한 트랜잭션이 rollback 연산을 실행하기 전에, 변경한 데이터를 가져가 사용한 다른 트랜잭션이 수행을 완료해버리면 rollback 연산을 실행할 수 없어 큰 문제가 발생하게 된다.
아래 예시와 같이 트랜잭션 T1, T2가 병행 수행 동일한 값에 접근하여 병행 수행 중 장애가 발생한 경우를 살펴보자.
1. 트랜잭션 T1은 기존 데이터베이스에 있는 X값을 불러들여 변경 연산을 수행 후 write(X) 한다.
(아직 처리되지 못한 Y값에 대한 변경 연산이 남아있다.)
2. 트랜잭션 T2는 데이터베이스에 있는 X 값과 Y 값을 각각 읽어들여 변경 연산 수행 후 write(X,Y) 한다.
3. 트랜잭션 T1의 잔여 연산 수행 중 장애가 발생
4. rollback T1을 통해 트랜잭션 원상 복구 시도
5. T1에서 변경 연산이 수행된 X 값을 사용한 T2 역시 rollback 되어야 하지만 T2 트랜잭션의 모든 연산이 완료되어
rollback 불가능
3. 트랜잭션 스케쥴
병행 수행에서는 트랜잭션들이 차례로 번갈아 가면서 수행되는 인터리빙 방식으로 진행된다고 설명했다.
트랜잭션에 있는 연산을 실행하는 순서에 따라 트랜잭션들의 수행 결과가 달라지기도 하고, 병행 수행에 따른 문제가 발생하기도 한다. 그러므로 여러 트랜잭션을 병행 수행할 때는 트랜잭션들의 연산을 실행하는 순서가 중요하다.
트랜잭션 스케쥴은 트랜잭션에 포함되어 있는 연산들을 수행하는 순서로 아래와 같이 세 가지 유형으로 구분할 수 있다.
트랜잭션 스케쥴 의미 직렬 스케쥴
(Serial schedule)인터리빙 방식을 이용하지 않고 각 트랜잭션별로 연산들을 순차적으로 실행시키는 것 비직렬 스케쥴
(Nonserial schedule)인터리빙 방식을 이용하여 트랜잭션들을 병행해서 수행시키는 것 직렬 가능 스케쥴
(Serializable schedule)직렬 스케줄과 같이 정확한 결과를 생성하는 비직렬 스케쥴 3-1. 직렬 스케쥴 (Serial schedule)
직렬 스케쥴은 인터리빙 방식을 이용하지 않고 각 트랜잭션별로 연산들을 순차적으로 실행시키는 것이다.
트랜잭션이 직렬 스케쥴에 따라 수행되면, 모든 트랜잭션이 완료될때까지 다른 트랜잭션의 방해를 받지 않고 독립적으로 수행됨으로, 트랜잭션이 수행되고 나면 항상 모순이 없는 정확한 결과를 얻는다.
하지만, 인터리빙 방식을 사용하지 않고 각 트랜잭션을 독립적으로 수행하기 때문에 트랜잭션들이 동시에 수행되는 병행 수행이라고 할 수 없으며, 일반적으로 잘 사용하지 않는다.
3-2. 비직렬 스케쥴 (Nonserial schedule)
비직렬 스케쥴은 인터리빙 방식을 이용하여 트랜잭션을 병행해서 수행시키는 것이다.
비직렬 스케쥴은 트랜잭션이 돌아가면서 연산들을 실행하기 때문에 하나의 트랜잭션이 완료되기 전에 다른 트랜잭션의 연산이 실행될 수 있다.
비직렬 스케쥴에 따라 여러 트랜잭션을 병행 수행하면 앞에서 설명한 갱신 분실, 모순성, 연쇄 복귀 등의 문제가 발생할 수 있어 최종 수행 결과의 정확성을 보장할 수 없다.
3-3. 직렬 가능 스케쥴 (Serializable schedule)
직렬 가능 스케쥴은 직렬 스케쥴에 따라 수행한 것과 같이 정확한 결과를 생성하는 비직렬 스케쥴이다.
직렬 가능 스케쥴은 인터리빙 방식을 이용하여 여러 트랜잭션을 병행 수행하면서도 정확한 결과를 얻을 수 있는 장점이 있지만, 모든 비직렬 스케쥴이 직렬 가능한 것은 아니므로, 직렬 가능 스케쥴인지 여부를 판단하는 선 작업이 필요하다.
다수의 트랜잭션을 대상으로 비직렬 스케쥴을 찾아내는 것이 어려울 뿐만 아니라, 하나씩 수행해보면서 직렬 스케쥴과 같은 결과가 나오는지 비교하는 것도 간단한 작업이 아니기 때문에 대부분의 데이터베이스 관리 시스템에서는 직렬 가능 스케쥴인지를 검사하기보다는 직렬 가능성을 보장하는 병행 제어 기법을 사용한다.
4. 병행 제어 기법 (Concurrency control)
병행 제어 기법은 여러 트랜잭션을 병행 수행하면서도 정확한 결과를 얻을 수 있는 직렬 가능성을 보장받기 위해 사용한다. 병행 제어 기법의 기본 원리는 모든 트랜잭션이 따르면 직렬 가능성이 보장되는 나름의 규약을 정의하고, 트랜잭션들이 이 규약을 따르도록 하는 것이다.
그러므로 트랜잭션 스케쥴이 직렬 가능 스케쥴인지를 미리 검사할 필요가 없다. 스케쥴 내의 모든 트랜잭션이 병행 제어기법에서 정의한 규약을 따르면 해당 스케쥴은 직렬 가능성을 보장할 수 있다.
4-1. 로킹 기법의 개념 (Locking protocol)
로킹 기법은 병행 수행되는 트랜잭션들이 동일한 데이터에 동시에 접근하지 못하도록 lock과 unlock이라는 2개의 연산을 이용해 제어한다. 로킹 기법의 기본 원리는 한 트랜잭션이 먼저 접근한 데이터에 대한 연산을 모두 마칠 때까지, 해당 데이터에 다른 트랜잭션이 접근하지 못하도록 상호 배제(mutual exclusion)하여 직렬 가능성을 보장하는 것이다.
일반적으로 데이터베이스에 있는 데이터에 접근이 필요한 연산은 데이터를 읽어오는 read와 기록하는 write다.
그러므로 기본 로킹 규약에 따르면 트랜잭션이 데이터에 read 또는 write 연산을 실행하기 전에 반드시 lock 연산을 실행해야한다. 하지만 다른 트랜잭션이 이미 lock 연산을 실행한 데이터에는 다시 lock 연산이 실행될 수 없다. 트랜잭션이 lock 연산을 통해 독점권을 획득한 데이터에 대한 모든 연산을 수행하고 나면 unlock 연산을 실행해서 독점권을 반납해야 한다.
lock 연산은 크게는 전체 데이터베이스부터 작게는 데이터베이스를 구성하는 속성에 이르기까지 다양한 크기의 데이터를 대상으로 실행할 수 있다. 로킹 단위가 커질수록 병행성은 낮아지지만 제어가 쉽고, 로킹 단위가 작아질수록 제어가 어렵지만 병행성은 높아진다. 그러므로 시스템에 따라 적절한 로킹 단위를 선택하는 것이 중요하다.
기본 로킹 기법을 사용하면 병행 수행을 제어하는 목표는 이룰 수 있지만 너무 엄격한 제약으로 인해 어떤 순간이든 데이터에 대한 독점권을 하나의 트랜잭션만 가지게 된다. 하지만 데이터를 단순히 읽어오기만 하는 read 연산의 경우, 다른 트랜잭션이 같은 데이터에 동시에 read 연산을 실행해도 문제가 생기지는 않는다. 그러므로 트랜잭션들이 하나의 데이터에 read 연산을 동시에 실행할 수 있도록 해서 처리 효율성을 높일 필요가 있다. 같은 데이터에 트랜젝션들이 read 연산을 동시에 실행하는 것을 허용하도록 lock 연산을 아래와 같이 두 종류로 구분할 수 있다.
연산 설명 공용(shared) lock 트랜잭션이 데이터에 대해 공용 lock 연산을 실행하면, 해당 데이터에 read 연산을 실행할 수 있지만 write 연산은 실행할 수 없다.
그리고 해당 데이터에 다른 트랜잭션도 공용 lock 연산을 동시에 실행할 수 있다.전용(exclusive) lock 트랜잭션이 데이터에 전용 lock 연산을 실행하면 해당 데이터에 read 연산과 write 연산을 모두 실행할 수 있다. 그러나 해당 데이터에 다른 트랜잭션은 공용이든 전용이든 어떤 lock 연산도 실행할 수 없다. 하지만 기본 로킹 규약만으로는 트랜잭션 스케쥴의 직렬 가능성을 완벽하게 보장할 수 없다. 즉, 모든 트랜잭션이 기본 로킹 규약을 지키더라도 잘못된 결과를 얻을 수 있다.
아래의 예시를 살펴보자.
기본 로킹 규약을 지켰지만 직렬 가능성이 보장되지 않는 스케쥴을 나타냈고
병행 수행의 문제 중 모순성 문제가 발생했던 경우와 동일한 경우이며,
트랜잭션 T1에서 X에 대한 연산 수행 이후 & Y에 대한 연산 수행 이전에 트랜잭션 T2가 Y값을 변경하는 경우이다.
* 해당 트랜잭션들이 순차적으로 진행되었다면 최종 X,Y 값은 (3000+1000) * 0.5 = 2000이 된다.4-2. 2단계 로킹 규약 (2PLP : 2 Phase Locking Protocol)
기본 로킹 규약의 문제를 해결하고 트랜잭션의 직렬 가능성을 보장하기 위해 lock과 unlock 연산의 수행 시점에 대한 새로운 규약을 추가한 것이 2단계 로킹 규약이다. 트랜잭션 스케쥴의 모든 트랜잭션이 2단계 로킹 규약을 준수하면 해당 스케쥴은 직렬 가능성이 보장된다.
2단계 로킹 규약을 따르려면 모든 트랜잭션이 lock과 unlock 연산을 아래와 같이 2단계로 나누어 실행해야 한다.
트랜잭션이 처음에 수행되면 확장 단계로 들어가 lock 연산만 실행할 수 있다.
그러다가 unlock 연산을 실행하면 축소 단계로 들어가 그때부터는 unlock 연산만 실행할 수 있다.
2단계 로킹 규약을 준수하는 트랜잭션은 첫 번째 unlock 연산을 실행하기 전에 필요한 모든 lock 연산을 실행해야 한다.
아래 그림은 2단계 로킹 규약을 적용한 스케쥴의 예시이다.
2단계 로킹 규약을 적용하면 트랜잭션 스케쥴의 직렬 가능성을 보장할 수 있지만, 교착 상태(Deadlock)이 발생할 수도 있다. 교착 상태에 빠지면 트랜잭션들은 더 이상 수행을 계속하지 못하고 상대 트랜잭션이 먼저 unlock 연산을 실행해주기를 한없이 기다리게 된다.
예를 들어 트랜잭션 T1과 T2가 모두 데이터 X와 데이터 Y에 접근하는 상황을 생각해보자.
트랜잭션 T1이 T2가 lock한 데이터 X에 접근하기 위해 T2가 unlock 연산을 실행해주기를 기다리고 있고,
T2는 T1이 lock한 데이터 Y에 접근하기 위해 T1이 unlock 연산을 실행해주기를 기다리고 있다면 교착 상태가 된다.교착 상태는 처음부터 발생하지 않도록 예방하거나, 발생했을 때 빨리 탐지하여 필요한 조치를 취하는 방법으로 해결한다.
'IT > 데이터베이스' 카테고리의 다른 글
[데이터베이스] 보안과 권한 관리 (0) 2022.10.11 [데이터베이스] 회복과 병행제어 (회복 기법) (1) 2022.10.11 [데이터베이스] 회복과 병행 제어 (트랜잭션) (0) 2022.10.11 [데이터베이스] 정규화 (0) 2022.10.11 [데이터베이스] 데이터베이스 설계 (E-R 모델과 릴레이션 변환규칙) (0) 2022.10.11