카테고리 없음

[MySQL] MVCC 와 언두 로그(Undo Log)의 개념과 관계 이해하기

승갱이 2025. 5. 20. 03:40
반응형

개요

트랜잭션 격리수준을 공부하면 꼭 나오는 MVCC 에 대해 확실히 이해해보자.

 

MVCC란? 

Multi Version Concurrency Control 의 약자로, 직역하면 멀티 버전 동시 제어를 말한다. 단순히 MySQL에만 한정된 기술이 아닌, DB에서 데이터를 관리하는 방법론 중 하나이다. 그럼 DB 입장에서 여러 버전의 데이터를 관리한다는 건 무슨 말일까? 그리고 여러 데이터를 관리해야하는 이유는 뭘까? 그건 바로 여러 트랜잭션들! 동시에! 일관된 읽기를 제공해야하기 때문이다.

 

일관된 읽기? 📕

일관된 읽기란 뭘 말하는 걸까? 수준이 가장 낮은 트랜잭션 격리수준인 READ UNCOMMITED를 생각해보자. 트랜잭션 격리 수준 중 하나로 트랜잭션이 어떤 레코드의 값을 수정하면 굳이 커밋하지 않아도 다른 트랜잭션에서 수정된 값을 볼 수 있다. 당연하게도 해당 트랜잭션이 롤백될 경우 데이터 정합성 문제가 발생하는 매우 낮은 수준의, 일반적으로는 사용하지 않는 격리수준임을 알것이다.

 

 이 정합성 문제를 해결하기 위해서는 레코드를 수정한 트랜잭션이 끝날때까지 다른 트랜잭션이 이 레코드의 데이터에 접근하지 못하도록 잠궈 (Exclusive Lock) 버리는 것이다. 읽기든, 쓰기든말이다. 그럼 데이터 정합성 이슈는 해결되지만, 동시에 접근하지 못하는 이슈가 발생한다.

 

여러 트랜잭션들이? 동시에 ? 🙌

 방금 말했던 동시성 이슈는 여러 트랜잭션이 동시에 수정/조회를 할때 발생한다. 잠금을 갖는 트랜잭션이 끝나야 비로소 접근 가능하기 때문이다. 이러한 동시성 이슈로 인해 트랜잭션의 처리속도는 전체적으로 저하된다. 결국 잠금 대신 다른 방법을 사용해야했다. 원본 데이터와 별개로 다른 버전의 데이터를 관리하는, 즉, 멀티 버전의 데이터를 제어할 수 있는 방법을 사용해 동시성 이슈를 해결하는 것이다.

 데이터들이 변경될때마다 스냅샷처럼 버전을 관리한다면, 상황에 따라 수정 전 데이터를 줄 수 있을것이다. 예를 들어 레코드 데이터를 수정중인 트랜잭션이 Commit 되지 않았을 때, 조회 트랜잭션이 발생한다면, "수정중인 데이터가 롤백될 수 있으니 끝날때까지 기다리라고 하자" 가 아닌 "수정 전 데이터를 주자"와 같이 컨트롤이 가능해진다.

 

바로 이것이 Multi Version Concurrency Control. MVCC이다. 그리고 MVCC와 함께 나오는 용어가 하나 있다. 바로 언두 로그이다. 언두 로그에 대해 먼저 이해하고, MVCC와 어떤 관계를 갖는지도 연결해보자.

 

MVCC 공부하면 언두 로그도 꼭 나오던데...🤔

 MVCC는 멀티 버전의 데이터를 관리하는 방식의 동시성 제어 전략이자 트랜잭션 격리 방법론 중 하나이다. 그럼 MySQL에서는 무엇을 통해 데이터를 다중 버전으로 관리하는 걸까? 그 무엇이 바로 언두 로그이다. MySQL에서는 언두 로그를 활용해 다중 버전의 데이터를 관리하는 것이다.

 

 

언두 로그 개념 이해하기

 

먼저 Undo 라는 단어의 사전적 의미는 다음과 같다. 

Undo
어떤 행위를 되돌리거나 취소하는 것을 의미

 

 언두 로그는 트랜잭션이 삽입, 수정, 삭제한 행위를 되돌리기 위해 사용하는 로그를 말한다. 트랜잭션으로부터 삽입, 수정, 삭제 행위가 발생하면, 이에 대한 로그를 언두 로그에 저장해놓고, 트랜잭션이 실패했을 때, 이 로그를 참조하여 원래의 데이터로 되돌려 놓는것이다. 예제를 통해 이해해보자.

 

먼저 다음과 같은 테이블에 한 건의 레코드를 INSERT 한 후 UPDATE 했을 때 언두 로그가 어떻게 활용되는지를 알아보자.

CREATE TABLE member{
	m_id INT NOT NULL,
    m_name VARCHAR(20) NOT NULL,
    m_area VARCHAR(100) NOT NULL,
    PRIMARY KEY (m_id),
    INDEX ix_area (m_area)
};

 

1) INSERT 문 실행 및 커밋

INSERT INTO member (m_id, m_name, m_area) VALUES (12, '홍길동', '서울');
COMMIT;

 

INSERT 문 실행후 커밋되면 데이터베이스의 상태는 아래와 같이 바뀔것이다. InnoDB 버퍼 풀에 데이터를 선 적재한 후, 내부 알고리즘에 의해 디스크에도 데이터가 저장될것이다. 참고로 커밋을 하고 언두 로그의 데이터도 삭제된 상황을 가정한거라 언두 로그에 데이터가 없는 것이다.

 

 

* 혹시 Insert 쿼리는 커밋을 안해도 언두 로그에 데이터가 없는걸까?

 언두 로그는 트랜잭션이 데이터를 변경하기 전 상태를 저장해둔다고 했다. INSERT 하기 전에는 해당 데이터가 없는 상태이므로, 언두 로그에도 데이터가 없을 것이라 생각할 수 있지만, 실제로는 INSERT 로그가 들어간다. INSERT 한 데이터가 기록되지 않는다면 트랜잭션을 롤백했을 때, "어떤 데이터를 INSERT 했었는지"를 찾을 수 없기 때문이다. 

 

2) UPDATE 문 실행 후 미 커밋상태

UPDATE member SET m_area = '영암' WHERE m_id = 12;

 

새로운 트랜잭션에서 UPDATE 쿼리가 실행되면 커밋 실행 여부와 관계 없이 InnoDB의 버퍼 풀은 새로운 값인 '경기'로 업데이트된다. 마찬가지로 디스크의 데이터 파일은 InnoDB 내부 알고리즘에 의해 추후 저장될것이다. 디스크에 저장된 m_area는 '경기'가 될수도, '서울'이 될수도 있으므로 '?' 로 기재하였다. 마지막으로 언두 로그에는 수정 전 데이터에 대한 로그가 들어갈것이다.

 

3) Commit 전 다른 트랜잭션의 SELECT

그럼 이렇게 언두 로그에 데이터가 있을 때 다른, 혹은 여러 트랜잭션에서 해당 로우에 대한 SELECT 쿼리를 날리면 어떻게될까?

 

SELECT * FROM member WHERE m_id = 12;

 

이 질문의 답은 MySQL 서버의 격리 수준에 따라 다르다.

 격리 수준이 READ_UNCOMMITTED인 경우 InnoDB 버퍼 풀이 가진 데이터를 읽으므로 '경기' 로 조회될것이다.

 READ_COMMITTED나 그 이상의 격리 수준인 REPEATABLE_READ, SERIALIZABLE 인 경우에는 아직 커밋되지 않았기 때문에 언두 영역의 데이터를 참조해 조회한다. 

 

* 갑자기 언두 영역 데이터를 왜 조회해?

커밋되지 않는 데이터를 다른 트랜잭션에서 조회해버린다면 처음 말했던 정합성 문제가 발생하게 된다. 이 문제를 해결하려면 수정 전 데이터가 조회되도록 하면 된다. 그럼 여기서 지금까지의 내용을 다시 짚어보자. 정합성 문제를 해결하기 위해 Lock 을 도입했다가, Lock의 동시 접근 이슈가 있어 MVCC를 통해 멀티 버전의 데이터를 관리하여 조회되는 데이터를 제어할 수 있다고 했다. 

 

 언두 로그를 보자. Lock을 걸지 않으니 여러 트랜잭션에서 언두 로그에 동시 접근 가능하다. 수정 트랜잭션이 끝나지 않은 상황에서 해당 레코드에 조회 트랜잭션이 발생할 때 언두 로그를 통해 수정 전 데이터를 보여지도록 하고있다. 즉, MySQL은 언두 로그 기반의 MVCC 방식을 채택하고 있는 것이다. MVCC와 언두 로그는 뗄레야 뗄수 없는 관계인 것이다.

 

 언두 로그를 통해 하나의 레코드(회원 번호가 12인 레코드)에 대해 2개의 버전을 유지하고, 필요에 따라 보여지는 데이터를 제어하고 있다. 위 예제는 한 개의 데이터만 가지고 설명했지만 관리해야 하는 예전 버전의 데이터는 무한히 많아질 수 있다. 즉, 트랜잭션이 길어지면 언두 로그에서 관리하는 예전 데이터가 오랫동안 관리되어야 하며, 그만큼 메모리를 많이 차지하게 될것이다.

 

4) Commit

 UPDATE 트랜잭션이 COMMIT 되면 InnoDB는 이상의 변경 작업 없이 지금의 상태를 영구적인 데이터로 만든다. 하지만 롤백될 경우 언두 로그에 있는 데이터를 InnoDB 버퍼 풀로 다시 복구하고, 언두 로그의 내용을 삭제해버린다.

 참고로 트랜잭션이 끝난다고 언두 영역의 백업 데이터가 항상 바로 삭제되는 것은 아니다. 이 언두 로그을 필요로 하는 트랜잭션이 더는 없을 때 비로소 삭제된다.

 

언두 로그가 삭제될 수 있는 조건?

다음 두 조건을 모두 만족하면 백그라운드에서 실행되는 Purge Thread에 의해 언두 로그가 제거된다.

 

1) 변경을 발생시킨 트랜잭션이 끝났을 때

 해당 언두 로그를 생성한 트랜잭션이 커밋 또는 롤백이 되어야한다. 너무 당연한 얘기이다. 트랜잭션이 끝나지도 않았는데 언두 로그에 데이터가 날아간다면, 롤백이 불가능해진다. 또한 트랜잭션이 끝나지 않은 상태에서 다른 트랜잭션이 조회 쿼리를 날린다면 언두 로그를 참조하여 정합성을 지켜줘야한다.

 

2) 해당 언두 로그를 참조하는 모든 트랜잭션이 종료되었을 때

 변경을 발생시킨 트랜잭션이 끝났다고 하더라도, 이미 다른 트랜잭션이 언두 로그를 참조하고 있었다면, 그리고 언두 로그를 삭제할 수 없다. Repeatable Read 격리 수준을 위해서도 삭제시키면 안된다. 언두 로그를 어떻게, 얼마나 활용하냐에 따라 Read Commited와 Repeatable Read가 나뉠정도로 매우 중요한 로그이다.

Read Commited 와 MVCC

 

Read Commited 단계에서의 핵심은 커밋된 데이터만을 조회하는 것이다. 커밋된 데이터를 조회하는 방법은 "최신의 커밋 데이터"를 읽어오는 것이다. 그런데 언두 로그 기반의 MVCC 생각하면 수정 전 데이터는 관리하지만 최신의 커밋 데이터는 관리하지 않는다. 그럼 최신의 커밋 데이터는 뭘 말하는걸까? 이건 바로 버퍼풀 + MVCC 를 함께 활용해 얻는 최신의 커밋 데이터를 말한다. 만약 MVCC 만을 사용한다면 가장 마지막으로 커밋된, 즉 언두 로그에 존재하는 수정 전 데이터를 로드할 수도 있지만, 로드하지 못할 수도 있다.

 

 

 

아까 m_id가 12인 레코드의 m_area의 값을 '서울' 에서 '경기'로 변경했던 걸 다시보자. 커밋하지 않은 상태라면 위처럼 언두 로그에는 '서울'이, 버퍼 풀에는 '경기'가 들어갈 것이다. 이때 다른 트랜잭션이 m_id = 12 인 데이터를 조회한다면 Read Commited 레벨에서는 언두 로그를 참조하여 '서울'이라는 데이터가 조회될것이다.

 

여기서 중요한 점은 언두 로그를 참조하기 전에, '조회된 데이터가 마지막으로 커밋된 데이터가 맞는지'를 체크해야한다는 것이다. 언두 로그는 커밋과 동시에 삭제되지 않는다고 했다. 즉, '경기'로 수정시킨 트랜잭션이 커밋됐다 하더라도, 언두 로그에 '서울'이라는 데이터가 남아있을 수 있다. Read Commited 격리 수준이 아무런 체크 과정 없이 언두로그의 데이터를 읽는 것이었다면 '서울' 로 조회될것이고, 커밋된 데이터가 조회되는 Read Commited 의 원칙에 어긋난다.

 

마지막으로 커밋된 데이터가 맞는지?

이를 이해하기 위해서는 레코드에 저장된 몇가지 메타 데이터를 알아야 한다. 레코드에는 단순 데이터 뿐 아니라, 트랜잭션 정보를 포함한 여러 메타 데이터들이 저장되어 있다.

 

* DB_TRX_ID

 이 레코드를 최종적으로 수정한 트랜잭션의 ID

 trx_sys라는 시스템 정보엔 트랜잭션 ID에 대한 커밋 여부를 확인할 수 있다.

 

* DB_ROLL_PTR

 이 레코드가 수정되기 전의 Undo Log를 가리키는 포인터

 

이제 최근 마지막으로 커밋된 데이터를 체크하는 방법을 알아보자.

 

1) 현재 레코드 확인 (버퍼 풀)

 트랜잭션이 읽으려는 레코드를 버퍼 풀에서 먼저 확인한 후, DB_TRX_ID가 커밋된 트랜잭션인지 확인한다.

 

2) 트랜잭션 커밋 여부 확인

 trx_sys를 참고하여 해당 트랜잭션의 커밋 여부를 확인한다.

 

3) 커밋 여부에 따른 읽기

 커밋된 트랜잭션이라면 버퍼 풀에 저장된 값을 그대로 읽고, 그렇지 않다면 DB_ROLL_PTR 를 통해 Undo Log로 접근 후 가장 마지막으로 커밋된 데이터인 수정 전 데이터를 읽어온다.

 

Repeatable Read 와 MVCC

 

그럼 Repeatable Read 에서는 어떨까? 가장 마지막으로 커밋된 데이터를 읽어오는게 아니라, 현재 트랜잭션이 시작되기 이전 시점을 기준으로 가장 마지막으로 커밋된 데이터를 읽어와야한다. 즉, 아래와 같은 상황에서 수정된 트랜잭션이 커밋됐다 하더라도, 조회 트랜잭션 이후에 발생한 커밋이므로, 이를 무시하고, 언두로그를 참조해 이전 커밋된 버전을 찾아나가게 된다.

 

정리

MVCC란 동시성을 보장하는 트랜잭션 격리 방법론중 하나로, MySQL에서는 언두 로그 기반의 MVCC를 사용한다.

단순히 트랜잭션이 끝난다고 해서 언두 로그가 삭제되지 않는다.

언두 로그를 어떻게 활용하느냐에 따라 트랜잭션의 격리 수준이 달라진다.

 

반응형