동시성 제어(Concurrency Control)
동시에 실행되는 여러 개의 트랜잭션이 작업을 성공적으로 마칠 수 있도록 트랜잭션의 실행 순서를 제어하는 기법이다
- 다중 사용자 환경을 지원하는 DB 시스템에서 여러 트랜잭션들이 성공적으로 동시에 실행될 수 있도록 지원하는 기능으로 DB의 일관성을 해치지 않도록하는 트랜잭션의 데이터 접근기술이다.
- 다중 사용자 환경을 지원하는 DBMS의 경우 필수적으로 지원해야 하는 기능으로 병행제어라고도 한다.
- 트랜잭션의 직렬화 수행 보장
ACID의 중 하나인 고립성은 상호 간의 트랜잭션을 독립적으로 만들어준다. 그런데 2개 이상의 트랜잭션이 하나의 값에 접근하는 경우에는 어떻게 될까?
2개의 트랜잭션이 모두 읽는 경우에는 문제가 발생하지 않지만, 1개의 트랜잭션은 쓰고 1개의 트랜잭션은 읽는 경우 상황에 따라 오손 읽기, 반복불가능 읽기, 유령데이터 읽기 문제가 발생할 수 있으며, 2개의 트랜잭션이 모두 쓰기(Write)를 통해 병행 수행을 하는 경우에 갱신 손실, 모순성, 연쇄 복귀 등의 문제가 발생할 수 있다.
그렇다면 동시성 제어를 하는 목적은 무엇일까? 동시성을 제어하는 목적은 다음과 같다.
- 트랜잭션의 직렬성 보장, 동시 수행 트랜잭션 처리량 최대화
- 공유도 최대, 응답 시간 최소, 시스템 활동의 최대 보장
- 데이터의 무결성 및 일관성 보장
동시성을 제어하지 않는 경우 문제점
갱신 손실(Lost Update)
- 하나의 트랜잭션이 갱신한 내용을 다른 트랜잭션이 덮어씀(Overwrite)으로써 갱신이 무효화가 되는 것을 의미한다.
- 두 개의 트랜잭션이 한 개의 데이터를 동시에 갱신(Update)할 때 발생
- 데이터베이스에서 절대 발생하면 안되는 현상
갱신 손실은 하나의 값에 계속해서 덮어쓰기(Overwrite)하여 데이터의 갱신이 무효화 되는 현상이다.
초기값 X = 1000이 있고, 트랜잭션 T1은 X +=300, 트랜잭션 T2는 X-=500 이라 할 때, 동시성 제어를 해주지 않으면 1000이라는 값이 T1에 의해 1300이 된 상태에서 T2가 아직 1300으로 Write 되기 전인 X=1000을 Read하여 1300-500이 아니라 1000-500이 수행되어 500을 갖게 되었다.
이때 T1과 T2가 순차적으로 값을 저장하여 덮어씌워지는 값의 손실을 갱신손실이라 한다.
현황 파악 오류(Dirty Read)
- 읽기 작업을 하는 트랜잭션 1이 쓰기 작업을 하는 트랜잭션2가 작업한 중간 데이터를 읽기 때문에 발생하는 문제
- 즉, 트랜잭션 2의 작업을 Commit하기 전의 데이터를 트랜잭션 1이 읽어서 발생하는 문제.
- 작업중인 트랜잭션 2가 작업을 Rollback한 경우 트랜잭션 1은 무효가 된 데이터를 읽게 되고 잘못된 결과를 도출한다.
아래의 그림과 같이 T1은 T2가 Rollback되기 전의 데이터 21 읽었고, T2가 Rollback이 되면 T1이 의미가 없는 값을 갖게 되므로 문제가 발생한다. 그리고 이러한 무효가 된 데이터를 읽게되어 발생하는 문제를 현황 파악 오류(Dirty Read)라고 한다.
모순성(Inconsistency)
- 다른 트랜잭션들이 해당 항목 값을 갱신하는 동안 한 트랜잭션이 두 개의 항목 값 중 어떤 것은 갱신되기 전의 값을 읽고 다른 것은 갱신된 후의 값을 읽게 되어 데이터의 불일치가 발생하는 상황
초기값 X=1500, Y=1000이 있고, 트랜잭션 T1은 X와 Y를 300 증가, 트랜잭션 T2는 X와 Y를 3배씩 증가시킨다고 하자.
트랜잭션 T1이 수행될 때 X와 Y는 각각 1500과 1000이 읽혀야 한다. T1이 수행되면서 X만을 1800으로 증가시키고 Write된 다음에 Y를 수행하는 것이 아닌 트랜잭션 T2가 수행된다고 하자.
그러면 T2에 의해 X와 Y는 각각 5400과 3000이 되고 다시 T1의 Y가 Read를 해야하는 상황에서 1000이 아니라 3000이 읽히게 된다. 이렇게 어떤 값은 갱신 전의 값을, 다른 값은 갱신 후의 값을 읽어 데이터가 불일치하는 것을 모순성이라 한다.
연쇄 복귀(Cascading Rollback)
- 두 트랜잭션이 동일한 데이터 내용을 접근할 때 발생
- 한 트랜잭션이 데이터를 갱신한 다음 실패하여 Rollback 연산을 수행하는 과정에서 갱신과 Rollback 연산을 실행하고 있는 사이에 해당 데이터를 읽어서 사용할 때 발생할 수 있는 문제
초기값 X=1500, Y=1000과 X를 300 증가시키고 Y를 200 감소시키는 트랜잭션 T1과 X항목을 3배 증가시키는 트랜잭션 T2가 있다고 하자.
T1이 X를 300 증가시키고 Write 하여 1800이 된 상황에서 T2 트랜잭션이 실행되어 X를 3배하여 Write한 후 종료되었다고 가정하자. 그리고 T1이 이제 Rollback 연산을 하여 X를 1500으로 다시 돌려놓으려고 하는데 T2는 이미 해당 트랜잭션을 완료하여 종료되었으므로 롤백할 수가 없게 되버리는 문제가 발생한다.
동시성 제어 기법의 종류
1. Locking
트랜잭션들이 동일한 데이터 항목에 대해 임의적인 병행 접근을 하지 못하도록 제어하는 것
- 트랜잭션 T가 데이터 항목 X에 대해 Read(X) or Write(X)연산을 수행하려면 반드시 lock(X) 연산을 해주어야 함
- 트랜잭션 T가 실행한 lock(X)에 대해서는 해당 트랜잭션이 종료되기 전에 반드시 unlock(X)연산을 해주어야 함
- 트랜잭션 T는 다른 트랜잭션에 의해 이미 lock이 걸려 있는 X에 대해 다시 lock(X)를 수행시키지 못함.
- 트랜잭션 T가 X에 lock을 걸지 않았다면, unlock(X)를 수행시키지 못함.
즉, 여러 개의 트랜잭션들이 하나의 데이터로 동시에 접근하려고 할 때 이를 제어해주는 도구가 바로 Lock이다.
락은 트랜잭션이 읽기를 할 때 사용하는 공유락(LS, Shared Lock)과 읽고 쓰기를 할 때 사용하는 배타락(LX, Exclusive Lock)으로 나뉜다.
트랜잭션 T가 데이터 항목 X에 대하여 Shared-Lock을 설정할 경우, 트랜잭션 T는 해당 데이터 항목에 대해서 읽을 수 있지만 기록할 수 없다. 그리고 Read는 서로 영향을 주지 않으므로 다른 트랜잭션도 Shared-Lock이 설정된 X에 대해서 Shared-Lock을 동시에 설정할 수 있다.
트랜잭션 T가 데이터 항목 X에 대하여 Exclusive-Lock을 설정할 경우, 트랜잭션 T는 해당 데이터 항목에 대해서 읽을 수도 있고, 기록할 수도 있다. Write는 영향을 주는 작업이므로 다른 트랜잭션은 Exclusive-Lock을 설정한 데이터 항목 X에 대해서 어떠한 lock도 설정할 수 없다.
공유락과 베타락을 사용하는 규칙
- 데이터에 락이 걸려있지 않으면 트랜잭션은 데이터에 락을 걸 수 있다.
- 트랜잭션이 데이터 X를 읽기만 할 경우 LS(X)를 요청하고, 읽거나 쓸 경우 LX(X)를 요청한다.
- 다른 트랜잭션이 데이터에 LS(X)를 걸어둔 경우, LS(X)의 요청은 허용하고 LX(X)는 허용하지 않는다.
- 다른 트랜잭션이 데이터에 LX(X)를 걸어둔 경우, LS(X)와 LX(X) 모두 허용하지 않는다.
- 트랜잭션이 락을 허용받지 못하면 대기 상태가 된다.
2. 2단계 락킹 (2 Phase Locking)
락을 걸고 해제하는 시점에 제한을 두지 않으면 두 개의 트랜잭션이 동시에 실행될 때 데이터의 일관성이 깨질 수 있어서 로킹 단계를 2개로 구분하여 이를 방지하는 방법
- 확장 단계(Growing Phase): 트랜잭션은 새로운 lock 연산만 할 수 있고, unlock 연산은 할 수 없는 단계
- 축소 단계(Shrinking Phase): 트랜잭션은 unlock 연산만 실행할 수 있고, lock 연산은 실행할 수 없는 단계
※ 참고
로킹 단위: 로킹 기법에서 사용하는 lock 연산의 대상
로킹단위가 속성->튜플->릴레이션->데이터베이스로 커질수록 구현이 용이하고 Lock의 수가 적어지며 제어가 간단하지만 병행성이 떨어지는 반면 로킹단위가 작을수록 구현이 복잡하고 Lock의 수가 많고 제어 기법이 복잡하여도 병행성을 높일 수 있다.
2단계 Locking 규약(2PLP: Two-Phase Locking Protocol)은 트랜잭션들이 lock하는 시간과 unlock을 하는 시간을 구분하여 수행하도록 하는 것이다.
2단계 Locking 규약을 사용하지 않으면 아래의 그림과 같이 일관성이 위배되는 문제가 발생할 수 있다. 그래서 임의의 시간에 lock 또는 unlock을 하는 것이 아니라 확장 단계와 축소 단계를 두어 이를 해결해준다.
2PLP는 직렬 가능성을 보장할 수 있는 규약으로 가장 많이 사용되지만 교착상태(데드락, Deadlock)가 발생할 가능성이 있으므로 이를 해결해주어야 한다.
2단계 락킹을 사용하지 않았을 때의 문제
A = 1,000, B = 1,000으로 최종적으론 A=990, B=1,210으로 계산되어 A+B=2,200을 원함.
1. T1이 A에 대해 배타락(LX)을 걸고 시작
- A 값을 가져오고 100을 감소시킨 후 A에 저장하고 A에 대한 배타락을 푼다.
2. A에 대한 락이 없기에 T2에서 A에 접근할 수 있다.
- A에 배타락을 걸고 A에 대한 처리 후 배타락을 푼다.
- B에 대한 배타락을 걸고 B에 대한 처리 후 배타락을 푼다.
3. T1이 B에 대해 배타락을 걸고 시작
- B를 가져오고 100을 증가시킨 후 B를 저장 B에 대한 배타락을 푼다
문제점
<T1이 끝난 후, T2가 실행되거나>, <T2가 끝난 후, T1이 실행되어야 하는데>
T1이 끝나지도 않은 상태에서 T2가 중간에 개입하는 문제점이 발생했다.
그러므로 T1에서 A에 대한 처리가 끝나더라도, 락을 풀면 안된다.
해결 - 2단계 락킹 기법을 사용한다.
데드락(Deadlock)
- 두 개 이상의 트랜잭션이 각각 자신의 데이터에 대하여 락을 획득하고 상대방 데이터에 대하여 락을 요청하면 무한 대기 상태에 빠질 수 있는 현상
- 데드락은 교착상태라고도 한다.
데이터베이스의 데드락과 운영체제의 데드락은 상당히 비슷하다.
위의 정의를 풀어 설명하면 공통된 자원을 이용하기 위해 여러 개의 트랜잭션이 서로 lock을 걸어주다가 무한 대기 상태에 빠지는 것을 데드락이라 한다.
예를 들어 먼저 T1에서 A에 대해 락을 걸고 T2에서 B에 대해 락을 걸었다고 하자. 그리고 T1에서 B에 대해 락을 걸고 T2가 A에 대해 락을 건다면 T1과 T2는 서로 A, B에 대한 락을 유지하며 무한루프에 빠지게 된다. 일반적으로 데드락이 발생하면 DBMS가 T1 혹은 T2 중 하나를 강제로 중지시켜 한 트랜잭션은 정상적으로 실행되며 중지된 트랜잭션에서 변경한 데이터는 원래 상태로 되돌려 놓는다.
3. 타임 스탬프(timestamp)
트랜잭션을 식별하기 위하여 DBMS가 부여하는 유일한 식별자인 타임 스탬프를 지정하여 트랜잭션간의 순서를 미리 선택하는 동시성 제어 기법
- 시스템에 들어오는 트랜잭션의 순서대로 시간 스탬프를 지정하여 동시성 제어의 기준으로 사용한다.
- 교착상태를 방지 할 수 있으나 RollBack 발생률이 높고 연쇄 복귀를 초래할 수 있다.
운영방식
read_TS(x) : read(x) 연산을 성공적으로 수행한 트랜잭션들의 타임스탬프 중 가장 큰 것
write_TS(x) : write(x) 연산을 성공적으로 수행한 트랜잭션들의 타임스탬프 중 가장 큰 것
트랜잭션 Ti가 Write(x) 수행 시 TS(Ti) < write_TS(x) 이면 TS(Ti)가 write(x)를 실행한 것으로 간주하고 무시하는 것이 토마스 기록 규칙(Thomas write rule), 이는 write 연산의거부로 인한 트랜잭션의 취소를 감소시키기 위한 목적이다.
Timestamp 생성기법
- 논리적 계수기(Logical Count) : 트랜잭션이 발생할 때마다 카운터를 하나씩 증가한다.
- 시스템 클럭(System clock) : 트랜잭션이 시스템에 들어올때의 시스템 시각을 부여한다.
4. 낙관적 검증
트랜잭션 수행 동안은 어떠한 검사도 하지 않고, 트랜잭션 종료 시에 일괄적으로 검사하는 기법.
- 트랜잭션 수행 동안 그 트랜잭션을 위해 유지되는 데이터 항목들의 지역 사본에 대해서만 갱신이 이루어진다.
- 트랜잭션 종료 시에 동시성을 위한 트랜잭션 직렬화가 검증되면 일시에 DB로 반영한다.
낙관적 검증 기법 구성도
낙관적 검증 수행 단계
1. 트랜잭션의 실행을 3단계로 나눈다.
- R, V, W 로 나누어 실행
2. 트랜잭션에 3가지 타임 스탬프 사용한다.
3. 확인 검사 조건
- TS(Ti)<TS(Tj)의 관계에 있는 모든 트랜잭션 Ti 에 대해 위 3가지 조건 중 하나만 만족하면 트랜잭션들 간에 간섭이 없는 것으로 보고 확인 검사를 성공 처리한다.
- 하나라도 만족하지 못하면 트랜잭션 간의 간섭이 일어났을 가능성이 있기 때문에 실패 처리한다.
4. 직렬 가능 순서
이 기법의 직렬가능성 순서는 Validation(Ti) 타임스탬프의 값에 기초한 순서로 결정되므로 트랜잭션 Ti의 타임스탬프는 실제로 Validation(Ti)이라고 할 수 있다.
따라서 트랜잭션 Ti와 Tj에 대해 TS(Ti) < TS(Tj)이면 실행된 스케줄은 반드시 Ti가 Tj보다 먼저 나오는 직렬 스케줄을 처리 해야한다.
5. 다중버전 병행제어 기법 (MVCC)
트랜잭션이 한 데이터 아이템을 접근하려 할 때, 그 트랜잭션의 타임스탬프와 접근하려는 데이터 아이템의 여러 버전의 타임스탬프를 비교하여, 현재 실행하고 있는 스케줄의 직렬가능성이 보장되는 적절한 버전을 선택하여 접근하도록 하는 기법이다. 하나의 데이터 아이템에 대해 여러 버전의 값 유지한다.
- 판독 요청을 거절하지도, 대기하지도 않음
- 기록보다 판독 연산이 주류를 이루는 데이터 베이스 시스템에 큰 이점
- 데이터 아이템을 판독할 때마다 중복되는 디스크 접근
- 트랜잭션간의 충돌문제는 대기가 아니라 복귀처리 함으로 연쇄 복귀초래 발생 가능성
동시성 제어 기법 비교
- 웹 페이지 등의 조회를 중심으로 처리하는 업무는 바로 실행 가능하고, 조회만 처리하므로 Timestamp Ordering 방식 추천
- 업무적인 구분이 명확하여 서로 중복이 거의 없다면, Validation 동시성 제어 기법 적용 권장함
참고