트랜잭션들이 겹쳐서 실행될 때(interleaving)나타나는 현상들

정민교·2023년 6월 18일
0

DB

목록 보기
6/12
post-thumbnail

📒

저번 포스팅에서는 recoverability에 대해서 알아보았습니다.

요약해봅시다.

여러 트랜잭션이 실행될 때 나올 수 있는 schedule 중 unrecoverable schedule과 recoverable schedule이 있다고 했습니다.

unrecoverable schedule은 어떤 트랜잭션이 commit 되지 않은 트랜잭션이 write한 데이터를 읽는 schedule이라고 말했습니다.

이러한 schedule은 rollback할 때 원복하기 힘든 경우가 발생하기 때문에 DBMS에서는 이러한 unrecoverable schedule을 허용하지 말아야 한다고 했습니다.

recoverable schedule은 한 트랜잭션이 commit 되지 않은 트랜잭션이 write한 데이터를 읽는 경우 이 트랜잭션이 commit 혹은 rollback하기 전까지 commit하지 않는 schedule입니다.

이러한 recoverable schedule은 rollback할 때 cascading rollback으로 DB 상태 원복이 가능합니다.

하지만 cascading rollback은 rollback을 연쇄적으로 처리하는 것이기 때문에 이것을 처리하는데 비용이 많이 든다고 했습니다.

따라서 이러한 cascading rollback이 일어나지 않는 cascadeless schedule이라고 했습니다.

cascadeless schedule은 schedule 내 어떤 트랜잭션도 commit 되지 않은 트랜잭션이 write한 데이터는 읽지 않는 schedule입니다.

즉, 다른 트랜잭션이 write한 데이터를 읽는 것은 이 트랜잭션이 commit 혹은 rollback하여 종료된 경우에만 읽는 것입니다.

그런데 cascadeless schedule이라도 이상한 결과가 나타난다고 말했습니다.

다른 트랜잭션이 commit한 데이터(업데이트 한 데이터)가 사라지는 현상입니다.

이러한 현상까지 막는 schedule이 strict schedule입니다.

strict schedule은 schedule 내에서 어떤 트랜잭션도 commit 되지 않은 트랜잭션이 write한 데이터는 쓰지도 읽지도 않는 schedule입니다.

그래서 최종적으로 정리하자면

unrecoverable schedule은 rollback할 때 데이터 원복이 불가능할 수 있기 때문에 DBMS에서 허용하면 안되고, recoverable schedule만 허용해야 합니다.

recoverable schedule 중에서도 cascading rollback이 발생하지 않는 schedule을 cascadeless schedule이 있고, cascadeless schedule 중에 더 엄격한 schedule인 strict schedule이 존재합니다.

✔️발생할 수 있는 이상 현상들

여러 트랜잭션들이 겹쳐서 실행되는 경우(interleaving이라고 합니다) 발생할 수 있는 이상 현상들에 대해서 알아보겠습니다.

📌dirty read

새로운 예시를 들어봅시다. DB에 아래와 같은 데이터가 존재합니다.

x = 10, y = 20

트랜잭션들은 다음과 같습니다.

tx1. x에 y를 더한다

A. read(x) -> 10
B. read(y) -> 20
C. write(x) -> x=80

tx2. y를 70으로 바꾼다.

A. write(y) -> y=70

다음과 같은 schedule이 있습니다.

tx1(A) - tx2(A) - tx1(B) - tx1(C) - commit1 - abort2
x->10    y=70     y->70    x=80				  y=20

위 schedule은 unrecoverable schedule입니다.

commit 되지 않은 tx2가 write한 데이터 y의 값을 tx1이 읽고 있습니다. commit 되지 않은 데이터를 읽은 것이죠. 그리고 tx2는 rollback을 진행했습니다.

tx2가 write한 y=70은 이제 유효하지 않은 값입니다. 이전 포스팅에서 이 유효하지 않은 값을 tx1이 읽었기 때문에 tx1도 rollback을 진행해주어야 한다고 말했습니다.

하지만 이미 tx1은 commit을 진행했기 때문에 rollback을 해줄 수가 없습니다. 그래서 unrecoverable schedule이라고 불렀죠.

자 어찌되었든 tx1이 유효하지 않은 값을 읽은 현상이 나타났습니다. 이 유효하지 않은 값을 읽어서 x에 더해주었기 때문에 x가 80이라는 데이터도 이상한 값이 되어버린 것입니다.

🔸이런 유효하지 않는 값을 읽는 현상을 dirty read 현상이라고 합니다.

dirty read는 commit되지 않은 변화를 읽었기 때문에 발생하는 현상입니다.

📌nonrepeatable read

새로운 예시를 들어봅시다. DB에 아래와 같은 데이터가 존재합니다.

x = 10

다음과 같은 트랜잭션이 존재합니다.

tx1 x를 두 번 읽는다.

A. read(x) x->10
B. read(x) x->10

tx2 x에 40을 더한다.

A. read(x) x->10
B. write(x) x=50

다음과 같은 schedule이 있습니다.

tx1(A) - tx2(A) - tx2(B) - commit2 - tx1(B)
x->10    x->10    x=50               x->50

tx1을 살펴봅시다. 한 트랜잭션 내에서 같은 데이터인 x를 두 번 읽었는데 x의 값이 서로 다른 값을 가져온 겁니다.

트랜잭션의 isolation 관점에서 일어나서는 안되는 일입니다. isolation은 여러 트랜잭션이 동시에 실행이 되더라도 각각의 트랜잭션이 따로 따로 혼자서 실행되는 것처럼 동작해야 한다는 것이 isolation 속성입니다.

isolation 관점에서 tx1이 혼자서 실행되었다면 x->10 결과가 두 번 나와야 합니다.

🔸한 트랜잭션 내에서 같은 데이터의 값을 여러 번 그 값이 다르게 나오는 현상을 non-repeatable 현상이라고 하며, fuzzy-read라고도 합니다.

📌phantom read

새로운 예시를 들어봅시다. DB에 아래와 같은 데이터가 존재합니다.

t1(..., v=10)
t2(..., v=50)

다음과 같은 트랜잭션이 존재합니다.

tx1. v가 10인 tuple을 두 번 읽는다.

A. read(v=10) t1
B. read(v=10) t1

tx2. t2의 v를 10으로 바꾼다.

A. write(t2) t2.v=10

다음과 같은 schedule이 있습니다.

tx1(A) - tx2(A) - commit2 - tx1(B) - commit1
t1       t2.v=10            t1, t2

tx1 트랜잭션에서 동일한 조건의 데이터를 가진 tuple을 읽어오는 작업을 두 번 했는데 각 작업의 결과가 다릅니다.

첫 번째 작업의 결과는 t1 뿐이었는데 두 번째 작업의 결과는 t1, t2가 나왔습니다. 즉, 없던 데이터가 생긴겁니다.

isolation 관점에서 보면 또 발생하면 안되는 일이죠.

🔸한 트랜잭션 내에서 같은 데이터를 두 번 읽었는데 없던 데이터가 생기는 경우를 phantom read라고 합니다.

위 예시에서는 tuple의 데이터를 바꿔주는 작업을 진행했지만 tuple을 삽입하는 작업을 진행하는 경우에도 phantom read가 발생할 것입니다.

✔️Isolation Level

이러한 이상한 현상들이 모두 발생하지 않도록 만들 수는 있지만, 이렇게 하면 제약사항이 많아져서 단위 시간당 DB의 전체 처리량(throughput)이 낮아지게 됩니다.

쉽게 단위 시간당 트랜잭션 처리 수가 낮아진다고 생각하면 됩니다.

그러면 어떻게 하는 것이 좋을까 고민하다가 일부 이상한 현상은 허용하는 몇 가지 level을 만들어서 사용자가 필요에 따라서 적절하게 선택해서 사용할 수 있도록 하자고 결정하게 됩니다.

그래서 이러한 이상한 현상을 어디까지 허용하는가를 정한 level이 isolation level입니다.

isolation leveldirty readnon-repeatable readphantom read
read uncommitedOOO
read commitedXOO
repeatable readXXO
serializableXXX

위에 있는 isolation level일수록 허용하는 이상 현상이 많아 제약사항이 줄어들기 때문에 throughput은 증가하게 될 것입니다. 대신 이상 현상에 많이 취약하겠죠.

🔸이 세 가지 이상 현상 중 어디까지 허용하는 지에 따라서 각각의 isolation level이 구분됩니다.

그래서 개발자들은 isolation level을 통해 데이터의 일관성과 DB 전체 처리량 사이에서 trade off를 가질 수 있습니다.

✔️또 다른 이상 현상과 isolation level의 비판

지금까지 소개한 isolation level과 이상 현상들은 1992년 11월에 발표된 SQL 표준에서 정의된 내용들입니다.

하지만 이를 비판하는 논문이 1995년에 등장하게 됩니다.

여기서 비판하는 내용은 다음과 같습니다.

  1. 세 가지 이상 현상에 대한 정의가 모호하다.
    예를 통해서만 이상 현상에 대해서 보여주고 있을 뿐 이상 현상에 대해 일반화하지 않았다.
  2. 이상 현상은 세 가지 외에도 더 존재한다.
  3. 상업적인 DBMS에서 사용하는 방법을 반영해서 isolation level을 구분하지 않았다.

📌추가적인 이상한 현상

👉dirty write

새로운 예시를 들어봅시다. DB에 아래와 같은 데이터가 존재합니다.

x=0

다음과 같은 트랜잭션이 존재합니다.

tx1. x를 10으로 바꾼다.

A. write(x) x=10

tx2. x를 100으로 바꾼다.

A. write(x) x=100

다음과 같은 schedule이 있습니다.

tx1(A) - tx2(A)  - abort1
x=10     x=100     

tx2가 commit되지 않은 tx1이 write한 데이터를 또 write하고 있습니다.

비슷한 내용으로 strict schedule을 살펴본 적이 있습니다.

어쨋든 이 상황에서 tx1이 rollback을 합니다.

그래서 x의 값을 tx1 실행 이전 상태인 x=0으로 돌려놓아야 합니다.

하지만 tx2가 x=100이라는 쓰기 작업을 했는데 x=0으로 돌려놓으면 이 작업이 없어지는 셈이 되는 겁니다.

만약 abort1을 진행했지만 x=0으로 돌려놓지 않고 이 상태에서 abort2를 진행해서 tx2를 rollback하려고 한다면 tx2 실행 상태 이전인 x=10으로 돌려놓아야 합니다.

하지만 실질적으로 x=10이라는 값도 tx1이 abort한 값이기 때문에 이 값으로 rollback을 하면 안되는 것입니다.

🔸이처럼 commit 되지 않은 데이터를 write할 때 발생하는 이상 현상을 dirty write라고 합니다.

따라서 rollback시 정상적으로 데이터 원복(recovery)하는 것은 매우 중요하기 때문에 모든 DBMS는 모든 isolation level에서 이 dirty write를 허용하면 안 됩니다.

👉lost update

위 schedule에서 추가로 설명해보겠습니다.

저 schedule 중간에 tx2를 commit하고 rollback1이 발생하는 경우

tx1(A) - tx2(A) - commit2 - commit1
x=10     x=100              x=10

tx2는 commit을 했음에도 x=10으로 바뀌기 때문에 x=100을 한 업데이트가 없어지게 됩니다.

다른 예시로 들어봅시다. DB에 아래와 같은 데이터가 존재합니다.

x=50

다음과 같은 트랜잭션이 존재합니다.

tx1. x에 50을 더한다.

A. read(x) x->50
B. write(x) x=100

tx2. x에 150을 더한다.

A. read(x) x->50
B. write(x) x=200

다음과 같은 schedule이 있습니다.

tx1(A) - tx2(A) - tx2(B) - commit2 - tx1(B) - commit1
x->50    x->50    x=200              x=100

x=200으로 업데이트 하는 작업을 했음에도 해당 업데이트가 사라지고 x=100이 되었습니다.

🔸이처럼 commit한 변화가 사라지는 현상을 lost update라고 합니다.

👉read skew

DB에 아래와 같은 데이터가 존재합니다.

x=50, y=50

다음과 같은 트랜잭션이 존재합니다.

tx1. x계좌에서 y계좌로 40을 이체한다.

A. read(x) x->50
B. write(x) x=10
C. read(y) y->50
D. write(y) y=90

tx2. x와 y를 읽는다.

A. read(x) x->50
B. read(y) y->50

다음과 같은 schedule이 있습니다.

tx2(A) - tx1(A) - tx1(B) - tx1(C) - tx1(D) - commit1 - tx2(B) - commit2
x->50    x->50    x=10     y->50    y=90               y->90

지금 tx1을 commit하고 나면 DB 상태는 x계좌와 y계좌의 합은 100입니다.

이체 작업을 하고 두 계좌의 총합은 당연히 변해서는 안됩니다.

그런데 tx2에서의 작업을 살펴봅시다. x, y 계좌의 합이 140이 되었습니다. 데이터의 일관성이 깨진 것입니다.

🔸읽기 작업에서 데이터의 일관성이 깨지는 현상을 read skew(inconsistent한 데이터 읽기)라고 합니다.

non-repeatable read와 비슷한데 non-repeatable read는 같은 데이터를 읽었을 때 같은 데이터의 값이 다르게 나온 경우이고

read skew는 관련있는 서로 다른 데이터를 읽는 경우입니다.

👉write skew

DB에 아래와 같은 데이터가 존재합니다.

x=50, y=50 (x+y >= 0, 제약사항)
두 계좌의 합은 최소 0이상이어야 한다.

다음과 같은 트랜잭션이 존재합니다.

tx1. x계좌에서 80을 인출한다.

A. read(x) x->50
B. read(y) y=50
C. write(x) x=-30

tx2. y계좌에서 90을 인출한다.

A. read(x) x->50
B. read(y) y->50
C. write(y) y=-40

다음과 같은 schedule이 있습니다.

tx1(A) - tx1(B) - tx2(A) - tx2(B) - tx1(C) - tx2(C) - commit1 - commit2
x->50    y->50    x->50    y->50    x=-30    y=-40          

x계좌와 y계좌의 합이 0이상이어야 하니까 인출작업을 위해서 두 계좌의 합이 인출하려는 돈보다 많은지 확인해야 해서 둘 다 x, y 계좌를 조회합니다.

그리고 인출작업까지 진행하고 commit한 상태를 확인하니 x=-30 y=-40입니다.

x+y>=0의 제약사항이 깨져버렸습니다.

만약 이 DBMS가 serializable하게 동작하도록 잘 구현해놓았다면 commit2를 진행하기 전에 제약사항을 확인해서 제약사항을 어겼다면 abort2를 진행했을 것입니다.

그런데 지금 둘 다 commit이 이루어졌고 데이터 일관성이 깨지는 쓰기 작업이 이루어진 것입니다.

서로 다른 두 트랜잭션에서 다른 데이터에 쓰기 작업을 했는데 inconsistent한 데이터 쓰기가 발생했습니다.

🔸이렇게 쓰기 작업 데이터 일관성이 깨지는 현산을 write skew라고 합니다.

📌이상현상 개념의 확장

👉dirty read의 확장

dirty read는 commit되지 않은 변화를 읽을 때 생겨나는 현상이라고 말했습니다.

dirty read는 어떤 트랜잭션(A)이 다른 트랜잭션(B)이 write한 데이터를 commit하기 전에 읽고,

B가 rollback이 일어나는 경우 B가 wrtie한 데이터는 이제 유효하지 않은데 A가 읽어서 사용하는 문제라고 정의했습니다.

그런데 이러한 dirty read는 rollback이 일어나야지만 일어나는 현상이 아닙니다.

DB에 아래와 같은 데이터가 존재합니다.

x=50, y=50

다음과 같은 트랜잭션이 존재합니다.

tx1. x계좌에서 y계좌로 40을 이체한다.

A. read(x) x->50
B. write(x) x=10
C. read(y) y->50
D. write(y) y=90

tx2. x와 y를 읽는다.

A. read(x) x->50
B. read(y) y->50

다음과 같은 schedule이 있습니다.

tx1(A) - tx1(B) - tx2(A) - tx2(B) - commit2 - tx1(C) - tx1(D) - commit1 
x->50    x=10     x->10    y->50              y->50    y=90

최종적으로 x=10, y=90이 되었습니다.

그런데 여기서 봤을 때 tx2는 x->10, y->50으로 데이터를 읽었습니다. 데이터의 불일치가 발생했습니다.

즉, tx2는 tx1이 write한 x데이터를 tx1이 커밋하기 전에 읽었습니다. 그리고 tx1은 rollback하지도 않았죠.

이렇게 dirty read는 commit되지 않은 데이터를 읽고 그 데이터가 rollback이 되는 경우만 발생하는 것처럼 이야기 하였지만,

실제로 트랜잭션이 abort가 발생하지 않아도 dirty read가 발생할 수 있다고 비판하고 있습니다.

👉phantom read 확장

phantom read는 같은 조건의 데이터를 검색했을 때 여러 번 검색했을 때, 없던 데이터가 생기는 현상이라고 말했습니다.

하지만 여기서는 꼭 같은 조건의 데이터를 검색하는 경우가 아니더라도 phantom read가 발생할 수 있다고 비판하고 있습니다.

t2(..., v=7)
cnt=0 -> v가 10 이상인 tuple의 개수를 의미하는 데이터

다음과 같은 트랜잭션이 존재합니다.

tx1. v가 10인 tuple을 읽고, cnt를 읽는다.

A. read(v>10) nothing
B. read(cnt) 0

tx2. v=15인 t2를 추가하고 cnt를 1 증가시킨다.

A. write(insert t2) t2.v=15
B. read(cnt) 0
C. write(cnt=1) 1

다음과 같은 schedule이 있습니다.

tx1(A) - tx2(A) - tx2(B) - tx2(C) - commit2 - tx1(B) - commit1
x        t2.v=15  cnt->0   cnt=1              cnt->1

지금 tx1(A)는 v>10인 데이터를 읽는 작업이었는데 이 때는 데이터가 하나도 나오지 않았습니다.

하지만 tx1(B)는 v>10인 데이터의 갯수를 읽는 작업인데 이 때는 또 1이라는 데이터가 나왔습니다.

데이터 일관성이 깨지는 일이 발생하는 것입니다.

이처럼 꼭 같은 조건으로 데이터를 여러 번 읽는 트랜잭션이 아니더라도 phantom read가 발생할 수 있습니다.

✔️정리

지금까지 isolation이 지켜지지 않았을 때 발생할 수 있는 이상 현상들에 대해서 알아보았습니다.

또한 SQL 표준에서 다루지 않은 이상 현상들과 SQL 표준에서 다룬 이상 현상 중에서도 이상 현상의 개념을 더 넓게 봐야한다고 비판했던 내용까지 알아보았습니다.

다음 포스팅에서는 snapshot isolation에 대해서 알아보겟습니다.

profile
백엔드 개발자

0개의 댓글