Home 데이터베이스 트랜잭션 - 2
포스트
취소

데이터베이스 트랜잭션 - 2

직렬성 (=serializable)

  • 격리수준의 문제
    • 위에서 공부한 것 처럼 격리 수준은 너무 어렵고 복잡하다. 그리고 모든 문제를 해결해주지 못한다.
    • 또 데이터베이스의 구현마다 그 격리 수준의 동작 방식은 조금씩 다르다.
    • 어떤 로직에는 어떤 격리수준이 안전한지 파악하는 일이라는건 쉬운일이 아니다.
    • 경쟁조건을 완벽하게 감지하는 방법은 없다.
  • 이에 대한 연구자들의 답은 항상 같았다 직렬성 (=serializable) 격리를 사용해라..!
  • 직렬성 (=serializable)
    • 가장 강력한 격리
    • ::여러 트랜잭션이 병렬로 실행되더라도 최종 결과는 동시성 없이 한 번에 하나씩 직렬로 실행될 때와 같도록 보장한다.::
    • 데이터베이스가 발생할 수 있는 모든 경쟁 조건을 막아준다.
    • 그런데 우리는 왜 모든 직렬성 (=serializable) 격리를 사용하지 않는거지?

실제적인 직렬 실행

  • 동시성 문제를 해결하는 가장 간단한 방법? => 동시성을 제거하는 것
  • 한번에 하나씩만 직렬로 단일 스레드에서 실행하면 된다.
  • ::다만. 지난 30년 동안 높은 성능을 위해서 다중 스레드 동시성이 필수적인 것으로 여겨졌는데 이제와서 단일 스레드 실행이 가능하게 된 이유는 무었을까?::
    • 램 가격이 저렴해져 많은 사용 사례에서 활성화된 데이터셋 전체를 메모리에 유지할 수 있을 정도가 됨.
    • 데이터베이스 설계자들은 트랜잭션이 보통 짧고 실행하는 읽기와 쓰기의 개수가 적다는 것을 깨달았다.
      • 반대로 오래 실행되는 분석 질의는 전형적으로 읽기 전용이라서 직렬 실행 루프 밖에서 일관된 스냅숏을 사용해 실행할 수 있다. (데이터 분석용)
  • 드랜잭션을 순차적으로 실행하는 방법은 레디스, 볼트 등에 구현되어있고 어떤 경우에는 동시성을 지원하는 시스템보다 성능이 더 좋은 경우도 있다.
  • 그렇지만 이들의 처리량은 CPU 코어 하나의 처리량으로 제한된다.
    • 단일 스레드를 최대한 활용하려면 트랜잭션이 전통적인 형태와는 다르게 구성되어야한다.

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

  • 항공권 예약은 경로 조회, 요금 조회, 좌석 조회, 일정 조회, 지불 등 복잡한 과정이 있다 이 모든 과정을 하나의 트랜잭션으로 표현하고 원자적으로 커밋될 수 있다면 깔끔할것인가?
  • 대부분의 데이터베이스는 위와 같은 과정을 하나의 트랜잭션으로 하기엔 너무 많은 지연과 성능 이슈가 있을 수 있다.그러므로 보통은 대화식으로 트랜잭션을 짧게 유지한다.
  • 단일 스레드에서 트랜잭션을 순차적으로 처리하는 시스템들은 어플리케이션의 코드 전체를 스토어드 프로시저 형태로 데이터베이스에 미리 제출하는 형식을 많이 사용한다.
    • 트랜잭션에 필요한 데이터는 모두 메모리에 있고 스토어드 프로시저는 네트워크나 디스크 I/O 대기 없이 매우 빨리 실행된다고 가정한다.

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

  • 단점
    • 데이터베이스 마다 스토어드 프로시저용 언어가 다르다
    • 데이터베이스에서 실행되는 코드는 관리하기 어렵다
    • 데이터베이스는 애플리케이션 서버 보다 더 성능에 민감한 경우가 많다.
  • 장점/단점 극복
    • 스토어드 프로시저가 있고 데이터가 메모리에 저장된다면 모든 트랜잭션을 단일 스레드에서 실행하는게 현실성이 있다.
      • I/O 대기가 필요 없고 다른 동시성 제어 메커니즘의 오버헤드를 회피하므로 단일 스레드로 상당히 좋은 처리량을 얻을 수 있다.

직렬성에서 파티셔닝

  • 모든 트랜잭션을 순차적으로 실행하면 동시성 제어는 훨씬 간단해지지만 데이터베이스의 트랜잭션 처리량이 단일 장비에 있는 단일 CPU로 제한된다.
  • 다만 각 트랜잭션이 단일 파티션 내에서만 데이터를 읽고 쓰도록 데이터를 파티셔닝 할 수 있다면 각 파티션은 다른 파티션과 독립적으로 자신만의 트랜잭션 처리 스레드를 가질 수 있다.
    • 이렇게 되면 각 CPU 코어에 각자의 파티션을 할당해서 트랜잭션 처리량을 CPU 코어 개수에 맞춰 선형적으로 확장할 수 있다.
  • 그러나 여러 파티션에 접근해야하는 트랜잭션이라면 모든 파티션에 걸쳐 잠금을 걸어여하니 주의

직렬실행 요약

  • 모든 트랜잭션은 작고 빨라야 한다. 느린 트랜잭션 하나가 모든 트랜잭션을 지연시킬 수 있다.
  • 활성화된 데이터셋이 메모리에 적재될 수 있는 경우로 사용이 제한된다.

2단계 잠금 (2PL) - two phase locking

  • 직렬성을 구현하는 방법중 가장 널리 쓰인 알고리즘 중 하나
  • 잠금을 2단계로 나눠서 수행한다.
  • ::쓰기 트랜잭션은 다른 쓰기 트랜잭션 뿐 아니라 읽기 트랜잭션도 진행하지 못하게 막고 그 반대도 마찬가지::
    • 스냅숏 격리는
      • 읽는 쪽은 쓰는쪽을 막지 않고 쓰는 쪽도 읽는 쪽을 막지 않는것 이라는 점에서 차이가 있다.

2단계 잠금 구현

  • 읽는 쪽과 쓰는 쪽을 막는 것은 데이터베이스의 각 객체에 잠금을 사용해 구현한다.
  • 공유잠금 (=shared lock), 독점(베타)잠금 (=exclusive lock)을 사용한다.
  • 구현
    • 트랜잭션이 객체를 읽기를 원한다면 먼저 공유잠금을 획득해야한다. 여러 트랜잭션이 말그대로 공유잠금을 획득하는 것은 허용되지만 만약 그 객체에 이미 베타잠금이 걸려있다면 베타잠금이 걸린 트랜잭션이 완료될 때 까지 기다려야한다.
    • 트랜잭션이 객체에 쓰기를 원한다면 먼저 베타잠금을 얻어야 한다. 다른 어떤 트랜잭션도 동시에 잠금을 획득할 수 없다. (공유잠금, 베타잠금 모두)
    • 트랜잭션이 객체를 읽다가 쓰기를 실행하는 경우엔 공유잠금을 베타잠금으로 변경해야하며 베타잠금을 얻는 과정은 위와 같다.
    • 트랜잭션이 잠금을 획득한 후에는 트랜잭션이 종료될 때까지 잠금을 갖고 있어야한다.
      • ::그래서 2단계다.::
        • 1단계 : 잠금을 획득할 때
        • 2단계 : 잠금을 해제할 때
  • 교착상태
    • 잠금이 아주 많이 사용되므로 트랜잭션들은 서로의 잠금을 기다리다가 교착상태에 빠지는 경우가 아주 많다.
    • 이런 상황을 자동으로 감지하고 트랜잭션 중 하나를 어보트시켜 다른 트랜잭션들이 진행할 수 있게 한다.

2단계 잠금 성능

  • 성능이 좋은 편은 아니다
  • 2PL 직렬성 격리는 교착상태가 많이 발생한다.
  • 이는 성능 문제를 넘어선 더 큰 문제를 일으킬 수 있다.

서술 잠금

  • 팬텀을 막을 수 있는 잠금이지만 잘 사용하지는 않는다.
  • 데이터베이스에 아직 존재하지는 않지만 미래에 추가될 여지가 이쓴 객체(팬텀)에 잠금을 거는 것
  • 어떤 서술(검색 조건에 부합하는 모든 객체)에 잠금을 거는 것

색인 범위 잠금

  • 서술잠금은 사실상 잘 사용하지 않고 보통 2PL을 지원하는 데이터베이스는 색인 범위 잠금을 구현한다.
  • 색인이 있다면 그 색인의 범위에 잠금을 걸어 팬텀을 방지하는 것
  • 범위 잠금을 잡을 수 있는 적합한 색인이 없다면 데이터베이스는 테이블 전체에 공유잠금을 잡는다. 성능에 좋지 않다.

직렬성 스냅숏 격리 (SSI) - serializable snapshot isolation

  • 직렬성과 좋은 성능은 근본적으로 공존할 수 없는것인가? 라는 물음으로 부터 시작
  • 앞에서 언급된 격리 수준들의 문제점의 대부분을 해결할 수 있는 새로운 알고리즘이다.
  • 낙관적인 방법을 사용해 트랜잭션이 차단되지 않고 진행할 수 있게한다.
  • 트랜잭션이 커밋을 원할 때 트랜잭션을 확인해서 실행이 직렬적이지 않다면 어보트 시킨다.

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

  • 2PL 잠금은 이른바 비관적 동시성 제어 메커니즘이다.
    • 뭔가 잘못될 가능성이 있으면 뭔가를 하기 전부터 상황이 안전해질 때까지 기다리는게 낫다고 보는 것 이다.
  • 반대로 SSI는 낙관적 동시성 제어 기법이다.
    • 트랜잭션이 커밋되기를 원하는 시점에 데이터베이스는 나쁜 상황이 발생했는지 (즉 격리가 위반 되었는지) 확인한다. 만약 그렇다면 트랜잭션은 어보트 되고 재시도 해야한다. 직렬로 실행된 트랜잭션만 커밋이 허용된다.

오래된 읽기를 감지하는 법? MVCC 활용

  • SSI는 말그대로 다중 버전 동시성 제어로 구현한다.
  • 왜 커밋되는 순간까지 낙관적으로 기다릴까?
  • 오래된 읽기를 감지한다고 해도 실질적으로 쓰기 스큐의 위험성이 없는 경우라면 불필요한 어보트를 할 필요가 없기때문
  • 하지만 일관된 스냅숏에서 부터 읽어서 오래된 읽기를 감지한다는 특성은 유지한다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

데이터베이스 트랜잭션 - 1

코루틴에서 Transaction 사용하기

Comments powered by Disqus.