이 글은 'Fundamentals of Database Engineering'에 관한 아래 유데미 강의를 보고, 공부한 내용을 정리하였습니다.
https://www.udemy.com/course/database-engines-crash-course
트랜잭션
트랜잭션이란? : 하나의 작업 단위로 취급되는 한 개 이상의 쿼리 모음
트랜잭션 수명 : BEGIN -> COMMIT -> ROLLBACK(when crash)
트랜잭션의 본질: 데이터를 변경하고 수정하는데 사용, 읽기 전용일 수도 있음
읽기 트랜잭션: 무언가가 동시 트랜잭션 때문에 바뀌어도 신경쓸 필요 없음
트랜잭션은 항상 BEGIN 해야하고, 그렇지 않을시 DB에서 암묵적으로 BEGIN을 실행함... 항상 트랜잭션 안에 있음
원자성(Atomicity)
원자성 정의
- 트랜잭션 내의 모든 쿼리는 성공해야한다 - 하나의 쿼리가 실패하면, 즉시 롤백되어야함
어떤 DB를 선택할까와도 연결됨.
- DB에 따라 커밋하기 전에 디스크에 기록하는 경우도 있음
- 어떤 DB는 커밋은 메모리에서 빠르게 일어나고, 디스크에 플러시할 때 느림, 대신 롤백은 매우 빠름
원자성 부족으로 일관성을 잃을 수 있음 -> DB는 재시작하면서 정리를 해야함... 긴 트랜잭션에선, 롤백이 오래걸릴 수도 있음
고립성(Isolation)
여러 트랜잭션이 변경하거나 읽으려고 경합하는 상황에 고립성이 필요.
Read phenomena
1. Dirty reads: 현재 실행 중인 트랜잭션에서 발생하는 읽기 현상
- 다른 트랜잭션이 쓴 내용을 읽지만 실제로 아직 커밋되지 않은 것을 읽는 것
* Dirty : 완전히 커밋되지 않은.. flush 되지 않은 것을 의미
2. Non-repeatable reads(중복되지 않은 읽기): 트랜잭션 중에 값을 읽은 후, 동일 트랜잭션에서 다시 그 값을 읽을 때, 그 값이 변경되어 있는 경우
- DB 종류에 따라 다름 Postgres는 버전 포인터를 유지해서 동일한 상황에서 원본 버전을 읽을 수 있지만, mysql은 기본적으로 원본 버전을 읽을 수 없고, 별도의 undo 로그를 열어서 확인해야하기에 장기 실행 트랜잭션에 대해 많은 비용이 들 수 있음
3. Phantom reads: 아직 존재하지 않아서 실제로 읽을 수 없는 것들
ex) 범위 쿼리에서 새로운 행을 삽입했을 때, 다시 읽으면 유령인 새 행을 읽음
- 범위쿼리에서 발생하게 됨
4. Lost updates: 커밋하기 전에 내가 쓴 것을 읽으려고 함.
- 쓰자마자 읽었는데, 다른 트랜잭션이 내가 쓴걸 바꿨을 수도 있음
ex) 동시에 트랜잭션이 발생해서 10인 값을 +5 +10했다면, 25가 되는게 아닌 먼저 커밋되는 쪽으로 write가 되어서 15가 될 수 있음
ㄴ 행 레벨 잠금으로 해결가능
Isolation Levels - 트랜잭션의 고립 수준
1. Read uncommitted
- SQL Server를 제외한 나머지 DB는 지원하지 않음.
- No Isolation의 의미로 내가 읽는 모든 것은 다른 이들이 변경하는 것들도 모두 보이게 됨
- dirty read 를 포함한 모든 읽기 현상이 발생할 수 있음. 대신 빠름.. 아무것도 유지할 필요 없음
2. Read committed
- 가장 인기있는 고립 수준으로 많은 DB가 엔진을 커밋된 읽기 워크로드에 최적화하고 있음.
- 트랜잭션 내의 각 쿼리는 트랜잭션에 의해 커밋된 변경사항만을 볼 수 있음. 다른 트랜잭션이 변경을 수행하는 동안, 해당 트랜잭션이 커밋되지 않는 이상 변경을 볼 수 없음.
- 장기 실행 트랜잭션이 있다면 다 실행되고 나서야 확인할 수 있음.
ㄴ 읽기 현상 중 Dirty read만 방지 가능
3. Repeatable Read
- 반복되지 않는 읽기를 해결하기 위해 고안된 고립수준... 읽기를 반복 가능하게 만듬
- 동일한 트랜잭션 상에서 읽고 다시 읽어도 값이 변경되지 않음
- 트랜잭션은 쿼리가 행을 읽을 때, 해당 행이 변경되지 않도록 보장됨
ㄴ 읽기 현상 중 유령 읽기 빼고는 방지 가능... 단, postgres는 RR에서도 유령 읽기를 막아줌 MVCC 덕분
4. Snapshot
- 각 쿼리는 트랜잭션의 시작 시점까지 커밋된 변경 사항만을 볼 수 있음
- 모든 읽기 현상을 제거하는 것을 보장함
- Postgres의 반복가능한 읽기는 사실 스냅샷 고립임(모든 것이 버전을 관리함... 트랜잭션을 시작하면 어떤 버전에 있는지 타임 스탬프로 표시)
5. Serializable(직렬화)
- 기본적으로 가장 느린 것으로 거의 물리적으로 각 트랜잭션이 데이터베이스에 연이어 직렬화 된 것처럼 구현
- 아예 동시성 자체를 없애버림
- DB가 트랜잭션 순서를 정하고, 거의 동일한 결과를 얻을 수 있도록 노력하는 것
- 의존성을 가지는(동시에 같은 행을 수정하는 등) 2개의 고립수준이 직렬화인 트랜잭션이 있다면, 하나는 실패할 준비를 해야함. 실패하고 다시 재시도하는 방식. 직렬화를 안쓰려면 비관적으로 잠그는 방법으로 해결 가능
데이터베이스의 고립성 구현
* 각 DBMS는 고립 수준을 서로 다르게 구현함
- Pessimistic(비관적 접근 방식): 비관적 동시성 제어로 잠금(행 수준, 테이블, 페이지 잠금)을 활용하는 것.. 잠금 하는 동안 다른 트랜잭션은 pending 상태가 됨
- Optimistic(낙관적 접근 방식): 잠금을 쓰지 않아 대기 중인 트랜잭션이 없음. 비용이 많이 드는 잠금 관리를 안해도됨. 실제로 잠금을 하지 않고, 상황에 맞게 하다가 실제로 충돌이 나면, 그때는 실패시키는 것(이때는 그냥 다시 시도시킴, nosql에서 선호하는 방식)
- 직렬화는 일반적으로 적극적 동시성 제어로 구현됨. 실제로 직렬화를 하면 DB가 너무 느려지기 때문
일관성(Consistency)
1. 데이터 자체의 일관성
- 현재 유지되고 있는 데이터의 상태를 나타냄. 실제 디스크에 있는 것과 데이터 모델이 일치하는지
- DBA와 같은 DB 설계 사용자에 의해 정의됨
- 참조 무결성과 외래키를 강제하는 것이 중요
- 원자성과 고립성이 깨지면, 일관성이 깨짐
2. 읽기의 일관성
- 읽기는 여러 인스턴스가 동기화되지 않아 일관성을 잃을 수 잇음, 시스템 전체에 적용
- DB에 커밋을 실행한 후 즉시 읽기를 실행해서 변경사항이 보이지 않는다면, 실제로 일관성이 없는 것
궁극의 일관성(Eventual Consistency)
- ex) 읽기 레플리카가 있을 때, 변경되기전에 레플리카를 읽는 경우 or Redis와 같은 캐시가 있는 경우
ㄴ 대부분의 데이터베이스는 불일치로부터 최종 일관성을 통해 회복됨... 복제 프로세스 후에는 일관성 유지하며, 동기 복제로 강제할 수 있음
- 하나의 데이터베이스에서는 일관되지만, 두 개이상의 데이터베이스가 하나의 시스템으로 묶여있을 때, 사용자 입장에서는 전체 시스템이 일관되지 않음.
- 궁극적 일관성은 시간이 지나면 최종적으로 DB 간 동기화가 되면서 일관성을 유지 한다는 것... 마케팅 용어에 불과할 수 있지만, SW엔지니어 입장에서는 그것이 용인될 수 있는지 결정해야함.
지속성(Durability)
- 고객이 DB에 만든 권한을 비휘발성 시스템 저장소에 지속시키는 과정
- 전력 소실이나, 트랜잭션을 완료한 후 클라이언트 충돌이 발생하더라도 돌아와서 변경 사항을 볼 수 있는 것
- 커밋된 트랜잭션에 의해 변경된 것들은 비휘발성 저장소에 저장되어야 한다.
지속성 기술
1. WAL(write ahead log) : 디스크에 쓰는건 느린 작업임. 그래서 쓰기 전용 log인 WAL에 즉시 먼저 기록된 후, 이것을 즉시 디스크에 플러시하여 지속성 보장
- 변경사항의 버전을 압축하여 WAL(write-ahead-log segments)로 만들어 로그 세그먼트만을 기록하여 지속시킴(이 값을 이렇게 변경했다~ 에 대한 정보를 압축한 것)
OS 캐시
- OS에 쓸 때 DB가 디스크에 쓰도록 OS에 요청하면 디스크에 쓰지 않고 자신의 메모리 캐시에 씀
- 대부분 애플리케이션이 많은 쓰기를 수행하고 한번에 디스크로 flush하려고 하는데, 성능상 이유로 이런 쓰기를 일괄 처리하는 것(I/O가 줄어들기 때문)
- 다만 이 과정에서 DB가 커밋해서 WAL이 디스크에 쓰인줄 알았지만, 실제로는 OS 단에서 캐시(RAM)에만 기록하고 디스크에 쓰였다고 알려줄 수 있음. 이때 crash가 일어나면 데이터를 잃어버림
- 이걸 방지하기 위해 DB에서 OS에 강제로 캐시없이 디스크에 flush하도록 명령할 수 있음(Fsync 명령), 대신 느림
2. 비동기 스냅샷 or 동기화 스냅샷 : 우리가 쓰는 동안에 모든 것을 메모리에 유지함, 백그라운드에서 비동기적으로 한꺼번에 모든 것을 디스크에 스냅샷함.
3. AOF : 변경사항을 추적하고 이러한 사항을 기록함. 데이터를 매우 가벼운 방식으로 매우 빠르게 저장하는 것을 보장, 충돌이 발생해도 항상 다시 읽고 테이블의 상태를 완전히 복원할 수 있으며, 모든 것을 저장할 필요 없이 변경 사항만 저장. 최종적으로 이것을 기록
Redis가 비동기 스냅샷과 WAL을 이용함