[번역글] 데이터베이스 시스템 이해하기 : 트랜잭션 격리수준 소개(줄글주의)
- 흑구의 공부내용 공유
- 2021. 12. 5. 07:03
수십 년 동안 데이터베이스 시스템은 사용자가 선택할 수 있는 여러 격리 수준을 제공했습니다. 고급형의 "serializability"부터 저가형의 "read committed" 또는 "read uncommitted"에 이르기까지 다양합니다. 이러한 서로 다른 격리수준은 어플리케이션에게 수많은 동시성 버그를 노출시킬 수 있습니다. 그럼에도 불구하고 많은 데이터베이스 사용자들은 현재 사용중인 데이터베이스의 기본 격리수준을 고수하고 어떤 격리수준이 자신의 어플리케이션 환경에 가장 적합한지 고려하지 않습니다. 이러한 관행은 매우 위험합니다. Oracle, IBM DB2, Microsoft SQL Server, SAP HANA, MySQL, PostgreSQL 과 같은 유명한 데이터베이스들은 “serializability” 를 기본으로 제공하지는 않을 수 있습니다. 아래에서 자세히 설명하겠지만 “serializability” 보다 낮은 수준의 격리수준은 동시성 버그를 일으킬 가능성이 농후하며 유저에게 나쁜 경험을 제공할 수 있습니다. 데이터베이스 사용자는 데이터베이스의 기본 보장 격리수준을 확인하고 발생할 수 있는 동시성 버그에 대해서 인지하고 있는 것이 중요합니다.
많은 데이터베이스 사용자들은 현재 사용중인 데이터베이스의 기본 격리수준을 고수하고 어떤 격리수준이 자신의 어플리케이션 환경에 가장 적합한지 고려하지 않습니다.
해당 게시글에서는 데이터베이스 격리수준에 대한 튜토리얼과 낮은 격리수준의 이점, 그리고 낮은 격리수준에 따른 동시성 버그들이 어떤 것이 있을 수 있는지 확인할 수 있을 것입니다. 해당 게시글에서 집중해야될 부분은 “serializability” 격리수준과 보다 낮은 격리수준의 차이를 인지하는 것입니다. “serializable” 격리수준에도 엄연히 다른 카테고리가 있습니다.(ex, “strict serializability와 one-copy serializability”)
그러나 이 포스팅의 제목을 “데이터베이스 격리수준에 대한 소개” 라고 제시한 것을 유지하기 위해 serializable 격리수준의 미묘한 차이는 잠시 접어두고 “serializable” 격리수준의 공통성에 좀더 초점을 맞추려고 합니다. 이는 추후 다른 포스팅에서 소개하고자 합니다.
격리 수준이란 무엇인가? (What is an Isolation level)
데이터베이스 격리란 동시적으로 실행중인 다른 트랜잭션이 없는 것처럼 트랜잭션을 실행할 수 있도록 하는 데이터베이스의 기능입니다.(실제로는 동시에 많은 트랜잭션이 실행될 수 있음.) 이의 가장 중요한 목표는 동시 트랜잭션에 의해 작성된 임시, 중단 또는 잘못된 데이터의 읽기와 쓰기를 방지하는 것입니다.
아래에서 완벽한 격리를 정의해보겠습니다. 불행히 완벽한 트랜잭션에는 트랜잭션 대기시간(트랜잭션이 완료되기까지 걸리는 시간) 혹은 처리량(시스템이 처리할 수 있는 초당 트랜잭션 수)과 같은 성능적인 비용이 따르게 됩니다. 어떻게 시스템이 설계되었는지에 따라 이런 완벽한 격리의 쉽고 어려움이 결정됩니다. 가볍게 설계된 시스템에서는 이러한 완벽함을 달성하기 위해서는 엄청난 성능 비용을 필요로 할것이며 사용자는 완벽에 못미치는 경험을 제공받게 될 것입니다. 그러나 잘 설계된 완벽에 가까운 시스템에서도 완벽에 미치지 못하는 어떠한 성능적인 부분을 가지더라도 달성될 수 있는 성능적 이점이 존재하게 됩니다. 따라서 격리수준이 존재하게 됩니다. 격리수준은 시스템 사용자에게 성능 향상을 위해 격리성 보장을 절충할 수 있는 기능을 제공합니다.
가장 중요한 목표는 동시 트랜잭션에 의해 작성된 임시, 중단 또는 잘못된 데이터의 읽기와 쓰기를 방지하는 것입니다.
계속 진행해보면 알겠지만 격리수준에 대해서 이야기하다보면 혼동을 일으킬만한 미묘한 것들이 매우 많습니다. 이러한 혼동은 데이터베이스의 격리수준을 정확히 정의하지 못하는 표준 SQL과 특정 명명된 격리 수준에 자유 및 비표준 의미를 첨부하는 데이터베이스 공급업체의 존재로 인해 악화됩니다. 그럼에도 불구하고 데이터베이스 사용자는 특정 시스템에서 제공하는 다양한 기능에 대해서 인지를 하고 있어야하며 직접 실행도 해보고 어플리케이션과 적합한 수준을 선택해야합니다.
완벽한 격리
완벽한 격리에 대해서 이야기 해보도록 하겠습니다. 위에서 격리란 다른 동시에 실행 중인 트랜잭션이 없는 것처럼 트랜잭션을 실행할 수 있는 데이터베이스 시스템의 기능으로 정의했습니다.(실제로는 많은 수의 동시 실행 트랜잭션이 될 수 있음) 완벽하다는 것은 무엇일까요? 처음에는 완벽은 불가능 하다고 느낄지도 모릅니다. 두 트랜잭션이 모두 동일한 데이터를 읽고 쓰는 경우 서로 영향을 미친다는 것이 중요합니다. 서로를 무시한다면 마지막으로 쓰기한 트랜잭션이 첫번째 트랜잭션의 쓰기를 덮어쓰거나 하는 식으로 방해하여 실행되지 않은 것처럼 최종상태가 존재하게 될 수 있습니다.
데이터베이스는 확장 가능한 동시성 시스템중 하나입니다. 이후 개발된 다른 동시성 시스템의 원형이었습니다. 수십년 동안 데이터베이스 커뮤니티에서는 동시성 프로그램 구현의 복잡성을 처리하기 위해 강력한 매커니즘을 개발했습니다.
아이디어는 다음과 같습니다. 인간은 기본적으로 동시성이라는 것을 추론하는 것이 매우 어렵습니다. 버그없는 비동기 프로그램의 작성은 매우 어렵습니다. 그러나 동시성을 추가하면 한 스레드가 프로그램의 17행에 도달하기 전에 다른 스레드가 5행에 도달하는 경우 발생할 수 있는 경쟁 조건은 거의 무한대입니다. 그러나 3행에 도달한 후 프로그램의 다른 동시 실행에서는 존재하지 않는 문제가 발생할 수 있습니다. 서로 다른 스레드에서 프로그램 실행이 서로 겹칠 수 있는 모든 다른 방법과 서로 다른 유형의 겹침이 어떻게 다른 최종 상태로 이어질 수 있는지 추론하는 것은 거의 *불가능*합니다.
대신 데이터베이스 시스템은 애플리케이션 개발자에게 "트랜잭션"이라는 아름다운 추상화를 제공합니다. 트랜잭션에는 임의의 코드가 포함될 수 있지만 기본적으로는 단일 스레드입니다.
애플리케이션 개발자는 시스템에서 실행 중인 다른 동시 프로세스가 없을 때 코드가 올바른지 확인하기 위해 트랜잭션 내의 코드에만 집중하면 됩니다. 데이터베이스의 시작 상태가 주어지면 코드는 애플리케이션의 의미를 위반해서는 안 됩니다. 코드의 정확성을 보장하는 것은 중요하지 않지만 코드가 자체적으로 실행될 때 코드의 정확성을 보장하는 것이 공유 데이터를 읽거나 쓰려고 시도할 수 있는 다른 코드와 함께 실행될 때 코드의 정확성을 보장하는 것보다 훨씬 더 쉽습니다.
트랜잭션에는 임의의 코드가 포함될 수 있지만 기본적으로는 단일 스레드입니다.
만약 어플리케이션 개발자가 그들의 코드에 대한 정확성을 다른 동시 프로세스가 돌고 있지 않는 상황에서 보장할 수 있다면, 완벽한 격리를 보장하는 시스템은 같은 데이터를 동시적으로 실행하고 있는 프로세스가 읽거나 쓰고 있는 상황에서도 그 코드가 정확하다고 보장할 수 있습니다. 즉, 데이터베이스 시스템은 데이터베이스 사용자가 동시성에 대한 걱정없이 코드를 작성할 수 있도록 합니다.
이 수준의 완벽함을 구현하는 것은 어렵게 들리지만 실제로 달성하는 것은 매우 간단합니다. 우리는 이미 시작 상태에 대해 동시성 없이 실행할 때 코드가 정확하다고 가정했습니다. 따라서 트랜잭션 코드가 순차적으로 실행되면 최종 상태도 정확합니다. 따라서 완벽한 격리를 달성하기 위해 시스템이 수행해야 하는 모든 작업은 트랜잭션이 동시에 실행될 때 최종 상태가 직렬로 실행되는 경우 존재하는 시스템 상태와 동일하도록 하는 것입니다. Lock, validation 또는 multi-versioning 와 같은 여러 가지 방법으로 이를 달성할 수 있습니다. 우리 목적의 핵심은 "완벽한 격리"를 시스템이 트랜잭션을 병렬로 실행할 수 있는 능력으로 정의한다는 것입니다. 그러나 마치 그들이 차례로 실행하는 것과 같은 방식으로..
SQL 표준에서 이러한 완벽한 격리 수준을 “serializability” 라고 합니다.
동시 시스템에서의 이상
SQL 표준은 “serializability” 말고도 다른 격리성 수준을 정의합니다. 게다가 상용 데이터베이스에서 볼 수 있는 다른 격리수준도 있습니다. 특히 SQL 표준에는 포함되어 있지 않은 스냅샷 격리가 있습니다. 이러한 다양한 격리 수준에 대해 논의하기 전에 “serializability”보다 낮은 격리 수준에서 발생할 수 있는 몇 가지 잘 알려진 애플리케이션 버그에 대해 논의하겠습니다.
고객이 위젯을 구매할 때마다 다음 "구매" 트랜잭션이 실행된다고 가정해 보겠습니다.
- 현재의 인벤토리 내역 읽기(42)
- 1에서 읽은 것보다 하나 적은 새 인벤토리를 작성. (41, 위에서 하나 읽었으니 하나를 빼서 작성하면 총 갯수는 같다는 의미인듯)
- 구매에 해당하는 새 주문을 주문 테이블에 삽입 (주문테이블에 하나씩 삽입)
이러한 구매 트랜잭션이 연속적으로 실행되는 경우 모든 초기 재고가 항상 고려됩니다. 42개의 위젯으로 시작했다면 항상 남은 모든 재고와 주문 테이블의 주문 합계는 42가 됩니다.
그러나 이러한 트랜잭션이 “serializability”보다 낮은 격리 수준에서 동시에 실행되면 어떻게 될까요?
예를 들어, 동시에 실행 중인 두 트랜잭션이 동일한 초기 인벤토리(42)를 읽은 다음 새 주문에 추가하여 읽은 값(41)보다 하나 작은 새 인벤토리를 작성하려고 시도한다고 가정해 보겠습니다. 이러한 경우 최종 상태는 41개의 인벤토리이지만 주문 테이블에는 2개의 새로운 주문이 있습니다(총 43개의 위젯이 설명됨) 우리는 무에서 위젯을 만들었습니다! 이것은 명백한 버그입니다. 이를 업데이트 손실 이상(lost-update anomaly) 현상이라고 합니다. (42개의 인벤토리인데 여기서 2개의 트랜잭션이 하나씩 읽었으니 주문테이블에는 2개의 인서트가 발생함. 하지만 인벤토리에는 41개가 남아있으므로(최종상태) 주문테이블과의 합이 43인데 여기서 1개의 인벤토리가 생성되었다는 것을 나타낸듯.)
다른 예로, 동일한 두 트랜잭션이 동시에 실행되고 있지만 이번에는 첫 번째 트랜잭션의 (2)와 (3) 단계 사이에서 두 번째 트랜잭션이 시작된다고 가정해 보겠습니다. 이 경우 두 번째 트랜잭션은 감소된 재고 값을 읽습니다. 즉, 값 41을 읽고 40으로 감소시키고 주문을 기록합니다. 그 동안 주문을 작성할 때 첫 번째 거래가 중단되었습니다(예: 신용 카드 거부로 인해).이러한 경우 중단 프로세스 동안 첫 번째 트랜잭션은 시작되기 전의 데이터베이스 상태로 되돌아갑니다(인벤토리가 42일 때). 따라서 최종 상태는 재고가 42이고 하나의 주문이 작성됩니다(두 번째 트랜잭션에서). 다시 말하지만, 우리는 무에서 위젯을 만들었습니다! 다시 말하지만, 우리는 무에서 위젯을 만들었습니다! 이를 “Dirty-Write” 이상이라고 합니다(커밋 또는 중단 여부를 결정하기 전에 두 번째 트랜잭션이 첫 번째 트랜잭션의 쓰기 값을 덮어쓰기 때문).
세 번째 예로, 별도의 트랜잭션이 지금까지 존재했던 모든 위젯을 설명하기 위해 인벤토리와 주문 테이블을 모두 읽는다고 가정해 보겠습니다. 구매 트랜잭션의 (2)와 (3) 단계 사이에서 실행되는 경우 위젯이 인벤토리에서 사라졌지만 아직 주문으로 나타나지 않은 데이터베이스의 임시 상태를 볼 수 있습니다. 위젯이 손실된 것처럼 보일 것입니다. 애플리케이션의 또 다른 버그입니다. 회계 거래가 구매 거래의 임시(불완전) 상태를 읽을 수 있도록 허용했기 때문에 이를 "Dirty-Read" 이상이라고 합니다. (2와 3사이에서 실행되는 경우 인벤토리 업데이트 -> 주문 삽입 으로 진행되는데 주문 삽입 이전에 실행되는 경우 주문이 없는 것처럼 보이기 때문)
네 번째 예에서 별도의 트랜잭션이 인벤토리를 확인하고 위젯이 10개 미만이면 위젯을 추가로 획득한다고 가정해 보겠습니다.
첫번째 코드
IF (READ(Inventory) = (10 OR 11 OR 12)) -> 표준 배송을 통해 재고를 보충하기 위해 몇 가지 새 위젯 배송
두번째 코드
IF (READ(Inventory) < 10) -> 특급 배송을 통해 재고를 보충하기 위해 몇 가지 새로운 위젯을 배송
이 트랜잭션은 인벤토리를 두 번 읽습니다. 다른 구매 트랜잭션이 이 트랜잭션의 (1) 단계와 (3) 단계 사이에 실행되는 경우 매번 다른 재고 값을 읽습니다. 구매 트랜잭션이 실행되기 전의 초기 재고가 10인 경우 동일한 재입고 요청이 두 번(표준 배송으로 한 번, 특급 배송으로 한 번) 발생합니다. 이를 non-repeatable read 라고 합니다. (즉, 다른 구매 트랜잭션에 의해 inventory의 값이 업데이트 되는 경우 두 read 쿼리의 값이 달라지게 되므로 표준배송 이후 특급배송을 한번 더 할 수도 있다. -> update에 의한 오류)
다섯 번째 예로 주문의 최고 가격을 계산하기 위해 주문 테이블을 스캔한 다음 평균 주문 가격을 찾기 위해 다시 스캔하는 트랜잭션을 상상해 보십시오. 이 “두 스캔 사이에(최고가격 스캔, 평균가격 스캔)” 평균을 너무 많이 왜곡하여 이전 스캔에서 발견된 최고 가격보다 높아지는 매우 비싼 주문이 삽입됩니다. 이 트랜잭션은 최대 가격보다 큰 평균 가격을 반환합니다. 즉, serializable 시스템에서는 절대 발생할 수 없는 명백한 불가능 및 버그입니다.(왜냐하면 트랜잭션이 직렬적이라면 서로 독립적이기 때문에 이런 버그는 발생할 수 없음) 이 버그는 트랜잭션이 읽은 모든 값이 두 스캔 간에 동일하게 유지되었기 때문에 non-repeatable read 와 약간 다릅니다.(팬텀은 insert에 의한 오류, non-repeatable 은 update에 의한 오류)
버그의 원인은 이 두 스캔 사이에 추가 레코드가 삽입되었다는 것입니다. 이것을 phantom read 이상이라고 합니다.
마지막 예로 애플리케이션이 인벤토리에 따라 위젯 가격을 변경할 수 있도록 허용한다고 가정합니다. 예를 들어, 많은 항공사는 항공편 재고가 감소함에 따라 티켓 가격을 인상합니다. 애플리케이션이 공식을 사용하여 이 두 변수가 상호 관련되는 방식에 대한 제약 조건을 설정한다고 가정합니다. (10*I) + P >= $500 (여기서 I는 재고이고 P는 가격임) 구매 성공을 허용하기 전에 구매 거래는 재고와 가격을 모두 확인하여 위의 제약 조건이 위반되지 않았는지 확인해야 합니다. 제약 조건을 위반하지 않는 경우 해당 구매 트랜잭션에 의한 재고 업데이트가 진행될 수 있습니다.
마찬가지로 특별 판촉 할인을 구현하는 별도의 거래는 판촉의 일부로 가격을 업데이트할 때 제약 조건이 위반되지 않도록 인벤토리와 가격을 모두 확인할 수 있습니다. 이를 위반하지 않으면 가격이 업데이트될 수 있습니다.
이제 이 두 트랜잭션이 동시에 실행되고 있다고 생각해 보십시오. 둘 다 I(재고) 및 P(가격)의 이전 값을 읽고 각각 재고 및 가격 업데이트가 제약 조건을 위반하지 않을 것이라고 독립적으로 결정합니다. 따라서 업데이트를 진행합니다. 불행히도 이것은 제약 조건을 위반하는 I(재고) 및 P(가격)의 새로운 값을 초래할 수 있습니다! 하나가 다른 것보다 먼저 실행되었다면 첫 번째 것은 성공했을 것이고 다른 하나는 첫 번째 것이 완료된 후 I 및 P 값을 읽고 업데이트가 제약 조건을 위반하여 진행되지 않음을 감지했을 것입니다. 그러나 동시에 실행 중이었기 때문에 둘 다 이전 값을 보고 업데이트를 계속할 수 있다고 잘못 결정했습니다. 이 버그는 두 개의 트랜잭션이 동일한 데이터를 읽었지만 읽은 데이터의 분리된 하위 집합을 업데이트할 때 발생하기 때문에 쓰기 “write skew” 라고 합니다.
ISO SQL 표준의 정의
SQL 표준은 이러한 예외가 가능한 측면에서 감소된 격리 수준을 정의합니다. 특히 다음 테이블이 포함되어 있습니다.
Isolation level | Dirty Read | Non-Repeatable Read | Phantom Read |
READ UNCOMMITTED | 가능 | 가능 | 가능 |
READ COMMITED | 불가능 | 가능 | 가능 |
REPEATABLE READ | 불가능 | 불가능 | 가능 |
SERIALIZABLE | 불가느 | 불가능 | 불가능 |
SQL 표준이 이러한 격리 수준을 정의하는 방법에는 많은 문제가 있습니다. 이러한 문제점의 대부분은 1995년에 이미 지적되었지만, 설명할 수 없이 이러한 문제점을 수정하지 않고 SQL 표준의 개정 이후에 릴리즈되었습니다.
첫 번째 문제는 표준이 각 격리 수준을 정의하기 위해 3가지 유형의 이상(dirty read, non-repeatable read, phantom read)만 사용한다는 것입니다. 그러나 실제로 나타날 수 있는 동시성 버그 유형은 이 세 가지 이상입니다. 이 게시물에서만 우리는 이미 여섯 가지 유형에 대해 설명했습니다. SQL 표준은 READ UNCOMMITTED, READ COMMITTED 및 REPEATABLE READ 격리 수준이 lost-update 이상, Dirty-Read 이상 또는 Write-Skew 이상에 취약한지 여부에 대해 언급하지 않습니다. 결과적으로 각 상용 시스템은 이러한 감소된 격리 수준이 영향을 받기 쉬운 다른 이상 현상을 자유롭게 결정할 수 있으며 많은 경우 이러한 취약성이 제대로 문서화되지 않아 응용 프로그램 개발자에게 혼란과 예측할 수 없는 버그가 발생합니다.
첫 번째 문제는 표준이 각 격리 수준을 정의하기 위해 3가지 유형의 이상(dirty read, non-repeatable read, phantom read)만 사용한다는 것입니다. 그러나 실제로 나타날 수 있는 동시성 버그 유형은 이 세 가지 이상입니다.
두 번째 문제는 격리 수준을 정의하기 위해 이상을 활용하여 최종 사용자에게 어떤 특정 유형의 동시성 버그가 불가능한지 보장할 수 있다는 것입니다. 특정 트랜잭션에서 볼 수 있는 잠재적 데이터베이스 상태에 대한 정확한 정의를 제공하지 않습니다. 학술 문헌에서는 격리 수준에 대해 몇 가지 개선되고 보다 정확한 정의를 제공하고 있습니다.
세 번째 문제는 표준이 실제로 사용되는 가장 널리 사용되는 격리 수준 중 하나인 스냅샷 격리(PSI, NMSI, Read Atomic 등의 다양한 변형)에 대한 정확성 제약을 정의하거나 제공하지 않는다는 것입니다. 스냅샷 격리에 대한 정의를 제공하지 않음으로써 스냅샷 격리가 허용하는 동시성 취약점의 차이가 시스템 간에 나타났습니다. 일반적으로 스냅샷 격리는 커밋된 데이터만 포함하는 데이터베이스 상태의 특정 스냅샷으로 모든 데이터 읽기를 수행합니다. 이 스냅샷은 트랜잭션 수명 동안 일정하게 유지되므로 모든 읽기가 반복 가능하도록 보장됩니다(커밋된 데이터만 포함). 또한 동일한 데이터를 쓰는 동시 트랜잭션은 서로 충돌을 감지하고 일반적으로 충돌하는 트랜잭션 중 하나를 중단하여 이 충돌을 해결합니다. 이것은 손실된 업데이트 이상(lost-update anomaly)을 방지합니다. 그러나 충돌하는 트랜잭션이 겹치는 데이터 집합을 쓰는 경우에만 충돌이 감지됩니다. 쓰기 세트가 연결되지 않은 경우 이러한 충돌이 감지되지 않습니다. 따라서 스냅샷 격리는 쓰기 스큐 이상에 취약합니다. 일부 구현은 또한 팬텀 읽기 이상에 취약합니다.
네 번째 문제는 SQL 표준이 SERIALIZABLE 격리 수준에 대해 두 가지 다른 정의를 제공하는 것 같습니다. 첫째, SERIALIZABLE을 올바르게 정의합니다. 즉, 최종 결과는 동시성이 없는 경우 발생할 수 있는 결과와 동일해야 합니다. 그러나 위의 표를 제시하는데, 이는 격리 수준이 Dirty Read, Non-Repeatable Read 또는 Phantom Read 를 허용하지 않는 한 SERIALIZABLE 이라고 부를 수 있음을 암시하는 것 같습니다. Oracle은 역사적으로 스냅샷 격리 구현을 "SERIALIZABLE"이라고 부르는 것을 정당화하기 위해 이러한 모호성을 활용했습니다. 솔직히 말해서, ISO SQL 표준을 읽는 대부분의 사람들은 문서의 앞부분에서 주어진 SERIALIZABLE의 보다 정확한 정의(정확한 것)가 문서 작성자의 의도라고 믿을 것입니다.
그럼에도 불구하고, 오라클의 변호사들이 그것을 살펴보고 문서에 다른 정의에 대한 의존을 법적으로 정당화하기에 충분한 모호성이 있다고 판단한 것 같습니다. (만약 내 독자 중 SERIALIZABLE 격리 수준을 받고 있다고 믿었지만 실제로 쓰기 스큐 이상을 경험한 응용 프로그램 개발자로부터 발생한 실제 소송에 대해 알고 있는 사람이 있다면 이에 대해 듣고 싶습니다. 또는 귀하가 응용 프로그램인 경우 개발자에게 이런 일이 발생했습니다. 저도 이에 대해 듣고 싶습니다.) -> 이 부분은 필자의 주관적인 생각입니다. 생략하셔도 좋을 것 같습니다.
결론은 다음과 같습니다. SQL 표준의 모호성이 구현/시스템 전반에 의미론적 차이를 가져오기 때문에 애플리케이션 개발자가 사용할 수 있는 실제 격리 수준에 대한 명확한 정의를 제공하는 것은 거의 불가능합니다.
그럼 어떤 격리 수준을 선택해야 할까?
응용 프로그램 프로그래머에게 드리는 조언은 다음과 같습니다. 격리 수준을 낮추는 것은 위험합니다. 어떤 동시성 버그가 나타날 수 있는지 파악하는 것은 매우 어렵습니다. 모든 시스템이 최소한 정확하고 형식적인 정의를 알고 있어야 합니다. 불행히도 Crooks 문서의 형식은 대부분의 데이터베이스 사용자에게 너무 고급이므로 데이터베이스 공급업체가 조만간 문서에서 이러한 형식을 채택할 것 같지 않습니다. 한편, 감소된 격리 수준의 정의는 실제로는 모호하고 사용하기에 위험합니다.
감소된 격리 수준은 위험합니다...감소된 격리 수준의 정의는 실제로 모호하고 사용하기에 위험합니다.
또한 특정 격리 수준에 대해 어떤 동시성 버그가 가능한지 정확히 알 수 있더라도 이러한 버그가 실제로 발생하지 않는 방식으로(또는 발생하더라도 사용자에게 부정적인 경험을 일으키지 않는 방식으로 애플리케이션을 작성해야합니다.) 적용도 매우 까다롭습니다. 데이터베이스 시스템에서 선택할 수 있는 경우 일반적으로 serializable 격리보다 낮은 격리 수준을 피하는 것이 올바른 선택입니다. 대부분의 데이터베이스 시스템의 경우 실제로 이를 수행하려면 기본값을 변경해야 합니다.
세 가지 주의 사항이 있습니다.
첫번째, 위에서 언급했듯이 일부 시스템은 "SERIALIZABLE"이라는 단어의 의미가 진정한 serializable 격리보다 약한 것을 의미할 수 있습니다. 불행히도 이것은 데이터베이스 시스템에서 단순히 SERIALIZABLE 격리 수준을 선택하는 것만으로는 실제로 직렬성을 보장하기에 충분하지 않을 수 있음을 의미합니다. 문서에서 다음과 같은 방식으로 SERIALIZABLE을 정의하는지 확인해야 합니다. 데이터베이스의 가시적 상태는 동시성이 없는 경우 발생할 수 있는 상태와 항상 동일합니다. 그렇지 않으면 애플리케이션이 쓰기-스큐 이상에 취약할 수 있습니다.
두번째, 위에서 언급했듯이 serializability 격리 수준에는 성능 비용이 따릅니다. 시스템 아키텍처의 품질에 따라 직렬화의 성능 비용은 크거나 작을 수 있습니다. 최근 연구 논문에서 우리는 잘 설계된 시스템에서 SERIALIZABLE과 READ COMMITTED 사이의 성능 차이가 무시할 수 있음을 보여주었습니다. 그리고 어떤 경우에는 SERIALIZABLE 격리 수준이 READ COMMITTED 격리 수준을 능가합니다. 시스템에서 직렬화 가능한 격리 비용이 어마어마하다는 것을 알게 된 경우 감소된 격리 수준을 고려하는 것보다 먼저 다른 데이터베이스 시스템을 사용하는 것을 고려해야 합니다.
세번째, 분산 시스템에서는 serializability 격리 수준내에서도 나타날 수 있는 중요한 이상이 있습니다. 이러한 시스템의 경우 직렬화 가능 격리 수준의 요소 간의 미묘한 차이를 이해하는 것이 중요합니다(strict-serializability 가 가장 안전한 것으로 알려져 있음). 향후 게시물에서 이 문제에 대해 더 자세히 설명하겠습니다. -> 즉, serializability 격리수준이라고 완전히 완벽한 격리수준은 아니라는 것임. 그 안에도 여러가지 구분이 나뉘어져 있고 좀더 세세한 차이가 존재한다는 것.
'흑구의 공부내용 공유' 카테고리의 다른 글
[번역글] PostgreSQL에서의 트랜잭션 격리 수준 (0) | 2021.12.10 |
---|---|
[번역글] F.I.R.S.T. 단위테스트 작성하는 방법. (0) | 2021.12.10 |
[인텔리제이] console line 증가 (0) | 2021.12.04 |
무작위 스캔공격을 조심하자! (0) | 2021.03.07 |
언택트 시대, 스터디 운영방법! Discord 화면 공유!! (0) | 2020.11.29 |