완화된 격리 수준

Alan·2023년 4월 30일
0

직렬성 격리

  • 직렬성 격리는 데이터베이스가 여러 트랜잭션들이 직렬적으로 실행되는 것즉 동시성 없이 한 번에 트랜잭션 하나만 실행)과 동일한 결과가 나오도록 보장한다는 것을 의미

  • 하지만, 현실적으로 직렬성 격리를 달성하는 것은 간단한 일이 아님. 달성한다고 해도 직렬성 격리는 성능 비용이 있고 많은 데이터를 다루어야 한다면 그 비용을 지불하지 않는 선택을 내릴 수 있음

  • 대부분의 데이터베이스는 직렬성 격리보다 완화된(비직렬성) 격리성을 사용

커밋 후 읽기

  • 가장 기본적인 수준의 트랜잭션 격리. 아래와 같은 두 가지를 보장

    • 데이터베이스에서 읽을 때 커밋된 데이터만 보게 된다(더티 읽기가 없음)
    • 데이터베이스에서 쓸 때 커밋된 데이터만 덮어쓰게 된다(더티 쓰기가 없음)
  • 더티 읽기

    • 트랜잭션이 데이터베이스에 데이터를 썼지만, 아직 커밋되지는 않은 상황에서 다른 트랜잭션에서 커밋되지 않은 데이터를 볼 수 있다면 이를 더티 읽기라고 함

    • 즉, 트랜잭션이 쓴 내용은 커밋된 후에야 다른 트랜잭션에서 보일 수 있다는 의미

    • 더티 읽기를 막아야 하는 이유

      • 트랜잭션이 여러 객체를 갱신한다면 다른 트랜잭션이 일부는 갱신된 값을 일부는 갱신되지 않은 값을 볼 수 있음
      • 트랜잭션이 어보트되면 그때까지 쓴 내용은 모두 롤백되어야 하는데, 다른 트랜잭션에서 롤백되어야 하는 내용을 볼 수 있음
  • 더티 쓰기

    • 두 트랜잭션이 동일한 객체를 동시에 갱신한다고 가정. 일반적으로 나중에 쓴 내용이 먼저 쓴 내용을 덮어씀

    • 하지만 먼저 쓴 내용이 아직 커밋되지 않은 트랜잭션에서 쓴 내용이며, 나중에 실행된 쓰기 작업이 커밋되지 않은 값을 덮어쓴다면, 순서가 보장되지 않음

    • 보통 먼저 쓴 트랜잭션이 커밋되거나 어보트될 때까지 두 번째 쓰기를 지연시키는 방법으로 더티 쓰기를 방지

    • 더티 쓰기를 방지함으로서 몇 가지 동시성 문제를 해결할 수는 있지만, 아래 그림과 같은 경쟁 조건은 막지 못함. 이와 같은 동시성 문제를 해결하는 방법으로 갱신 손실 방지가 있음

  • 구현

    • 로우 수준 잠금

      • 트랜잭션에서 특정 객체(로우나 문서)를 변경하고 싶다면 먼저 해당 객체에 대한 잠금을 획득

      • 트랜잭션이 커밋되거나 어보트될 때까지 잠금을 보유

      • 다른 트랜잭션에서 동일한 객체에 쓰기를 원한다면 첫 번째 트랜잭션이 커밋되거나 어보트된 후에야 잠금을 얻어 진행

  • 오라클 11g, PostgreSQL, MemSQL 등이 기본 설정으로 사용

스냅숏 격리

  • 커밋 후 읽기 격리를 사용하더라도 동시성 버그가 완전히 해소되지는 않음

  • 엘리스는 두 개의 계좌에 각각 500씩 가지고 있고, 하나의 계좌에서 다른 계좌로 100을 송금

  • 운이 없어서 트랜잭션이 처리되고 있는 순간에 계좌 잔고를 본다면, 총액이 900이라고 나올 수 있음

  • 이런 현상을 비반복 읽기 or 읽기 스큐(read skew)(시간적인 이상 현상을 뜻함)라고 함

  • 읽기 스큐는 커밋 후 읽기 격리에서는 받아들일 수 있는 것으로 여겨짐

  • 물론 이런 현상은 일시적일테지만, 몇몇 경우에서는 이를 받아들일 수 없기도 함

    • 백업된 시점이 읽기 스큐가 발생한 시점이라면, 이 백업을 사용해서 복원하면 읽기 스큐가 영속적인 것으로 변경됨
  • 스냅숏 격리는 각 트랜잭션이 데이터베이스의 일관된 스냅숏으로부터 데이터를 읽게 함으로써 이를 해결

  • PostgreSQL, InnoDB(MySQL, Oracle) 등에서 지원

  • 구현

    • 커밋 후 읽기에서처럼 쓰기를 실행하는 트랜잭션은 같은 객체에 쓰는 다른 트랜잭션을 차단하지만, 읽을 때는 차단하지 않음

    • 성능 관점에서 스냅숏 격리의 핵심원리는 읽는 쪽에서 쓰는 쪽을 결코 차단하지 않고 쓰는 쪽에서 읽는 쪽을 결코 차단하지 않는다는 것

    • 데이터베이스는 객체마다 커밋된 버전 여러 개를 유지. 이러한 기법을 **다중 버전 동시성 제어(Multi-Version Concurrency Control, MVCC)라고 함

    • MVCC는 커밋 후 읽기에서도 사용됨(버전을 두 개는 가지고 있어야 하기 때문)

  • 위 그림은 PostgreSQL에서 MVCC 격리 구현을 나타냄
  • 트랜 잭션이 시작하면 계속 증가하는 고유한 트랜잭션 ID(txid)를 할당
  • 테이블의 각 로우에는 그 로우를 테이블에 삽입한 txid를 갖는 created_by 필드가 존재
  • 처음엔 비어있는 deleted_by가 존재하며, 트랜잭션이 로우를 삭제하면 deleted_by에 해당 txid를 설정하고 지워졌다고 표시
  • 이후, 아무 트랜잭션도 더 이상 삭제된 데이터에 접근하지 않는 게 확실해지면, 데이터베이스의 가비지 컬렉션 프로세스가 지워졌다고 표시된 로우들을 삭제
  • 색인

    • 스냅숏 격리에서 색인은 어떻게 저장할까?

      • 간단한 예는 색인이 객체의 모든 버전을 가리키게 하고 색인 질의가 현재 트랜잭션에서 볼 수 없는 버전을 걸러내게 하는 것

      • PostgreSQL은 동일한 객체의 다른 버전들이 같은 페이지에 저장될 수 있다면, 색인 갱신을 회피하는 최적화를 수행

      • 카우치DB 등은 추가 전용이며 쓸 때 복사되는 변종 B트리를 사용. 페이지가 갱신될 때 덮어쓰는 대신 각 변경된 페이지의 새로운 복사본을 생성

  • 스냅숏 격리의 여러 이름들

    • 여러 이름을 가지게 된 이유는 SQL 표준에 스냅숏 격리의 개념이 없기 때문(1975년 기반)
    • 오라클에서는 직렬성, PostgreSQL, MySQL에서는 반복읽기라고 부름

0개의 댓글