본문 바로가기

데이터베이스(DB)

[DB] 9. 트랜잭션(Transaction) - (3) 병행 제어, 로킹, 타임스탬프

반응형

 

 

[목차]

 

1. 병행 제어(Concurrency Control)란?

 

2. 병행의 문제점

 

3. 병행 제어 기법 

 

 

  1. 병행 제어(Concurrency Control)란?

 

병행 제어에 앞서, 먼저 병행에 대해서 알아보자. 

병행(Concurrency)매우 빠르게 여러 트랜잭션 사이를 이동하면서 조금씩 처리를 수행하는 방식이다. 

따라서 실제로는 한 번에 한 트랜잭션만 수행하지만, 마치 동시에 여러 트랜잭션을 수행하는 것처럼 보이도록 하는 것이다. 

 

병행 제어(Concurrency Control)는 이렇게 트랜잭션이 병행 수행될 때 트랜잭션이 데이터베이스의 일관성을 파괴하지 않고, 다른 트랜잭션에 영향을 주지 않도록 트랜잭션 간의 상호작용을 제어하는 것을 말한다. 

 

병행 제어의 목적은 다음과 같다. 

 

  • 데이터베이스의 일관성 유지
  • 데이터베이스 공유 최대화
  • 시스템 활용도 최대화
  • 사용자 응답 시간 최소화
  • 단위 시간당 트랜잭션 처리 건수 최대화

 

  2. 병행의 문제점

 

병행 제어를 하지 않고, 트랜잭션들이 동시에 데이터베이스에 접근할 수 있다면 여러 문제점이 발생한다. 

 

 

1. 갱신 분실 (Lost Update)

 

갱신 분실은 같은 데이터에 대해 둘 이상의 트랜잭션이 동시에 갱신할 때, 갱신 결과의 일부가 없어지는 현상이다. 

 

 

위와 같은 예시를 생각해보자. 

두 개의 트랜잭션이 수행되고, 최종적인 결과로는 x에 800이 더해져야 한다. 

하지만, 같은 데이터인 x에 대해서 트랜잭션 1이 갱신하기 이전에 트랜잭션 2가 x값을 받아와서 결국 최종 결과는 x + 500이 되어버린다. 

이렇게 둘 이상의 트랜잭션이 동시에 같은 데이터를 갱신하면 올바르게 갱신되지 못한다. 

 

 

2. 모순성(Inconsistency)

 

모순성은 하나의 트랜잭션이 여러 데이터 갱신 연산을 수행할 때, 일관성 없는 상태의 데이터베이스에서 데이터를 가져옴으로써 데이터의 불일치가 발생하는 것을 의미한다. 

 

 

위의 예시를 살펴보자. 트랜잭션 1이 완료되기 이전에 트랜잭션 2가 수행되는 모습이다.

 

따라서, 최종 결과로는 y에 500을 더해준 후 4를 곱한 결과가 나와야 하지만, 4를 곱한 y값에 500을 더하는 모순이 발생한다. 

트랜잭션 2의 입장에서는 일관성이 유지되지만, 트랜잭션 1의 입장에서는 실행 전후 차이가 500 만큼이 발생해야 하는데 그보다 더 큰 차이가 발생하기 때문에 데이터의 불일치가 발생한다. 

 

 

3. 연쇄 복귀 (Cascading Rollback)

 

연쇄 복귀는 병행 수행되던 둘 이상의 트랜잭션 중 어느 한 트랜잭션에 오류가 발생하여 Rollback 하는 경우 다른 트랜잭션들도 함께 Rollback 되는 현상을 말한다. 

 

 

4. 비완료 의존성 (uncommitted Dependency)

 

하나의 트랜잭션 수행이 실패한 후 회복하기 전에 다른 트랜잭션이 실패한 갱신 결과를 참조하는 현상을 말한다. 

 

 

  3. 병행 제어 기법

 

1. 로킹 (Locking)

 

로킹은 트랜잭션이 접근하려는 데이터를 다른 트랜잭션이 접근하지 못하도록 잠그는(lock) 병행 제어 기법이다.

이를 통해 상호 배제(Mutual Exclusive) 기능을 제공하며, 잠금을 설정한 트랜잭션이 해제(unlock)할 때까지 데이터를 독점적으로 사용할 수 있다. 

 

한 번에 로킹 할 수 있는 데이터의 크기로킹 단위라고 하며 필드(Field), 레코드(Record), 테이블(Table), 파일(File), 데이터베이스(Database) 모두 로킹 단위가 될 수 있다. 

 

로킹 단위의 크기에 따라 성능의 차이가 발생한다.

로킹 단위가 클수록 병행 제어가 단순해지고 관리하기가 편하지만 병행성 수준이 낮아진다.

반면 로킹 단위가 작을수록 병행 제어가 복잡해지고 오버헤드가 증가하지만, 병행성 수준이 높아지고 데이터베이스 공유도가 높아진다. 

 

기본적으로 로킹은 lockunlock 연산을 사용한다. 로킹 규약은 다음과 같다. 

 

1) 트랜잭션 T가 공유 데이터 x를 접근하려면 먼저 lock(x)을 해야 한다.

2) 공유 데이터를 사용한 T는 반드시 unlock(x)을 해야 한다.

3) 다른 트랜잭션에 의해 lock(x)가 실행되었다면, 트랜잭션 T는 lock(x)을 실행하지 못한다.

4) 트랜잭션 T가 lock(x) 한 것을 다른 트랜잭션이 unlock(x)할 수 없다.

 

이러한 로킹 규약에는 제약이 있다. 

하나의 트랜잭션만이 공유 데이터를 사용할 수 있는데, 실제로 오직 읽기(read)만 하는 경우에는 동시에 접근해도 문제가 없기 때문에 이런 경우에 효율적이지 못하다.

따라서 위와 같은 문제점을 해결하기 위해 사용되는 것이 2단계 로킹 규약이다. 

 

로킹 기법은 교착 상태(Dead lock)가 발생할 수 있다는 한계가 있다. 

교착 상태란, 여러 트랜잭션이 특정 데이터에 lock을 한 채 다른 트랜잭션이 lock을 수행한 데이터에 접근하려고 할 때 실행을 하지 못하고 서로 무한정 기다리는 상태를 말한다. 

 

위의 예시처럼, 트랜잭션 T1에서 x를 lock 하고 T2에서 y를 lock 한 경우에 T1도 y에 접근할 수 없고 T2도 x에 접근할 수 없다. 따라서 서로 무한정 기다리게 된다. 

 

 

2. 2단계 로킹 규약 (Two-Phase Locking, 2PL)

 

2단계 로킹(Two-Phase Locking, 2PL) 규약은 각 트랜잭션의 lock과 unlock 요청을 2단계로 실시하는 방식이다.

단계는 확장 단계(Growing phase)와 축소 단계(shrinking phase)로 나뉜다. 

이를 통해 직렬성을 보장하는 대표적인 로킹 규약이지만 여전히 lock 연산으로 인한 교착상태를 예방할 수는 없다는 단점이 있다. 

 

  • 확장 단계(Growing phase) : 새로운 lock 연산만을 수행할 수 있고, unlock 연산은 수행할 수 없는 단계
  • 축소 단계(shrinking phase) : unlock 연산을 수행할 수 있고, lock연산은 수행할 수 없는 단계

 

2단계 로킹 규약은 트랜잭션 내의 모든 lock 연산이 첫 번째 unlock 연산 이전에 위치해야 한다. 따라서 하나의 트랜잭션에서 데이터에 대한 연산을 완전히 끝낸 후 unlock 하므로 직렬성이 보장된다. 

아래의 그림에서 왼쪽은 2단계 로킹 규약을 만족하는 경우, 오른쪽은 만족하지 않는 경우의 예시이다. 

 

 

 

3. 타임스탬프 순서 (Timestamp ordering) 기법

 

타임스탬프 순서 기법은 비직렬 트랜잭션을 타임스탬프 순서에 따라 직렬화 시키는 방법이다.

데이터에 접근하는 시간(Timestamp)을 미리 정해두어 부여된 시간 순서대로 데이터에 접근하며, lock을 사용하지 않고 시간을 나눠 사용하기 때문에 교착 상태(Dead lock)가 발생하지 않는다. 

하지만, Rollback 발생률이 높고 연쇄 복귀를 초래할 수 있는 단점이 있다. 

 

타임스탬프는 트랜잭션을 유일하게 식별할 수 있는 식별자의 역할을 할 수 있으며 트랜잭션의 시작 시간으로 간주할 수 있다. 타임스탬프를 생성하는 방법은 논리적 계수기(Logical Count) 또는 시스템 클럭(System Clock)을 이용한다. 

 

- 논리적 계수기 : 계수기를 사용하여 트랜잭션이 들어올 때마다 카운터를 하나씩 증가

- 시스템 클럭 : 시스템의 고유 시계 사용

 

 

타임스탬프 순서 기법의 운영 방식은 다음과 같다. 

read_TS(x) : read(x) 연산을 성공적으로 수행한 트랜잭션들의 타임스탬프 중 가장 큰 것

write_TS(x) : write(x) 연산을 성공적으로 수행한 트랜잭션들의 타임스탬프 중 가장 큰 것

 

1) 트랜잭션 T가 read(x)를 수행하려고 할 때

TS(T) < write_TS(x)이면, read(x)를 거부하고 T 취소 & 복귀

TS(T) ≥ write_TS(x)이면, read(x)를 허용하고 read_TS(x) = TS(T)로 갱신

 

2) 트랜잭션 T가 write(x)를 수행하려고 할 때

TS(T) < read_TS(x)이면, write(x)를 거부하고 T 취소 & 복귀

TS(T) < write_TS(x)이면, write(x)를 수행한 것으로 간주하고 무시(Thomas write rule, 트랜잭션 취소 감소 목적)

이외의 경우, write(x)를 허용하고 write_TS(x) = TS(T)로 갱신 

 

 

4. 낙관적 병행 제어 (Optimistic Concurrency Control)

 

낙관적 병행 제어는 트랜잭션 수행 동안은 어떠한 검사를 하지 않고, 트랜잭션이 종료된 이후에 일괄적으로 검사하는 방식이다. 수행 도중에는 트랜잭션을 위해 유지되는 데이터 항목들의 지역 사본에 대해서만 갱신하고, 트랜잭션이 종료되고 난 후에 직렬화를 검증하여 검증되면 데이터베이스에 한 번에 반영하는 방식이다. 

병행 수행하고자 하는 대부분의 트랜잭션이 판독 전용(Read-only)인 경우, 트랜잭션 간 충돌률이 매우 낮기 때문에 병행 제어 기법을 사용하지 않고도 대부분 일관성을 유지한다는 점을 이용한 방식이다. 

 

 

5. 다중 버전 병행 제어 (Multi-version Concurrency Control)

 

한 데이터에 대해 여러 버전의 값을 유지하며 관리하는 방식이다. 타임스탬프의 개념을 이용하며, 다중 버전 타임 스탬프 기법이라고도 한다. 

여러 버전의 타임스탬프를 비교하여 스케줄상 직렬 가능성이 보장되는 타임스탬프를 선택한다. 

충돌이 발생할 경우 연쇄 복귀가 발생할 수 있는 단점이 있다.

 

 

PC로 보시는 것을 권장합니다. 

피드백은 언제나 환영입니다. 댓글로 달아주세요 ^-^

 

 

반응형