<개요>
- Designing Data Intensive Applications 를 읽고 그 중 Transactions에 대한 내용을 정리.
- 이전글 https://icthuman.tistory.com/entry/Transactions-개념정리-1
Single-Object and Multi-Object Operation
Atomicity
- error 가 발생했을때 transaction은 중단이 되어야하고, 변경되었던 내용은 삭제가 되어야 한다.
- 부분적인 실패가 발생하면 안되는 것으로 이해하면 된다.
- 키워드는 all or nothing !
Isolation
- 동시에 수행되고 있는 transactions는 서로에 간섭하지 않아야 한다.
- 가장 많이 나오는 예제로 다음을 살펴본다.
- emails에는 우리가 받은 이메일이 저장되고, mailboxes에서는 읽지않은 메일의 수가 저장된다고 가정할때.
TimeLine | 1 | 2 | 3 | 4 |
User#1 Req | INSERT INTO emails VALUES(2,'Hello', true) |
UPDATE mailboxes SET unread=unread+1 WHERE recipient_id=2 |
||
User#1 Res | OK | OK | ||
User#2 Req | SELECT FROM emails WHERE recipient_id='2' |
SELECT * FROM mailboxes WHERE recipient_id='2' |
||
User#2 Res | 'Hello',true | 0 |
- emails, mailboxes 데이터간의 불일치가 발생하게 된다.
- 이러한 현상을 Dirty Read라고 하며 커밋되지 않은 데이터를 읽기 때문에 발생하는 현상이다.
(흔히 말하는 가장 낮은 isolation level 0 : UNCOMMITED_READ)
Weak Isolation Levels
Read Committed
No dirty reads
- commit된 데이터만 읽어야 한다.
No dirty writes
- commit된 데이터만 overwrite한다.
COMMIT이 수행된 데이터만 읽도록 하여 Dirty Read를 막는다.
Implementing read committed
- READ_COMMITED를 실제로 구현하는 방식은?
- 수정하려고 할때 lock을 얻는다. (row, document, table level )
- 오직 한 트랜잭션만이 lock을 보유한다. 앞 트랜잭션이 끝날때까디 대기하는 것이 원칙
- Dirty Read를 방지하기 위해서 읽을 때 lock을 얻고, 읽고난 뒤 lock을 해제하는 방식은 응답시간에 악영향을 미친다.
-> 객체들의 전/후 값을 기억하고, 트랜잭션이 진행되는 동안에 다른 트랜잭션에서 해당 객체를 읽을때에는 이전값을 제공하는 방식으로 개선할 수 있다.
Snapshot Isolation and Repeatable Read
Implementing snapshot isolation
- READ COMMITED의 방식대로 하면 Dirty Read의 문제를 해결할 수 있으나 아래와 같은 문제가 여전히 존재한다.
TimeLine | 1 | 2 | 3 | 4 |
User#1 | SELECT * FROM accounts where id=1 |
SELECT * FROM accounts where id=2 |
||
Account#1 | Account #1 = 500 | Account #1 = 600 | ||
Account#2 | Account #2 = 500 | Account #2 = 400 | Account #2 = 400 | |
Transfer | UPDATE accounts SET bal = bal+100 WHERE id=1 |
UPDATE accounts SET bal=bal-100 WHERE id=2; COMMIT; |
- User#1이 계좌를 조회했을때 각각 500, 400으로 조회가 되어 총 잔액이 900인 상태가 된다. ( 100원이 사라짐 )
- 이러한 상태를 Read Skew, Non Repeatable Read 라고 한다.
- 다시 재조회를 하면 문제가 없지만 다음과 같은 상황에서 주의해야한다.
A) Backup : 대부분의 작업이 오래 걸리며, 일부 백업은 이전버전 데이터, 일부 백업은 최신버전 데이터를 가져가게 되면 불일치가 영구적으로 발생하게 된다.
B) Analytics : 전체적인 통계작업등의 정합성이 맞지 않는 상황이 발생하게 된다.
이러한 현상을 해결하기 위해서 일반적으로 Snapshot Isolation이 사용된다.
- 아이디어는 해당 트랜잭션 시작시 Database에서 커밋된 모든 데이터를 보고 , 이후 다른 트랜잭션에 의해서 데이터가 변경되더라도 각 트랜잭션에서는 이전 데이터만 표시되는 형태이다.
- Read-Only Long query (백업,분석) 에서 유용하게 사용될 수 있다.
Implementing snapshot isolation
- Read Commited 처럼, Dirty Write 를 방지하기 위해서는 write lock이 필요하다.
- 하지만 읽기에서는 lock이 필요하지 않음으로 성능을 향상시킬 수 있는 포인트가 된다.
(Readers never block writers, Writers never block Readers) -> Lock경합없이 쓰기를 처리하면서 장시간 실행되는 Read Query를 처리할 수 있다.
MVCC
- Dirty Read를 방지하기 위해 사용되는 유형을 일반화 시킨 것
- 다양하게 진행되고 있는 트랜잭션들이 서로 다른 시점에서 상태를 확인하기 위해서는 결국 Object의 여러 커밋된 버전을 유지해야 한다.
- Database가 Read Comiited만 제공하고 Snapshot Isolation을 제공하지 않는 다면 단순하게 두 가지 버전으로 접근하면 된다.
the commited version / the overwritten-but-not-yet-commited version
- 일반적으로 Snapshot Isolation을 지원한다면 Read Commited에서도 MVCC를 사용한다.
- 전체 트랜잭션에 대해서 동일한 Snapshot을 사용함으로 Snapshot Isolation을 적용하게 된다.
- 이 때 내부적으로는 어느 transaction에 의해서 데이터가 created,deleted되었는지 기록하는 방식으로 접근하며
update 역시 create / delete의 형태로 관리하게 된다.
Repeatable read and naming confusion
- Snapshot Isolation의 경우 매우 유용한 Isolation Level임에도 불구하고 많은 Database에서 각자의 이름과 방식으로 구현하고 있다.
- 예를 들어서 Oracle : serializable, PostgerSQL/MySql : Repetable Read
- 그 이유는 SQL 표준이 만들어 질때에는 snapshot isolation의 개념이 포함되지도 않았으며 만들어 지지도 않은 상태였기 때문..
- 결국 이 책에서는 Isolation Level의 표준 정의자체에 결함이 있다고 이야기한다. (모호함, 부정확함)
특히 우리가 표준이라고 부르는 것들에는 (e.g RFC문서) 구현에 독립적이어야 하는데 그렇지 못하며, 각 Database들이 표면적으로는 표준화 되어있지만 실제로 guarantee 하는 부분에도 많은 차이가 있다.
실제로 과거에 어플리케이션 개발을 하면서 JDBC Driver의 각 구현체의 차이에 따라서 의도치 않은 결과나 버그를 얻은 적이 있었는데 아마도 DB의 영역만큼 각 벤더사에 특화된 부분이 있을까 싶다.
<정리>
- Database가 READ_COMMITED, SNAPSHOT ISOLATION (REPEATABLE_READ) 을 통해서
- 동시에 Write연산들이 수행되는 상황 속에서, Read only Transaction에게 어떤 방식으로 데이터를 제공하여
- 그것들을 guarantee 할 수 있는지에 대해서 정리해봤다.
- 다음 글에서는 동시에 Write연산들이 수행되면서 발생하는 문제인 Lost Updates 에 대해서 정리하도록 한다.
'Software Architecture' 카테고리의 다른 글
HTTP Cache #1(문서의 나이와 캐시 신선도) (0) | 2022.12.29 |
---|---|
Designing Data-Intensive Applications - Transaction #3 (Lost Updates, dirty-writes) (1) | 2022.11.10 |
Designing Data-Intensive Applications - Transactions #1 (Basic) (0) | 2022.04.14 |
Redis를 활용한 캐시 사용시 주의점 (0) | 2022.03.22 |
Algorithm In Architecture #3 (시간복잡도, 문제해결, MVC) (0) | 2019.11.27 |