개발놀이터

데이터베이스 동시성 제어 본문

CS 지식/데이터베이스

데이터베이스 동시성 제어

마늘냄새폴폴 2024. 1. 20. 22:31

데이터베이스에는 동시성 문제를 해결하기 위해 시스템 내부적으로 다양한 방법들을 제공해줍니다. 오늘은 데이터베이스에서 사용할 수 있는 다양한 동시성 제어에 대해서 알아보도록 하겠습니다. 

 

 

동시성 제어

이번 포스팅에서 중점적으로 다룰 동시성 제어 방식은

 

  • 타임스탬프 기반 프로토콜 (Timestamp-Based Protocol)
  • 낙관적 동시성 제어 (Optimistic Concurrency Control)

이렇게 두가지를 보도록 하겠습니다. 

 

사실 저 두개 말고도 락 기반 프로토콜과 MVCC가 있는데 이 두가지는 이미 포스팅으로 다뤘기 때문에 이번엔 링크만 남기고 넘어가도록 하겠습니다. 

 

락 기반 프로토콜

https://coding-review.tistory.com/302

 

Phantom Read 부정합문제 해결방안 In Mysql

이번 포스팅에서는 Mysql에서 Phantom Read 부정합 문제를 어떻게 해결하고 있는지에 대해서 알아보도록 하겠습니다. 굳이 Mysql에서 라고 글을 쓴 이유는 공식문서를 여러개 찾아보던 중에 PostgreSQL과

coding-review.tistory.com

 

MVCC

https://coding-review.tistory.com/301

 

MVCC (Multiversion Concurrency Control)

오늘 포스팅에서는 흔히 Read Committed 격리 수준에서 생기는 부정합 문제인 Non-Repeatable Read 문제를 해결하기 위해 사용하는 방법이라고 알려져있는 MVCC에 대해서 알아보도록 하겠습니다. 이 포스

coding-review.tistory.com

 

그럼 본격적으로 시작해보도록 하겠습니다. 

 

타임스탬프 기반 프로토콜

타임스탬프 기반 프로토콜은 여러개의 트랜잭션들이 동시다발적으로 발생하는 것에 대해서 일관성을 유지하기 위해 데이터베이스 시스템 내부에서 사용되는 방법입니다. 

 

이 방법은 트랜잭션이 실행해야 하는 순서를 결정하기위해 각각의 트랜잭션이 유니크한 타임스탬프를 할당하는 것에 의존합니다. 

 

이는 직렬성을 보장하는데 이 말은 트랜잭션이 각각의 타임스탬프 순서대로 실행되는 것을 의미합니다. 

 

쉽게 말해 시간을 체크한다는 것입니다. 

 

타임스탬프 기반 프로토콜의 특징

  • 유니크한 식별자 : 각각의 트랜잭션은 트랜잭션이 시작할 때 유니크한 타임스탬프를 할당받습니다. 이 타임스탬프는 트랜잭션의 실행을 위해 사용됩니다. 
  • 실행 순서 : 트랜잭션의 실행 순서는 각각의 트랜잭션의 타임스탬프에 기반하여 결정됩니다. 더 빠른 타임스탬프를 가지고 있는 트랜잭션은 늦은 타임스탬프를 가지고 있는 트랜잭션보다 더 높은 우선순위를 부여받습니다. 

 

기본적인 동작 방식

  1. Read and Write Timestamps : 데이터베이스 안에 있는 각각의 데이터들은 읽기 타임스탬프와 쓰기 타임스탬프를 가지고 있습니다. 이는 트랜잭션이 읽기 작업을 수행하거나 쓰기 작업을 수행하면 마킹이 되는 방식입니다. 

    즉, 트랜잭션이 어떤 데이터에 접근해서 읽기 혹은 쓰기 작업을 수행하면 "지나갑니다~" 하고 시간을 적어 놓는 것입니다. 
  2. Read Operation : 트랜잭션이 데이터를 읽고 싶을 때 시스템은 데이터에 적혀있는 쓰기 타임스탬프와 트랜잭션의 타임스탬프를 비교합니다. 만약 트랜잭션의 타임스탬프가 데이터의 쓰기 타임스탬프보다 오래 되었으면 읽기작업이 거부당합니다. 

    왜냐하면 트랜잭션이 생성된 시기보다 쓰기 작업이 일어난 시기가 더 늦기 때문입니다. 이 상황은 read-write 충돌이라고 부릅니다. 
  3. Write Operation : 트랜잭션이 데이터에 쓰기 작업을 하고 싶을 때 시스템은 데이터에 적혀있는 읽기 타임스탬프와 쓰기 타임스탬프를 트랜잭션의 타임스탬프와 비교합니다. 

    만약 트랜잭션의 타임스탬프가 데이터에 적혀있는 읽기, 쓰기 타임스탬프보다 늦게 생성된 것이라면 쓰기 작업이 취소됩니다. 이유는 위의 Read Operation에서와 같은 이유입니다. 이 상황을 write-read 충돌이라고 부릅니다. 

 

타임스탬프 기반 프로토콜의 장점

타임스탬프 기반 프로토콜은 락킹 매커니즘이 아니기 때문에 데드락의 위험성이 없습니다. 그리고 트랜잭션이 각각의 타임스탬프대로 동작하는 것을 보장하며 직관적이라 구현하기 쉽다는 장점이 있습니다. 

 

타임스탬프 기반 프로토콜의 단점

만약 어떤 트랜잭션의 타임스탬프가 읽기 혹은 쓰기 작업을 원하는데 데이터에 적혀있는 읽기, 쓰기 타임스탬프보다 오래된 것이라 작업이 중지되었다면 또 다른 트랜잭션이 와서 데이터의 타임스탬프를 변경해줄 때까지 해당 데이터에 접근조차 할 수 없습니다. 

 

이는 기아상태를 유발하기 딱 좋은 상황입니다. 또한, 타임스탬프를 유지하고 타임스탬프 기반으로 동작하게 하는 것은 추가적인 오버헤드를 유발할 수도 있습니다. 

 

타임스탬프 기반 프로토콜의 사용처

타임스탬프 기반의 동시성 제어는 특히 각각 노드들에 락을 거는 것이 힘든 분산 시스템에 주로 사용됩니다. 또한, 데이터 contention을 가진 애플리케이션에도 적합합니다. 

 

 

낙관적 동시성 제어

Optimistic Concurrency Control 줄여서 OCC는 데이터에 락을 걸어서 동시성 제어를 하는 것이 아닙니다. 

 

기존 데이터베이스에서 실제로 업데이트 되는 것이 아닌 가상의 데이터베이스 공간에서 데이터의 버전을 확인하여 충돌 여부를 확인하고 롤백하는 시스템입니다. 

 

사실 OCC는 포스팅에서 한번 다룬 적이 있습니다. JPA의 낙관적 락과 비관적 락을 얘기할 때 등장했었죠. 하지만 이는 구현하는 방법이지 이론적인 방법은 아니었습니다. 이번 기회에 이론적으로 잡고 들어가기 위해서 다시 한번 포스팅 하는 것입니다. 

 

아래의 링크에 짧게지만 설명이 나와있고 구현방법도 나와있으니 아래의 링크를 확인해주세요!

https://coding-review.tistory.com/421

 

JPA의 동시성 컨트롤 (낙관적 락, 비관적 락)

이번 포스팅에선 JPA가 어떻게 동시성 문제를 컨트롤하는지에 대해서 포스팅해보겠습니다. 개인적으로 동시성 문제는 애플리케이션에서 일어날 수 있는 가장 최악의 버그라고 생각합니다. 그

coding-review.tistory.com

 

OCC의 순서

  1. Read Phase (Begin Transaction) : 이 페이즈에서 트랜잭션은 데이터베이스에 실제로 업데이트 되지 않는 가상의 데이터베이스에서 데이터를 확인합니다. 그리고 가상의 데이터베이스를 관리하면서 읽기 작업을 수행합니다. 
  2. Validation Phase (Check before Commit) : 이 단계에서 시스템은 다른 트랜잭션들이 현재 트랜잭션에 의해 데이터가 업데이트 되었는지 체크합니다. 

    이때 버저닝을 통해 데이터의 업데이트를 체크합니다. 만약 트랜잭션이 데이터를 변경하였다면 충돌이 일어나게 됩니다. 
  3. Write Phase (Commit if Safe) : 만약 검증 단계에서 충돌이 없다고 판단하면 트랜잭션은 데이터베이스에 그대로 변경된 데이터를 적용시킵니다. 만약 충돌이 일어났다면 롤백이 일어나고 재시도를 거치게 됩니다. 

 

쉽게 설명해서 다음과 같은 데이터가 있다고 가정해봅시다. 

 

 

 

이 데이터베이스는 실제 데이터가 있는 데이터베이스입니다. 여기에 실제 데이터를 그대로 복사하고 가상의 데이터베이스를 만듭니다. 

 

 

다른 점은 옆에 version이 붙었다는 것입니다. 

 

A와 B 트랜잭션이 Tom의 나이를 변경한다고 가정해보겠습니다. 

 

먼저 A 트랜잭션이 접근했습니다. A 트랜잭션이 Tom의 나이를 24로 변경합니다. 

 

Update members m set m.age=24 where m.name = 'Tom' and m.version = 1

 

조건절에 모두 일치하는 데이터가 존재하네요. Tom의 나이가 24로 변경되면서 version을 1 올립니다. 

 

 

하지만 동시에 접근한 트랜잭션 B가 Tom의 나이를 25로 변경하려고 하네요.

 

Update members m set m.age = 25 where m.name = 'Tom' and m.version = 1

 

A와 B 모두 같은 시간에 트랜잭션에 접근했기 때문에 모두 버전이 1인 것을 검색합니다. 

 

하지만 이미 A가 지나간 뒤여서 version이 2가 되었습니다. 때문에 B의 저 쿼리는 충돌이 일어나게 됩니다. 

 

충돌이 일어나면 롤백이 일어나고 재시도하게 됩니다. 

 

OCC 장점

OCC는 락에 의존하는 동시성 제어가 아니기 때문에 데이터에 락을 거는 그 행위 자체를 피할 수 있습니다. 이렇게 락을 거는 행위 자체만 피하더라도 수많은 장점이 따라옵니다.

 

예를 들어서 락킹 시스템에서의 오버헤드도 피할 수 있고 락킹 시스템에서 가장 치명적인 데드락도 피할 수 있습니다. 

 

또한, 충돌하지만 않으면 뛰어난 성능을 보여주기도 하죠. 때문에, read-only인 트랜잭션 연산에 아주 적합합니다. 

 

OCC 단점

하지만 만약 충돌이 발생하는 것이 빈번한 경우라면 얘기가 달라집니다. 이 상황은 롤백이 많이 일어나게 되고 수많은 롤백은 수많은 재시도를 낳고 이는 네트워크 I/O가 폭발적으로 증가하여 병목현상이 발생할 수 있습니다. 

 

또한, OCC의 경우 3단계중 2단계인 검증 단계에서 검증에 필요한 네트워크 I/O가 있기 때문에 네트워크 오버헤드가 발생할 수 있습니다. 그럼 병목현상이 두배로 늘어나겠네요?

 

마지막으로 트랜잭션 내부적으로 연산이 많아서 트랜잭션을 오래 유지해야하는 경우에는 충돌의 위험이 높아질 수 있기 때문에 이런 경우엔 OCC를 사용하는 것이 올바른 방법이 아닙니다. 

 

OCC 정리

타임스탬프 기반 프로토콜과 마찬가지로 OCC는 각각의 노드에 락을 거는 것이 불가능에 가까운 분산 시스템에서 주로 사용됩니다. 

 

만약 분산 시스템에서 각각의 노드에 락을 걸게 되면 복잡성이 엄청나게 올라가고 매우 비효율적이기 때문에 주로 분산 시스템에선 락킹 매커니즘을 사용하지 않는 것입니다. 

 

 

정리

이렇게 데이터베이스 동시성 제어를 위한 타임스탬프 기반 프로토콜과 낙관적 동시성 제어를 알아봤습니다. 

 

제가 알고 있던 동시성 제어는 MVCC와 Shared Lock과 Exclusive Lock을 이용한 락킹 매커니즘밖에 몰랐는데 이번 기회에 두가지 더 알게 되어서 굉장히 뿌듯합니다. 

 

주로 MVCC와 흔히 Lock-Based Protocol이라고 부르는 Shared Lock과 Exclusive Lock은 RDBMS에 특화된 동시성 제어 방식입니다. 

 

제가 주력으로 사용하던 데이터베이스가 MySQL이었기 때문에 RDBMS에 특화된 동시성 제어에 관심이 있었지만 이제 NoSQL도 제 기술스택으로 추가되었기 때문에 분산 시스템에 적합한 타임스탬프 기반 프로토콜과 낙관적 동시성 제어를 알게 되어서 다행입니다. 

 

여기까지 긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요~

 

 

출처

Chat GPT 4.0 - Open AI