data7 MIN READ

[데이터 중심 애플리케이션 설계] 07. 트랜잭션

[데이터 중심 애플리케이션 설계] 07. 트랜잭션

데이터 중심 애플리케이션 설계 시리즈의 글입니다.

transaction = 어플리케이션에서 몇개의 읽기와 쓰기를 하나의 논리적 단위로 묶는 방법

  • commit / abort 존재
  • 어플리케이션상의 오류처리가 단순해짐
  • 안정성을 보장해줌
  • 속도는 느려짐

The Slippery Concept of a Transaction

  • 트랜잭션은 이점(지속성)과 한계(가용성)이 있음
  • trade-off를 잘 알고 써야함

The Meaning of ACID

  • ACID
    • Atomicity 원자성
    • Consistency 일관성
    • Isolation 격리성
    • Durability 지속성
  • DB마다 ACID구현이 제각각임
  • ACID는 단순한 마케팅 용어임
  • 각 용어에 기술적으로 지향하는바를 정리하자면 아래와같음

원자성

  • 여러 쓰기작업이 하나의 원자적 단위로 묶였을때, 커밋될수 없다면 모두 abort되어야함
  • 이로인해 데이터 정합성이 깨질 염려가 적어짐.
  • = 잘 abort하는 능력. abortability가 더 정확

일관성

  • 데이터는 항상 진실이어야하는 invariant(불변식)이 있음
    • ex. UK 제약
    • ex. FK 제약
  • 이 일관성을 유지하도록 transaction을 잘 정의해야함
  • 어플리케이션의 속성(책임)임.

격리성

  • 동일데이터에 동시에 접근하는 동시성문제
  • 동시에 실행되는 transaction은 서로 격리되어야함

지속성

  • transaction이 commit됐다면 결함이 발생해도 데이터는 손실되지않는다는 보장
  • 비휘발성 저장소에 기록되었다는 뜻. (복구수단을 동반)
  • 지속성 보장하려면 복제완료시까지 기다려야함
  • 완벽한 지속성은 존재하지않음

Single-Object and Multi-Object Operations

  • 원자성/격리성 - 클라가 한 transaction에서 여러번쓰기를하면 db는 어떻게동작하는가?
    • 원자성 : 오류시 abort
    • 격리성: 각 transaction은 서로 방해하면안됨
  • multi-object transaction은 어떤 읽기연산과 쓰기연산이 동일한 transaction에 속하는지 알아야함
    • RDB는 BEGIN TRANSACTION ~ COMMIT 으로 구분
    • 비RDB는 연산을 잘 묶지 않음 (⇒ 부분갱신될 여지가잇음)
  • Single object writes
    • 보통 한 노드 single object수준 원자성/격리성 제공
    • 동시에 쓰기시도가 있지 않을때 쓰기 반영
    • transaction은 아님 (보통 multi에 적용되는걸 의미)
  • The need for multi-object transactions
  • 분산db는 대부분 포기함
  • 필요하긴함.
  • 원자성/격리성 구현필요
  • Handling errors and aborts
    • 오류시 abort 라는 철학을 모든 db가 따르진않음
    • 오류복구는 애플리케이션의 책임으로..
    • abort transaction retry는 효과적이지만 완벽하지는 X
      • 재시도 중복
      • 과부하가 원인일때 재시도는 현상을 악화시킴
      • abort sideeffect
      • retry fail시 데이터손실

Weak Isolation Level

  • Concurrency Issue(race condition)
    • 트랜잭션이 다른트랜잭션에서 동시에 변경한 데이터를 읽음
    • 두 트랜잭션이 동시에 같은 데이터를 변경
  • Concurrency Bugs
    • 타이밍문제
    • 운이없을때만 촉발
    • 테스트로 발견 어려움
    • 재현이 어려움
  • Transaction isolation
    • concurrency issue를 감춤
    • serializable isolation(직렬성 격리) : 트랜잭션을 직렬로 실행
    • 구현이 어려움.. → 보통 weak isolation level 사용

동시성 문제에 은탄환은 없음. concurrency issue의 종류를 잘 이해하고 방지하는 방법을 배울 필요가 있다

Read Committed

  • 가장 기본적인 수준의 transaction isolation
  • no dirty reads
    • dirty read = 커밋되지 않은 데이터를 읽는것
    • 일부만 갱신된 데이터를 읽는 혼란을 막을수있음
    • 나중에 롤백될수도있는 데이터를 보이는 혼란을 막을 수 있음
  • no dirtye writes
    • dirty writes : 커밋되지 않은 변경을 이후 쓰기작업에서 덮어쓰는것
    • 먼저 실행된 transaction이 commit / abort 되기 전까지 두전빼 쓰기를 지연시킴
  • Implementing
    • read commited = 매우 널리 쓰이는 격리수준
    • 보통 row수준 lock을 사용해 dirty writes 방지
    • 그러나 잠금대기때문에 응답시간이 느려짐

Snapshot Isolation and Repeatable Read

  • nonrepeatable read : 같은 row를 다시읽을때 값이 바뀌는현상
  • read skew : 관련된 여러 행 사이에 불일치 발생 (이체계좌 / 입금계좌 금액 불일치)
  • 보통은 잇을수 있는 일로 ㅇㅋ해줌.
  • 감내할수없는경우도 있음.
    • backups
    • analytic queries and integrity check
  • Implementing
    • dirty write를 막기 위해 write locks 사용
    • read할때는 lock 없음
    • readers never block writers, and writers never block readers
    • lock 경쟁 없이 일상적으로 write가능
    • 일관성잇는 snapshot에 대해 long query 수행 가능
    • MVCC(multi-versin concurrency control) : 객체마다 커밋된 버전 여러개를 유지
      • txid
      • created_by
      • deleted_by
  • Visibility rules for observing a consistent snapshot
    • txid로 어떤 스냅샷을 볼지 결정
    • 이미 커밋된 트랜잭션만 읽음
    • 대상객체가 삭제로 표시되지 않음 (or 삭제 트랜잭션이 있어도 아직 커밋되지않음) 만족시 읽음
  • Indexes and snapshot isolation
    • 심플하게는.. index가 객체의 모든 버전을 가리키게 한 뒤, query 가 현재 transaction에서 볼수 없는 버전을 걸러냄
    • 성능을 올리고싶다면?
      • 동일객체의 다른버전을 같은 page에 저장되게함 → index갱신 회피
      • append-only B-tree : 특정 root = 해당 시점의 snapshot

Preventing Lost Updates

  • lost update (갱신손실) : 쓰기작업이 동시에 일어나 두번째 쓰기 작업이 첫번째 변경을 포함하지 않고 변경이 수행됨
  • atomic write operations (원자적 쓰기 연산)
    • 한 operation에서 read-modify-write 한번에 순차적으로 실행되도록 강제함.
    • concurrency-safe
    • 객체를 읽을때 해당 객체에 대한 exclusive lock(독점적인 잠금) 획득
  • Explicit locking (명시적 잠금)
    • application에서 갱신할 객체를 명시적으로 lock
    • race condition 유발하기 쉬움
  • Automatically detecting lost updates
    • read-modify-write 병렬실행을 허용. 대신 lost updates가 탐지되면 abort후 retry.
    • snapshot isolation과 결합하면 효율적으로 수행됨
  • Compare-and-set
    • 마지막으로 읽은 후로 변경되지않았을대만 갱신을 허용
    • 현재값 ≠ 이전에 읽은값이면 갱신반영 X, retry
  • Conflict resolution and replication
    • 여러 replica에 대해서도 lost updates를 막아야함..
    • replica에는 lock, compare-and-set 적용 불가능.
    • sibling 생성한 뒤 사후에 병합.
    • LWW(last write wins) : 갱신손실 발생하기 쉬움..

Write skew and Phantoms

  • write skew: 두개 이상 트랜잭션이 같은 조건을 검사하고 서로 다른 객체를 수정할때 각 트랜잭션 안에서는 유효하지만 전체 시스템 상에서는 제약조건을 위반하게 되는 상황.
  • 해결하려면 결국 serializable isolation(직렬성격리)가 필요. 차선으로는 row lock.
  • phantom : 어떤 트랜잭션에서 실행한 쓰기가 다른 트랜잭션의 검색 질의 결과를 바꾸는 효과
  • 보통 팬텀이 까다로운 쓰기스큐를 유발하게됨
  • materializing conflict(충돌 구체화) : 충돌을 유발하는 구체적인 row를 정의하고 (select for update) 이를 위반하면 잠금 충돌이 일어나도록함.

Serializability

  • 충돌의 명시적 해답은 결국, 직렬성 격리(serializable isolation)
  • 동시성없이 한번에 하나씩 직렬로 실행되도록 보장
  • 모든 경쟁조건을 막아줌

Actual Serial Execution

  • 램값이 싸져서 단일스레드에 트랜잭션 실행이 가능해짐.
  • cpu코어 하나로 처리해야하므로 트랜잭션이 구조화되어야함.
  • Encapsulating transactions in stored procedure
    • 상호작용식 트랜잭션은 네트워크통신에 많은 시간을 소비
    • 트랜잭션 코드 전체를 stored procedure형태로 db에 미리 제출
    • 필요 데이터를 메모리에 모두 올리고 작업 수행
    • deterministic해야함
  • partitioning
    • 트랜잭션 처리량이 단일cpu코어속도로 제한됨..
    • 여러 코어/노드로 확장하기위해 data를 partioning해 사용
    • coordinating이 필요할가능성이 높음..복잡해짐

Two-Phase Locking (2PL)

  • lock requirements가 훨씬 강함
  • 쓰기 트랜잭션은 다른 쓰기/읽기 트랜잭션을 막음.
  • implementation
    • shared mode / exclusive mode 존재
    • 읽기를 원한다면 공유모드 잠금을 획득
    • 읽다가 쓰려면 shared → exclusive로 upgrade
    • 쓰기를 원한다면 독점모드 잠금을 획득
    • 트랜잭션이 잠금 획득 후에는 종료될때까지 잠금을 유지해야함
    • 1단계 : 트랜잭션 실행하며 잠금 획득
    • 2단계: 트랜잭션 종료하며 잠금 해제
    • deadlock발생시? db가 감지하고 하나를 강제로 abort
  • performance
    • 성능이 구림..
    • 처리량 / 응답시간 매우 bad
  • predicate locks
    • 팬텀을 막기위해 조건으로 lock을 거는거
    • 2PL이 predicate lock을 포함하면 → serializable isolation
  • index-range locks
    • predicate lock이 잘 동작하기 어려움.. (확인에 시간 오래걸림)
    • 현실적으로 index-range locking 을 사용 (~= predicate lock)

Serializable Snapshot Isolation (SSI)

  • 직렬성 격리 + 좋은성능을 공존하려면? → SSI
  • pessimistic concurrency control
    • 비관적 동시성제어
    • 뭔가 잘못될거같으면 안전해질때까지 기다림.
    • 2PL, mutex, serial execution
  • optimistic concurrency control
    • 낙관적 동시성 제어
    • SSI
    • 뭔가 잘못될거같으면 괜찮아질거라는 가정하에 계속 진행
    • 일단 시도하고 잘못된거면 abort 후 retry.
    • 성능이 더 좋은 경향이 있음.
  • snapshot isolation 기반.
  • serialization conflict를 감지해 abort시킬 트랜잭션을 결정.
  • oudated premise를 감지하려면?
    • stale MVCC version을 읽었는지 감지
    • 과거의 읽기에 영향을 미치는 쓰기 감지
  • performance
    • trade-off : transaction read/write 추적 detail 정도와 속도
    • 2PL에 비해 다른 트랜잭션의 잠금을 기다릴 필요가 없음
    • 읽기전용 질의는 consistency snapshot 위에서 빠르게 수행가능
    • 단일 cpu처리량에 제한되지않음
    • abort ratio 중요함