CS/데이터베이스 시스템

[DB System 이론] Transaction & Conflict Serializability

CSE 2025. 11. 18. 17:28

transaction은 기본적인 하나의 실행 단위이다.

DB에서는 SQL문 하나라고 생각할 수 있다.

 

트랜잭션의 4가지 속성(ACID)

아래의 예시를 생각해보자.

 

<송금 예시>

1.DB에서 A계좌 정보를 읽어온다.

2.A잔액을 50달러를 뺀다.

3.A계좌 정보를 DB에 업데이트한다.

4.DB에서 B계좌 정보를 읽어온다.

5.B잔액을 50달러 늘린다.

6.B계좌 정보를 DB에 업데이트한다.

 

atomicity

만약 3번이후, 6번 이전과정 중에 시스템이 죽었다고 생각하면 DB의 상태는 데이터 무결성이 깨진(inconsistent)상태가 되버린다.

시스템이 다시 재부팅 되더라도 A의 돈만 없어지고 B는 그대로가 된다.

따라서 이런 경우에 아예 트랜잭션 이전 상태로 되돌리던지 다 실행된 상태로 만들던지 해야한다.

다른말로 all or nothing을 지켜야한다.

이를 atomicity(원자성)라고 한다.

 

durability

만약 위의 경우 같은 일이 벌어지지 않고 모든 명령이 잘 실행되었더라도 문제는 발생할 수 있다.

DB에서 정보를 읽어 버퍼에 올려 메모리에서 연산을 수행하는데, 이를 DB에 다시 써줘야 트랜잭션이 완벽히 수행된 것이다.

근데 이때 시스템이 죽어버리면 DB에는 반영이 안되었고 트랜잭션 실행 전으로 돌아가므로 all or nothing에서 nothing이 되어 atomicity에는 위배되지 않는다.

 

하지만 문제는 메모리에서 처리가 끝나면 DB는 사용자에게 작업이 완료되었다고 알림을 보내는데,

실제로는 nothing으로 돌아갔다는 것이다.

 

그럼 버퍼에서 정보들이 내려와 DB에 작성될 때 알림을 보내면 되지 않을까? 라고 생각할 수 있지만,

버퍼에서 정보가 언제 내려올지 모르기 때문이다.

왜냐면 이 정보가 앞으로도 사용될 것을 예상해 한동안 버퍼에서 내려오지 않기 때문이다.

사용자가 이 모든 과정을 기다려 결국 버퍼에서 내려올 때 알림을 받게 된다면 매우 오랜 시간을 기다려야한다.

따라서 메모리에서 연산이 끝나면 DB에 정보를 기록하는 대신 로그를 생성해 상태를 저장한다.

 

Q. 로그를 만들어서 저장할 바에 그냥 DB에 쓰면 안되나?

1. 로그는 시간순으로 저장된다. DB는 랜덤 io이므로 로그가 성능에서 유리하다.

2. 로그는 durability를 위해서만 쓰는게 아니다. atomicity를 위해서도 필요하다.

로그만 잘 기록되면 실제로 DB에 기록되지 않았더라도 기록된 것으로 취급한다. 

 

consistency

위 트랜잭션 예시에서 A에서 50달러를 빼면 B에서 50달러가 늘어나야한다.

하지만 코딩 실수로 이게 안될 수 있다. 예를 들면 개발자가 B+=50코드를 빼먹은 것이다.

A와 B의 송금에서 두 계좌의 잔액 합은 항상 같아야하지만 그렇지 않게 되었다.

이런 상황이 inconsistent한 상황이다.

DB는 항상 consistency를 유지해야한다.

 

 

isolation 

바로 위에서 consistency를 항상 유지해야 한다고 했지만, 트랜잭션 중간에는 잠시 inconsistent한 상태가 발생할 수 있다.

위 예시에서는 A에서 50달러를 빼고 아직 B에는 더하지 않은 상태이다.

이는 임시적으로 허용을 해주는데, 여기서 한가지 문제가 또 발생할 수 있다.

저 임시 상태에서 다른 트랜잭션이 해당 정보를 사용하게 되면 문제가 발생한다.

 

따라서 isolation이라는 속성도 지킬 필요가 있다.

이는 한 트랜잭션이 실행될 때 다른 트랜잭션의 영향을 받지 않는 것처럼 실행되어야한다는 것이다.

 

isolation속성을 지키는 가장 쉬운 방법은 트랜잭션 여러개를 동시에 실행하지 않고 순서대로(sequential하게) 실행하면 된다.

하지만 코어/쓰레드 많은데 하나만 사용하는 것은 매우 아깝기 때문에 동시에 실행은 필수적이다.

따라서 concurrency control 이라는 기술이 필요하다.

 

방금까지 알아본 트랜잭션의 4가지 속성을 알파벳의 앞글자를 따서 ACID라고 부른다.


Transaction State

DB시스템에서 이 4가지 속성을 보장해주기 위해 어떻게 트랜잭션 관리를 하는지 알아보자.

Active : 트랜잭션의 코드를 실행중인 상태

 

active상태에서 코드가 잘 실행되었다고 바로 트랜잭션이 끝났다는 상태인 committed상태로 가면 안된다.

durability문제 때문이다.

따라서 partially committed라는 중간 상태 하나를 거친다.

 

Partially committed : 트랜잭션의 마지막 실행이 끝났지만 아직 durability가 해결이 되지않은 상태.

즉 이 상태에서 전기가 나가면 없던 트랜잭션이 되는 것이다.

durability를 해결하면 committed로 넘어갈 수 있다.

만약 해결 중 에러가 난다면 failed로 넘어간다.

 

committed : 트랜잭션 실행을 성공했을 때. DB에 직접 업데이트가 되었을 수도 있지만 로그만 있어도 이 상태로 취급할 수 있다.

 

Failed : 실행 중 0으로 나누기 등 여러 이유로 실행이 불가능해질 때. atomicity 보장을 위해 nothing으로 되돌려야함.

 

aborted : 위 failed상태에서 되돌리는데 성공한 상태

 


위에서 언급만 했던 concurrency control에 대해 조금 더 알아보자.

 

해결할 내용은 여러 트랜잭션이 동시에 돌아도 isolation문제가 생기지 않게끔 하는 것이다.

얼핏 생각하면 동시에 도는 트랜잭션이 어떻게 개별적으로 실행되는 것처럼 처리를 할지, 어려워 보인다.

 

두 트랜잭션이 있을 때 어떤 명령어끼리는 동시실행이 가능하고,

어떤 것들은 순서가 있게 실행이 되어야하는지를 판단해서 실행하는 방식으로 구현할 수 있다.

 

 

schedule

각 트랜잭션에 포함된 인스트럭션을 실행하는 순서를 정한 것이 schedule이다.

 

아래의 두 가지 트랜잭션을 예시로 생각하자.

T_1 : A에서 B로 50달러 송금

T_2 : A에서 B로 잔금의 10%를 송금

이 통합된 스케줄에서 각 트랜잭션 내부의 명령어 순서는 바뀌면 안된다.

위 사진의 두 가지 통합 스케줄은 모두 유효하다.

하지만 잠깐 생각해보면 두 트랜잭션의 순서가 달라지면 그 결과도 바뀔 것이라는 것을 알 수 있다.

 

서로 다른 유저가 동시에 위 두 개의 트랜잭션 버튼을 눌렀을 때를 생각해보자.

이 순서는 OS에서 딜레이, 네트워크의 딜레이 등 여러 요인으로 느려지는데, 결국에 서버에 먼저 도착하는 것이 먼저 실행된다.

 

이는 유저가 감당하는 부분이고, 이런 예시로 배그 같은 게임에서 서로 동시에 총을 쐈을 때가 있다.

만약 한명이 서버와 물리적으로 가까워서 이 사람의 데이터가 먼저 서버로 갔다면 이 사람이 먼저 상대를 맞췄다고 판단이 될 것이다.

 

 

위의 두 스케줄은 순서대로 실행한 트랜잭션이었다.

이번에는 서로 겹쳐서 실행되는 concurrent 스케줄의 경우를 알아보자.

스케줄3은 serial하게 도는 스케줄과 결과가 동등(equivalent)한데 concurrent하므로 더 좋은 스케줄이다.

그러나 스케줄4에서는 T1의 write(A)가 T2의 write(A)결과를 덮어써버리게 된다.

따라서 문제가 발생한다.


 

serializability

만약 어떤 concurrent한 스케줄이 serial 스케줄과 equivalent하다면 이는 serializable하다고 말한다.

 

위처럼 말로 간단히 설명할 수 있지만 조금 더 formal하게 정의하는 방법으로 conflict serializability라는 이론과, view serializability라는 이론이 있다.

이 각각의 이론에서 정의하는 equivalent는 의미가 다르다.

그러나 두번째 방법은 overhead가 너무 커서 사용하지 않으므로 첫번째 이론에 대해 알아보자.

 

 

conflicting instructions, conflict equivalent

 

<4가지 경우>

Case 0. 각각 다른 변수에 읽기/쓰기 (don't conflict)

case 1. 동시에 같은 변수 읽기 (don't conflict)

case 2. 같은 변수에 대해 한 명은 읽고 다른 한 명은 쓰기 (conflict)

case 3. 같은 변수에 동시에 쓰기 (conflict)

 

conflict가 발생하는 것들은 실행 순서에 따라 결과가 달라진다는 것을 의미한다.

그러나 반대로 conflict가 발생하지 않는 명령어들에 대해서는 순서를 바꿔도 된다는 것이다.

따라서 concurrent한 스케줄이 순서변환을 통해 serial한 스케줄이 된다면 기존 스케줄은 안전한 스케줄이라고 생각할 수 있다.

 

이렇게 스케줄 S에서 non-conflicting instruction들을 swap해서 바꾼 스케줄 S'에 대해,

S와 S'은 conflict equivalent하다고 한다.

 

conflict serializablility

위에서 두 이론 각각에서 정의하는 equivalent가 다르다고 했었다.

이 중 conflict serializability이론에서 얘기하는 equivalent를 conflict equivalent 라고한다.

 

어떤 스케줄 S가 serial 스케줄과 conflict equivalent하다면 S는 conflict serializable하다고 한다.

왼쪽 표에서 T1의 read(B)와 T2의 write(A)를 보면, 위의 case0(don't conflict)이므로 둘의 순서를 바꿀 수 있다.

다시한번 T1의 read(B)와 T2의 read(A)를 보면, 이번에도 같은 이유로 둘의 순서를 바꿀 수 있다.

T1의 write(B)도 같은 이유로 순서를 두 번 바꾸면 오른쪽 표와 같은 순서관계를 얻을 수 있다.

 

오른쪽 표는 serial 스케줄이 되었으므로 스케줄3은 conflict serializable한 스케줄임을 알 수 있다.

위 예시는 어떻게든 순서를 바꿔보려 해도 모두 conflict라는 것을 알 수 있다. (case2,3)

따라서 이 스케줄은 conflict serializable하지 않은 스케줄이다.