본문 바로가기
기술스택을 쌓아보자/데이터 엔지니어링

데이터 중심 애플리케이션 설계 - 트랜잭션

by 소리331 2023. 4. 26.
반응형

트랜잭션

  • 트랜잭션은 수십년동안 여러 내결함성 결여로 인해 발생되는 문제를 해결하는 메커니즘으로 채택되어 왔다. 데이터베이스에 접속하는 애플리케이션에서 프로그래밍모델을 단순화 하려는 목적으로 만든 것이다.
  • 안전성보장: safety guarantee: db에서 트랜잭션 사용을 통해 어플의 잠재적 오류와 동시성 문제를 무시할 수 있다.
  • 항상 트랜잭션이 필요한 것은 아니다.‘
  • 이번장의 중요 질문: 트랜잭션이 필요한지 아닌지 어떻게 알 수 있을까?

애매모호한 트랜잭션의 개념

  • 관계형 데이터베이스는 거의 모두 트랜잭션을 채택하는 경우가 많고, 비관계형 베이스는 채택하는 경우도, 아닌 경우도 있다. ⇒ 이 과정에서 트랜잭션의 의미가 약화되었다.

ACID의 의미

  • 원자성(atomicity), 일관성 (consistency), 격리성(isolation), 지속성(durability)
  • 현실에서는 acid의 구현 의미가 제각각이다. , 거의 마케팅 용어가 되어 버렸다.
  • BASE: ACID 표준을 따르지 않는 시스템
    • Basically available, soft state, eventual consistency( 가용성을 제공하고, 유연하며, 최종적 일관성을 지님),

원자성 atomicity

  • 원자적 연산의 의미: 클라이언트가 쓰기 작업 몇개를 실행하려고 하는데, 그중 일부만 처리된 후결함이 생기면 무슨일이 생기는지를 설명한다. 완료되지 않는다면 여태까지 쓰인 연산을 abort 해야한다.
  • 오류가 생겼을 때 롤백하고 기록한 모든 내용을 취소하는 능력.
  • 나현공유: 속성을 가지고 있으면서 속성을 띄는 가장 작은 단위, tr은 하나의 tr 보다 작게 쪼갤 수 없기 때문에 원자성( 커밋되던가 어보트되던가)

일관성 consistency

  • 일관성을 뜻하는 여러단어들이 있다. ( 최종적 일관성, 복제일관성, 일관성 해싱, 선형성)
  • Invariant: Acid 맥락에서는 데이터베이스가 좋은 상태에 있어야 한다는 것의 어플에 특화된 개념을 말한다. 데이터에 관한 어떤 선언이 있다는 것.
  • TR이 올바르게 작성되도록 하는 것은 어플리케이션의 책임이다. ⇒ c는 db 보다는 어플의 성격

격리성 isolation

  • 여러 클라이언트들이 동시에 동일한 레코드에 접근하면 경쟁조건에 맞닥뜨리게 된다.
  • 격리성은 동시에 실행되는 트랜잭션은 서로 격리된다는 것을 의미한다. (직렬성)
  • 여러 트랜잭션이 동시에 실행되었더라도 트랜젝션이 커미소디었을 때의 결과각 순차적으로 실행되었을 때의 결과와 동일하도록 보장한다.
  • 직렬성 격리는 성능 손실이 있으므로 보통 이보다 약한 스냅숏격리를 한다.
  • 트랜잭션들은 서로를 방해하지 말아야한다.

지속성 durability

  • TR이 성공적으로 커밋되었다면 하드웨어 결함이 발생하거나 db가 죽더라도 기록된 모든 TR은 손실되지 않는다는 보장이다.

단일 객체 연산과 다중 객체 연산

  • 원자성과 격리성은 클라이언트가 한 트랜잭션 내에서 여러번 쓰기를 하면 db가 어떻게 해야하는지를 설명한다.
  • 다중 객체 트랜잭션: 데이터의 여러 조각이 동기화된 상태로 유지돼야 할 때 필요하다. 한번에 여러 로우를 변경할 수 있다고 가정한다.
  • 다중 객체 트랜잭션은 읽기연선과 쓰기연산이 동일한 트랜잭션에 속하는지 알아낼 수단이 있어야 한다. Tcp 연결을 기반으로 한다.
  • 그런데, 비관계형은 앞서 말한 것고 ㅏ같은 연산을 묶는 방법이 없는 경우가 많다. 다중 객체 api가 있어도 이게 반드시 tr 시맨틱을 뜻하지는 않는다. 부분적으로만 갱신될 수 있다.

단일 객체 쓰기

  • 저장소 ㅔㅇㄴ진들은 거의 보편적으로 한 노드에 존재하는 단일 객체 수준에서 원자성과 격리성을 제공하는 것을 목표로 한다.
  • 원자성은 자앵복구용로그를 써서 구현할 수 잇고, 격리성은 각 객체에 잠금을 통해 구현할 수 있다(flock처럼)

다중 객체 트랜잭션의 필요성

  • 보통 다중 객체 트랜잭션을 구현하지 않는 경우가 있다. 왜냐하면 파티션 등 여러 시나리오에서 방해가 되기 때문이다.
  • 단일객체 만으로도 충분한 사용사례가 ㅣㅇㅆ다. 하지만 많은 경우에는 코디네이션 되어야 한다.

오류와 어보트 처리

  • 트랜잭션의 핵심기능은 오류가 생기면 기존 작업이 초기화 되어 안전하게 재시도 할 수 잇다는 것이다. 그런데 모든 시스템이 이철학을 따르지 않는다.
  • 가령 리더없는 복제는 best effort 원칙을 기반으로 더 많은 일을 하는 것을 우선으로 둔다.
  • 오류를 해결하는 것은 어플리케이션의 역할이다.

완화된 격리 수준

  • 동시성버그는 운이 없을 때만 촉발되기 때문에 테스트로 발견하기 어렵다. 때문에 트랜잭션 격리를 제공하므로서 동시성 문제를 감추려고 했다. (직렬성 격리를 통해)
  • 그런데 현실에서 격리는 간단하지 않다. 모든 이슈로부터 보호할 수 없다.
  • 맹목적으로 도구에 의존하기 보다는 방지하는 방법을 배울 필요가 잇다.
  • 비직렬성 격리수준을 몇가지 살펴본다.

커밋 후 읽기

  • 이 수준에서는 두가지가 보장된다.
  • 더티읽기와 쓰기가 없음: 커밋된 데이터만 보고 쓰이게 된다.

더티 읽기 방지

  • 더티읽기: 커밋되지 않은 트랜잭션을 보게 되는 것
  • 더티읽기를 막아야 한다. (사용자마다 보는 것의 일관성을 보장 할 수 없음)

더티 쓰기를 방지

  • 커밋되지 않은 트랜잭션들이 겹쳐 덮어씌워지게 되는 것.

커밋 후 읽기 구현

  • 로우 수준 잠금을 사용해 더티 쓰기를 방지한다.
  • 커밋되거나 어보트 될때까지 잠금을 보유하고 잇어야 한다.
  • 동일한 잠금을 써서 객체를 읽기 원하는 드랝개션이 잠시 잠금을 획ㄷ그한 후 읽기가 끝난 후 바로 해제하게 하는 것이다. ⇒ 잘 작동하기 어려움
  • 때문에 로우 수준으로 잠금을 진행하는 것이다.

스냅숏 격리와 반복 읽기 (263~)

  • ㅋㅓ밋 후 읽기 구현도 여전히 동시성 버그가 생길 수 있다.
  • 비반복 읽기, 읽기 스큐: Nonrepeatable read, read skew, ⇒ 앨리스 잔고 사례
  • 스냅숏격리: 각 트랜잭션은 데이터 베이스의 일관된 스냅숏으로 부터 읽는다. Tr이 시작될 때, db에 커밋된 모든 데이터를 본다는 걳이다.
  • 읽기만 실행하는 질의에 요긴하다.

스냅숏 격리 구현

  • 더티 쓰기, 읽기를 방지하기 위해 쓰기 잠금을 사용한다. ⇒ 한번에 하나씩만 쓰인다는 것.
  • 읽는 쪽과 쓰는 쪽이 서로를 결코 차단하지 않는다는 것.
  • 다중 버전 동시성 제어: db가 객체의 여러버전을 함께 유지하는 것.
  • 전체 트랜잭션에 대해 동일한 스냅숏을 사용한다.

일관된 스냅숏을 보는 가시성 규칙

  • 아래 두 조건이 참이면 객체를 볼 수 있다.
    • 읽기를 실행하는 트랜잭션이 시작한 시점에 ㅇ릭기 대상 객체를 생성한 트랜잭션이 이미 커밋된 상태였다.
    • 읽기 대상 객체가 삭제된 것으로 표시되지 않았다. 또는 삭제된 것으로 표시했지만 읽기를 실행한 트랜잭션이 시작한 시점에 삭제 요청 트랜잭션이 아직 커밋되지 않았다.

색인과 스냅숏 격리

  • 다중 데이터베이스에스 색인이 동작하는 방법
    • 색인이 객체의 보든 버전을 가리키게 하고, 볼수 없는 버전을 걸러내는 것

반복 읽기와 혼란스러운 이름

  • 스냅숏격리는 오라클에서는 직렬성 마이sql에서는 반복읽기 라고 한다.
  • Sql 표준에 스냅숏 격리의 개념이 없기 때문이다.
  • /??? 반복읽기 아는 사람 아무도 없다.

갱신 손실 방지

  • Tr이동시에 시랭되면 발새앟ㄹ 수 있는 충돌
    • 갱신손실(lost update) : 데이터가 Clobber;

원자적 쓰기 연산

  • Cursor stability: 원자적 연산은 보통 독점적인 잠금을 획득해서 구현한다.

명시적인 잠금

  • 충돌을 막기 위해 갱신시 명시적으로 잠그는 것
    • 가령 두명으ㅢ 플레이어가 같은 객체를 동시에 잠그지 못하게 하는 것

갱신 손실 자동 감지

  • 원자적 연산과 잠금은 read0modify0wirte 주기가 순차적으로 실행되도록 강제함으로 손실을 방지하는 방법
  • 자동감지는 db에서 스냅숏 격리와 결합해 효율적으로 시행할 수 있다. 누락이 발견되면 tr을 어보트 시키는 것이다.

Compare-and-set

  • 값을 마지막으로 읽은 후로 변경되지 않았을 때만 갱신을 허용함. 현재 값이 이전에 읽은 값과 일치하지 않으면 갱신은 반영되지않음
  • 앞과 비교해서 맞아야 갱신이 가능하다는 뜻인듯~
  • 최신 복사본이 하나만 있다고 가정한다.

쓰기 스큐와 팬텀

  • 경쟁조건이 발생할 경우 발생할 수 있는 좀 더 미묘한 충돌을 보자!
  • 데이터 오염을 방지하려면 경쟁조건을 방지해야한다.

쓰기 스큐를 특징짓기

  • 한번에 여러명이 동시에 쓰기를 진행하여 조건을 맞추지 못할 때( 서비스에서 정의한 조건)
  • 경쟁조건이 명백히 발생하였을 때
  • 쿼리문을 통해 잠글 수 ㅣㅇㅆ다.

직렬성

  • 다루기 까다로운 예시
    • 격리수준은 이해하기 어렵고 데이터베이스마다 구현에 일관성이 없다.
    • 거대한 앱에서 특정 격리수준에서 해당코드를 실행하는게 안전한지 알기 어렵다
    • 경쟁조건을 감지하는데 도움이 되는 좋은 도구가 없다.
  • 이럴 때, 직렬성 격리를 사용하라!
  • 보통 가장 강력한 격리 수준이다.
  • 동시성 없이 한번에 하나씩 직렬로 실행될 때와 같도록 보장한다.
  • 모든 경쟁조건을 막는 것이다.
  • 직렬성 격리의 기법
    • 말그대로 순차적으로 실행하기
    • 수십년동안 유일한 수단이었던 2단계 잠김
    • 직렬성 스냅숏 격리 같은 낙관적 동시성제어 기법

실제적인 직렬 실행

  • 동시성을 피하는법: 동시성을 완전히 제거한다.
  • 비교적 최근에야 단일 스레드 실행이 가능하게 되었다.
  • 두가지 발전이 생각을 바꿨다. (원래 동시 스레드 선호)
    • 램 가격이 저렴해져서 많은 사용 사례에서 활성화된 데이터셋 전체를 메모리에 유지할 수 있을 정도가 됐다.
    • Oltp 트랜젝션이 보통 짧고 실행하는 읽기와 쓰기의 개수가 적다는 것을 깨달았다.
  • 이 방법은 볼트db/h-스토어, 레디스, 데이토믹에서 구현되어 있다.(단일스레드)
  • 잠금을 코디네이션하는 오버헤드를 피할 수 있지만 cpu 코어 하나의 처리량으로 제한된다.

트랜잭션을 스토어드 프로시저 안에 캡슐화하기

  • 초기에는 사용자 활동의 전체 흐름을 포함할 수 있게하려는 의도가 있었다.
  • 사람을 기다리는 것은 비효율적이다(잠재적으로 많은 트랜잭션이 있음)
  • 사람이 주요경로에서 제외되고 한번에 구문하나씩 실행하는 방식으로 되어왔다. 이것은 처리량이 끔찍하다.
  • 때문에 단일 ㄹ스레드에서 트랜잭션을 순차적으로 처리하는 시스템들은 상호작용하는 다중구문을 허용하지 않는다. 대신 앱은 코드 전체를 스토어드 프로시저 형태로 db에 미리 제출해야한다.

스토어드 프로시저의 장단점

  • 단점
    • Db에서 실행되는 코드는 관리하기 어렵다.
    • Db 벤더마다 제각각 스토어드 프로시저용 언어가 있다.
    • Db는 성능에 민감해서, 잘못 작성된 스토어드 프로시저는 곤란한 상황을 만들 수 있다.
  • 극복방법
    • 범용 프로그래밍 언어를 사용한다.
    • 스토어드 프로시저가 있고 데이터가 메모리에 저장된다면 모든 트랜잭션을 단일 스레드에 실행하는게 현실성이 있다. (좋은 처리량을 얻을 수 있다)
    • 복제에도 사용되는 경우도 있다. (볼트db- 결정적이어야함(동일한결과))

파티셔닝

파티셔닝은 순서, 멀티스레드는 노순서

  • 직렬성은 단일 cpu 코어의 속도로 제한된다.
  • 특히 쓰기에서 성능이 병목이 될 수 있다.
  • 여러 cpu 코어를 쓰기위해 데이터를 파티셔닝할 수도 있다.
    • 코어별로 파티션을 할당해서 cpu 코어 개수에 맞춰 선형적으로 확장할 수 있다.
    • 그런데 여러 파티션에 접근해야하는 tr이 있다면 모든 파티션에 걸쳐 코디네이션을 해야한다. 이는 느리다.

직렬 실행 요약

  • 모든 트랜잭션은 작고 빨라야한다.
  • 활성화된 데이터셋이 메모리에 적재될 수 있는 경우로 사용이 제한된다.
  • 쓰기처리량이 단일 cpu로 처리할 수 있을 정도로 충분히 낮아야한다.
  • 여러파티션을 쓸 수 있지만 제한이 있다.

2단계 잠금(2pl)

  • 2pc 랑 다르다.
  • 2단계잠금은 앞에 나왔던 더티쓰기를 막는 것과 유사하지만 훨씬 더 강하다.
    • 잠금: 다른 쓰기가 완료될 때까지 대기(여전히 접근 가능)
      • 쓰이는 동안 과거 데이터 읽기 가능
    • 2pl: 쓰기 시 독점적인 접근 필요
      • 쓰이는 동안 과거 데이터 읽기 불가능
  • 2pl은 쓰기 뿐만 아니라 읽기도 막는다. 이는 스냅숏격리와 반대된다

2단계 잠금 구현

  • 보통 2pl은 이노db에서 직렬성 격리 수준을 구현하는데 사용되고, db2에서는 반복읽기격리수준을 구현하는데 사용된다.
  • 잠금은 공유모드나 독점모드로 사용될 수 있다.
    • Tr이 읽기를원함 ⇒ 공유모드로 잠금 획득 ⇒ 여러명이 공유모드로 잠금하고 읽기는 허용되지만 독점모드로 잠금되어 있다면 읽을 수 없다.
    • 쓰기를 원한다면 독점모드로 잠금을 획득해야한다.
    • 읽다가 쓰면 독점으로 업그레이드
    • 잠금을 로득한 후에는 종료될 때까지 잠금을 가지고 있어야한다
  • 2단계의 의미: 1단계 ⇒ 잠금 획득 , 2단계 ⇒ 잠금 해제
  • 잠금 수요가 겹치면 멈춘 상황이 쉽게 발생할 수 있다. 이런 상황을 교착상태라고 한다.(데드락)

2단계 잠금의 성능

  • 성능이 약점이다. 완화된 격리 수준보다 처리량과 질의응답시간이 크게 나빠진다.
  • 잠금 획득의 오버헤드 뿐만 아니라 동시성이 줄어들기 때문이다.
  • 교착상태가 성능 저하를 부른다.
  • (아래) 2단계로 팬텀 문제를 해결하는 방법

서술 잠금 predicate lock

  • 직렬성 격리를 쓰는db는 팬텀을 막아야한다.
  • 쿼리문의 where을 통해 겹치지 않는 조건을 찾아 팬텀을 막는다.
  • 핵심아이디어는 서술잠금은 db에 아직 존재하지 않지만 미래에 추가될 수 있는 객체에도 적용이 가능하다.

색인 범위 잠금 index range locking, next-key-locking

  • 서술 잠금은 잘 작동하지 않는다. 잠금이 많으면 조건에 부합하는 잠금을 확인하는데 시간이 걸린다. 그래서 색인범위 잠금을 쓴다.
  • 검색 조건에 인덱싱이 붙은 칼럼이 붙는 것 , 팬텀과 쓰기스큐로부터 보호해주는 효과가 있다.
  • 정밀하지 않지만 오버헤드가 훨씬 낮다.
  • 적합한 색인이 없다면 테이블 전체에 공유색인을 잡는 방법도 있다.

직렬성 스냅숏 격리(ssi)

  • 직렬성과 좋은 성능의 공존 방법!
  • Serializable snapshot isolation
  • 완전한 직렬성을 제공하지만 스냅숏 격리에 비해 약간의 성능손해만 있을 뿐.

비관적 동시성 제어 대 낙관적 동시성 제어

  • 2pl: 비관적 동시성 제어 메커니즘
    • 뭔가 잘못될 가능성이 있으면 뭔가를 하기 전에 상황이 다시 안전해질 때까지 기다리는게 낫다는 원칙을 기반으로 한다.
    • 상호배제와 유사하다. Mutual exclusion
  • 직렬 실행은 극단적으로 비관적이다.(무조건 독점잠금과 같다)
  • 직렬성 스냅숏 격리: 낙관적 동시성 제어 기법
    • 낙관적이란? 뭔가 위험한 상황이 발생할 가능성이 있을 때 트랜잭션을 막는 대신 모든 것 이 괜찮아질것이라는 희망을 갖고 계속 진행한다는 뜻이다
    • 나쁘면 어보트
  • 예비용량이 충분하고 경쟁이 너무 심하지 않으면 낙관적 동시성 제어 기법은 비관적 동시성 제어보다 성능이 좋다.
  • 스냅숏 격리를 기반으로 한다.

뒤처진 전제에 기반한 결정

  • 트랜잭션은 전제를 기반으로 동작한다. 커밋할 때 전ㄴ제가 변경되어 더이상 참이 아닐 수 있다.(가령 where문)
  • 이런 쓰기가 유효하지 않을 수 있다고 가정한다.

오래된 mvcc 읽기 감지하기, 과거의 읽기에 영향을 미치는 쓰기 감지하기

  • 스냅숏은 보통 다중 버전 동시성제어로 구현한다.
  • 전제가 변경되는 것을 막으려면 db는 트랜잭션이 mvcc 가시성 규칙에 따라 다른 tr의 쓰기를 무시하는 경우를 추적해야한다. 커밋될 때까지 기다려야한다.

직렬성 스냅숏 격리의 성능

  • 상충: 성능과 트랜잭션의 읽기 쓰기를 추적하는 세밀함의 정도

 

출처

 

데이터 중심 애플리케이션 설계 : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

 

반응형

댓글