애매모호한 트랜잭션의 개념
- ACID
- A(원자성)은 어보트(abortability)가 더 어울린다.
- C(일관성)는 데이터베이스에서 달성할 수 있는 게 아니고 애플리케이션 속성이다. C는 약어를 만들기 위해서 사용됐고 중요하게 생각되지 않았다고 한다.
- I(격리성)은 성능 저하를 초래해서 아예 구현하지 않거나 완화된 것을 사용한다.
- D(지속성)은 신뢰성을 토대로 완벽한 지속성은 불가하다.
- 트랜잭션 격리: 동시성 문제를 감추기 위한 것으로 동시성이 없는 것처럼 한다. 대표적으로 직렬성 격리인데 현실적으로 비용이 크다.
- 커밋 후 읽기: 읽을 때 커밋된 데이터만 읽고, 쓸 때는 커밋된 데이터만 덮어쓴다. (더티 읽기, 더티 쓰기 방지)
- 더티 읽기: 커밋이나 어보트되지 않은 데이터를 읽는 것
- 로우 수준 잠금: 해당 객체에 대한 잠금을 획득해야 수정이 가능하고 완료되기까지 보유하고 있어야 한다.
- 더티 읽기도 비슷한 방식으로 막을 수 있지만, 쓰기를 하는 동안 모든 읽기가 막히면 성능에 악영향을 준다. 따라서 대부분은 과거의 값과 현재 쓰고 있는 값을 둘 다 가지고 있다가 커밋 시점에 따라 읽을 수 있게 한다.
- 스냅숏 격리와 반복 읽기
- 계좌 금액 이동 같은 경우 양쪽 계좌의 커밋 후 읽기에선 커밋 시간 차로 인해 잘못이 발생할 수 있음
- 더티 읽기 방법을 일반화한 것으로 객체마다 여러 개의 커밋된 버전을 갖게 한다. (다중 버전 동시성 제어 MVCC) 읽을 때 독립된 스냅숏을 읽게 한다.
- 쓸 때는 쓰기 잠금을 사용하고 읽기에는 잠금을 사용하지 않는다. 스냅숏 격리는 오래 걸리는 읽기만 하는 경우에 유용하다.
- 트랜잭션마다 id를 가지고 있다.
- 규칙
- 진행 중인 트랜잭션은 무시한다. (나중에 커밋돼도)
- 어보트된 트랜잭션이 쓴 데이터는 무시한다.
- 트랜잭션 ID가 더 큰 경우에는 무시한다.
- 갱신 손실 방지
- 갱신 손실: 동시에 쓸 때 일부 쓰기가 무시됨
- 원자적 쓰기 연산: value = value+1 같은 형식으로 표현
- 명시적인 잠금: 첫번째 read-modify-write 주기과 완료될 때까지 대기
- compare-and-set: 마지막으로 읽었는데 이전에 읽었을 때와 같으면 갱신 허용
- 쓰기 스큐와 팬텀
- 쓰기 스큐: 두 트랜잭션의 두 개의 다른 객체를 갱신하는데 동시에 발생하여 경쟁 조건 발생 (하나씩 했으면 한 개만 성공인데, 동시에 하여 두 개가 성공돼서 이상 동작), 갱신 손실의 일반화로 생각할 수 있음
- 팬텀: 어떤 트랜잭션이 실행한 쓰기가 다른 트랜잭션의 검색 질의 결과를 바꾸는 효과를 팬텀이라 한다.
- 스냅숏 격리는 읽기 전용 질의에서는 팬텀을 회피하지만, 읽기 쓰기 트랜잭션에서는 팬텀이 쓰기 스큐를 유발할 수 있다. (조건이 되는 row를 반환하지 않을 때)
- 충돌 구체화(materializing conflict): 인위적으로 데이터베이스 잠금 객체를 추가 (시간표를 블록으로 만듦), 방법을 알아내기 어렵고 오류가 발생하기 쉬워 최후의 수단
- 직렬성 격리: 여러 트랜잭션을 동시성 없이 하나씩 실행하게 한다.
- 실제적인 직렬 실행: 오랜 기간 동안 성능을 이유로 사용하지 않았다가 메모리가 늘어나고, OLTP에선 짧고 읽기와 쓰기가 적어서 가능하다고 최근에 결론을 내림 → 어쨌든 cpu 코어 하나의 성능으로 제한됨
- 상호작용식 트랜잭션에선 대기 시간이 필요하므로 단일 스레드에서 성능에 안 좋은 영향이 있어 저장 프로시저 형태로 데이터베이스에 미리 제출한다.
- 저장 프로시저는 db별로 각자의 언어가 있고 코드 관리 및 성능 문제가 있었으나 최근에는 상용 애플리케이션 코드로 사용할 수 있게 변화 중
- 파티셔닝을 활용해 파티션 별로 트랜잭션을 처리할 수 있으나, 여러 파티션에 접근 하는 경우가 있을 수 있고 이럴 경우 성능에 악영향을 미침
- 상호작용식 트랜잭션에선 대기 시간이 필요하므로 단일 스레드에서 성능에 안 좋은 영향이 있어 저장 프로시저 형태로 데이터베이스에 미리 제출한다.
- 2단계 잠금: 쓰기를 실행하는 트랜잭션이 없는 개체는 여러 트랜잭션에서 동시에 읽을 수 있으나 누군가 쓰려고 하면 독점적인 접근이 필요하다. (읽을 수도 없음)
- 공유모드 잠금: 읽을 때 사용하는 잠금
- 독점모드 잠금: 쓸 때 사용하는 잠금
- 교착 상태가 발생 할 수 있어 자동으로 감지하여 어보트시켜야 한다.
- 동시성이 줄어들어 성능이 안 좋아 쓰지 않기 시작함
- 서술 잠금(predicate lock): 특정 객체가 아니고 검색 조건에 부합하는 모든 객체를 잠그는 방식으로 팬텀을 막기 위해 필요
- 조건에 부합하는 잠금을 확인하는 데 시간이 오래 걸려서 서술 잠금은 근사한 색인 범위 잠금(index-range locking)을 사용한다.
- 특정 시간대의 모든 회의실을 잠그거나 모든 시간대의 특정 회의실을 잠금
- 조건에 부합하는 잠금을 확인하는 데 시간이 오래 걸려서 서술 잠금은 근사한 색인 범위 잠금(index-range locking)을 사용한다.
- 직렬성 스냅숏 격리(SSI) 같은 낙관적 동시성 제어(optimistic concurrency control)
- 2단계 잠금은 비관적 동시제어로 잘못된 가능성이 있으면 기다리는 게 낫다는 방식
- 직렬성 스냅숏 격리는 낙관적 동시제어로 트랜잭션이 커밋할 때 데이터베이스의 격리가 위반됐는 지 확인하고 그렇다면 어보트한다. (일단 실행하고 마지막에 확인하는 방식)
- 경쟁이 심할 때는 어보트가 많아져 성능이 떨어지나 경쟁이 적을 때는 성능이 좋다.
- SSI는 일관된 스냅숏을 보게 하여 스냅숏 격리 위에서 직렬성 충돌을 감지하여 어보트 시키는 알고리즘을 추가함
- 스냅숏 격리에서는 쓰기 스큐의 문제로 질의 결과가 뒤쳐졌을 수 있다는 것을 확인해야 한다.
- 오래된(stale) MVCC 읽기 감지 (읽기 전에 커밋되지 않은 쓰기가 발생했음)
- 트랜잭션이 커밋을 하려 할 때 무시된 쓰기 중에 커밋된 게 있는 지 확인
- 커밋이 기준인 이유는 쓰기 여부를 확인하기 힘들고 불필요한 어보트를 피하기 위함
- 과거의 읽기에 영향을 미치는 쓰기 감지하기 (읽은 후에 쓰기가 실행됨)
- 임시로 각 트랜잭션들이 특정 데이터를 읽었다는 정보를 저장한다.
- 트랜잭션이 데이터를 쓸 때 영향받는 데이터를 최근에 읽은 트랜잭션이 있는 지 확인한다. 트랜잭션끼리 서로 읽은 데이터가 뒤처졌다고 알려준다.
- 먼저 커밋을 시도한 것은 성공하고 이후에 커밋을 하면 어보트한다.
- 오래된(stale) MVCC 읽기 감지 (읽기 전에 커밋되지 않은 쓰기가 발생했음)
- 실제적인 직렬 실행: 오랜 기간 동안 성능을 이유로 사용하지 않았다가 메모리가 늘어나고, OLTP에선 짧고 읽기와 쓰기가 적어서 가능하다고 최근에 결론을 내림 → 어쨌든 cpu 코어 하나의 성능으로 제한됨
'Studying > Data System Design' 카테고리의 다른 글
일관성과 합의 [데이터 중심 애플리케이션 설계 9장] (0) | 2023.08.20 |
---|---|
분산 시스템의 골칫거리 [데이터 중심 애플리케이션 설계 8장] (0) | 2023.08.19 |
파티셔닝 [데이터 중심 애플리케이션 설계 6장] (0) | 2023.07.13 |
복제 [데이터 중심 애플리케이션 설계 5장] (0) | 2023.07.09 |
부호화와 발전 [데이터 중심 애플리케이션 설계 4장] (0) | 2023.07.08 |