본문 바로가기
DB|SQL

데이터 베이스 MVCC(Multiversion Concurrency Control)

by Marco Backman 2024. 2. 5.

MVCC는 위키피디아에서 다음과 같이 정의된다.

 

Multiversion concurrency control (MCC or MVCC), is a non-locking concurrency control method commonly used by database management systems to provide concurrent access to the database and in programming languages to implement transactional memory.

 

다중 버전 동시성 제어 (MCC 또는 MVCC)는 데이터베이스 관리 시스템에서 일반적으로 사용되는 비락킹 동시성 제어 방법으로, 데이터베이스에 대한 동시 액세스를 제공하고 프로그래밍 언어에서 트랜잭션 메모리를 구현하는 데 사용된다.

 

여기서 중요한 키워드들이 보인다.

  • non-locking concurrency
  • concurrent access to the database
  • transactional memory

 

Locking concurrency - 대표적으로, Pessimistic locking의 경우 여러 개의 데이터 베이스 연결이 동시에 여러 개의 transaction을 통해 읽기와 쓰기를 동시에 수행하거나 같은 레코드의 같은 필드에 쓰기를 순차적으로 했지만 첫 번째로 수행한 transaction이 DBMS 내부에서 느리게 처리되어 제일 최신 데이터를 오래된 데이터로 덮어 씌울 때 문제가 발 생하는데 Pessimistic locking가 이 사태들을 막아줘서 데이터의 무결성과 정확성을 보장해 준다. 그렇지만 Pesimistic locking의 크나 큰 단점이 있는데, 동일 서비스의 데이터 접근이 매우 느려지고 같은 테이블을 사용하는 다른 애플리케이션 컨테이너들의 레코드 처리를 매우 느리게 만들기 때문에 성능의 문제점이 생긴다.

 

Non-locking concurrency - 만약 버전 컨트롤을 안한 non-locking의 경우 위에서 언급한 문제가 생긴다. 만약 정말로 성능 때문에 locking을 사용하기가 싫다면, DB의 쓰기와 읽기 프로세스는 다른 시간대에서 처리가 되어야 하며, 애플리케이션에서 짧은 시간에 반복적으로 같은 레코드의 데이터 수정을 하지 않아야 한다.

 

Concurrent access to DB

DBMS에서 동시에 다중 연결 기능을 지원한다. 이는 매우 기본적이면서도 필수적인 기능인데, 말 그래도 여러명의 DB 권한자가 동시에 데이터 베이스 데이터들을 참조를 할 수 있게끔 하는 것이다. 만약 이 기능이 없다면 오로지 한 사람, 혹은 하나의 애플리케이션 만이 접속을 하고 해당 연결이 접속을 끝낼 때까지 다른 사람이나 다른 서비스들이 데이터 베이스에 접근을 못하게 되는데, 얼마나 끔찍한 일인가? 마치 단일 코어 단일 스레드 컴퓨터를 보는것같다.

 

Transaction Memory

각 트랜잭션은 각각의 실행 절차대로 데이터베이스 프로세스를 다중 스레드에서 동시에 진행하는데, 이때 공유된 메모리를 순차적으로 접근(Atomic, synchronized)하게 하여 공유된 자원도 충돌이 일어나지 않게 하는 메모리 접근 방식이다. 이렇게 해서 DBMS는 locking을 실현시켜 같은 레코드의 예상치 못한 값의 변경이나 충돌을 방지할 수 있다. 

 

더보기

Relational DBMS의 Transational Isolation

ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON

https://en.wikipedia.org/wiki/Transactional_memory

 

그렇다면, 읽기와 쓰기를 동시에 하고 같은 레코드의 데이터 수정도 자주 일어나는 애플리케이션의 프로세스가 있을 때는 어떻게 할까? 성능도 챙기고 싶지만 데이터 무결성도 챙기고 싶다면 말이다. 이제 제목에서 언급된 MVCC의 locking-concurrency를 수정해서 Optimistic-locking을 구현하면 된다. Optmistic-locking 기법에서 본인은 두 가지 방법을 사용해 봤었다.

  • 버전 컨트롤 (multi-version concurrency control)
  • 시간 (timestamp-based concurrency control)

이 두 방법에는 장단점이 역시 존재한다.

 

multi-version concurrency control

버전 컨트롤 방식은 각 레코드마다 고유 버전 인식번호를 부여하는데 매 새로운 레코드마다 증가되는 버전을 값을 매긴다.

예를 들어, 다음과 같은 test 테이블이 있다고 가정하자

 

id name unique_id age version
1 Daniel 25123-aw32 46 4
2 Brian rwerw-1234 24 5
3 Wilson 512421-asdw 64 3

 

그렇다면 Brian의 정보는 다음 Query문구로 가져올 수 있다.

SELECT name, age FROM test where unique_id='rwerw-1234';

 

그러면 정상적으로 Brian, 24가 반환되는 것으로 예상한다.

 

 

그렇지만 문제점은 다음과 같은 상황에서 발생한다

더보기

 UPDATE anotherTable
       SET age=30
       WHERE unique_id='rwerw-1234'; -- 첫 번째 업데이트


 UPDATE test
       SET age=23
       WHERE unique_id='rwerw-1234'; -- 두 번째 최신 데이트

비슷한 타이밍에 서로 다른 transaction으로 이와 같이 보내졌다면 어떤 데이터가 올바른 데이터 일까? 만약 두 개의 요청이 동시에 도착하고 우연찮게 첫 번째 업데이트가 commit이 느리게 된다면 Brian의 나이는 23이 아니라 30이 되어버린다. 문제는 우리는 30이 맞는지, 23이 맞는지 모를뿐더러, 이러한 중첩의 문제가 생겼는지도 인지를 못한다. 그러면 데이터 자체적으로 올바른 값이 아닐 수 도 있기 때문에 문제가 생긴다.

 

그렇다면 우리는 데이터의 충돌을 인지하기를 바라는데 이때 Version을 사용하면 된다.

더보기

 BEGIN TRANSACTION;
 UPDATE anotherTable
       SET age=30,
           version = version + 1
       WHERE unique_id='rwerw-1234' and version='5';
 UPDATE test
       SET age=23,
           version = version + 1
       WHERE unique_id='rwerw-1234' and version='5';

그리면 둘 중 먼저 Commit을 하는 프로세스가 버전을 6으로 업데이트할 것이고 느리게 Update commit을 하는 프로세스는 version을 5를 탐색하지만 이미 6으로 바뀌어진 시점이기 때문에 실패를 할 것이다. 그렇다면 fetched row는 0을 반환할 테고 우리는 이 시점에서, "아! 나이를 23으로 바꾸려는 요청은 제대로 처리가 되지 않았구나"를 알 수 있게 된다. 그러면 비즈니스 로직에 따라 재처리를 시도할 수 있다.

 

timestamp-based concurrency control

그렇지만 우리는 Brian의 나이가 30이 정말로 최신 정보인지, 23이 맞는 정보인지 모른다. 그렇기 때문에 versioning도 한계가 있다. 이럴 때 사용하는 기법은 timestamp-based concurrency control이다.

 

테이블의 version column을 datetime으로 바꿔준다

id name unique_id age last_updated_time
1 Daniel 25123-aw32 46 2024-02-04 15:22:00
2 Brian rwerw-1234 24 2024-02-04 15:22:00
3 Wilson 512421-asdw 64 2024-02-04 15:22:00

 

그리고 다음과 같이 두 개의 query가 동시에 실행된다고 가정할 때,

BEGIN TRANSACTION;
 UPDATE anotherTable
       SET age=30,
           version = version + 1
       WHERE unique_id='rwerw-1234' and last_updated_time <='2024-02-04 15:25:00';
 UPDATE test
       SET age=23,
           version = version + 1
     WHERE unique_id='rwerw-1234' and last_updated_time <='2024-02-04 15:27:00';

 

만약 첫 번째 update가 성공적으로 되면 두 번째 업데이트도 성공적으로 수행이 된다. 왜냐하면 시간 상 따졌을때 나이 23이 제일 최신 정보이기 때문이다.

반대로, 두 번째 업데이트가 성공적으로 된다면 첫번째는 업데이트가 안된다. 이미 시간상 두번째 시간 대 보다 이르기 때문이다. 그렇지만 데이터 자체에는 문제가 없다 왜냐하면 두 번째가 최신 정보이기 때문에 업데이트 필요가 없기 때문이다.

 

 

이렇게 우리는 멀티스레딩 환경에서의 동시 업에이트의 데이터 무결성을 Optimistic-locking으로 보장했다. 실제로 널리 쓰이고 기본적인 방식이지만 모르는 사람들에게는 많은 도움이 되었으면 좋겠다.