DB

mysql 아키텍쳐[2] - 언두로그와 리두로그(읽기/쓰기 작업 성능 및 안정성의 핵심)

코딩공장공장장 2025. 2. 7. 16:24

언두 로그  :  고립성과 MVCC의 핵심


서로 다른 트랜잭션이 레코드를 변경하고 데이터를 읽었을 때, 두 트랜잭션의 작업은 서로 영향을 미치지 않기에 select를 했을 때 다른 트랜잭션이 변경한 내용은 나의 트랜잭션 내에서 보이지 않는다. 이는 실제 데이터 파일을 읽어들이는게 아니라 언두 로그의 데이터를 읽었기에 가능한 것이다. 트랜잭션이 시작되고 데이터를 변경하면 데이터 페이지에 변경사항을 기록하고 변경하지 않은 데이터는 언두로그에서 읽어 온다. 따라서 다른 트랜잭션이 데이터를 변경하더라도 읽어오는 공간이 언두로그이므로 영향을 미치지 않는 것이다.(고립성)

또한 트랜잭션 수행 중 롤백을 하기 위해 트랜잭션 수행 이전 데이터를 읽어 오는 공간도 언두 로그이다.

 

언두 로그는 여러 버전의 데이터가 존재한다.(Mutli Version Conccurency Control : 다중 버전 동시제어)
이 버전은 트랜잭션 id로 구분한다. 특정 트랜잭션이 커밋하여 변경한 데이터 상태를 버전별로 기록하고 있다.

모든 트랜잭션은 id로 순차적으로 증가하는 시퀀스 넘버를 갖고 트랜잭션은 자신의 id보다 작은 언두로그를 참조한다.

언두로그 없이 다른 변경 트랜잭션에 의해 영향을 받지 않으려면 락을 걸어야한다. 락을 걸어서 다른 트랜잭션이 변경되지 못하게 해야 영향을 받지 않게 된다. 허나 언두로그를 이용하면 락을 걸지 않고 수많은 트랜잭션이 같이 시작될 수 있다. 수많은 트랜잭션이 동시에 수행되더라도 언두로그를 통해 자신이 수행되기 이전 버전의 데이터를 참조하기 때문이다.(잠금 없는 일관된 읽기, 높은 동시성 처리)

 

예시를 통해 자세히 알아보자.

나의 트랜잭션 id가 10이고 9번까지 커밋되었다고 하자.10, 11, 12는 동시에 진행되는 기간이 존재하지만 데이터를 읽어올 때 모두 자신이 수행되기 전 커밋된 버전 언두로그 9를 사용한다.
따라서 각 트랜잭션에서 자신이 변경한 레코드를 제외한 나머지 조회결과는 모두 같다.

 

transaction 10 : |----------start------------------------------commit--------------|

transaction 11 : |-----------------------start----------------------------commit--------------|

transaction 12 : |-------------------------------start----------------------------------------commit--------------|

 

언두로그 삭제는 어느 시점에 이루어질까

모두 9를 사용하기에 9를 사용하는 12까지 커밋이 이루어져야 언두로그 9가 삭제된다.

 

만일 트랜잭션이 아래와 같이 사용되었다면 트랜잭션 11과 12에서 언두로그 10을 사용함으로써 언두 로그 9는 트랜잭션 10이 커밋되고 삭제된다.

transaction 10 : |----start------commit--------------|

transaction 11 : |-----------------------start----------------------------commit--------------|

transaction 12 : |-------------------------------start----------------------------------------commit--------------|

 

높은 동시성 처리를 위해 여러 버전의 데이터를 언두로그에 저장하고 디스크에 접근하지 않고 메모리에서 읽어들일 수 있는다면 읽기 성능은 향상될 것이다.
허나, 
사용하지 않는 언두로그를 메모리에 저장하고 있는 것은 좋지 못하다.

만일 트랜잭션이 길어지면 해당 언두로그를 사용하는 트랜잭션이 많아지고 다른 트랜잭션의 언두로그들도 모두 저장되야하므로 메모리에 부하를 줄 수 있다. 따라서 개발자의 실수나 잘못으로 트랜잭션 처리를 길게하는 상황은 좋지 못하다.

 

[참고1] 언두로그도 별도의 로그파일(디스크)에 기록함

비정상적 종료 이후 재시작 될 때, 로그파일을 통해 언두로그를 메모리에 복구하여 mvcc를 지원하여 높은 동시성 처리와 롤백을 지원함

 

[참고2] 5.7 이후 부터 언두로그 삭제 자동화

5.7 버전 부터 언두로그 삭제 기능이 자동화 되었다. 롤백되거나 오래전에 커밋되어 사용되지 않은 언두로그는 즉시 삭제되도록 지원되었다.
이전에는 수동으로 관리자가 관리했었다고 함

 

리두 로그 : 안정적인 쓰기 작업의 핵심


쓰기 작업이 어떻게 이루어지는 먼저 살펴보자.

  1. 변경 대상 데이터가 존재하는 페이지에서 데이터를 변경한다.
    버퍼풀에 현재 디스크와 동기화된 데이터들이 페이지 조각으로 저장되어있는데,

    변경사항이 없는 데이터 페이지를 클린 페이지, 변경사항이 존재하는 데이터 페이지를 더티 페이지라고 한다.

  2. 트랜잭션 커밋

  3. 리두 로그 버퍼에 변경사항을 기록한다.
    리두 로그 공간은 여러 영역으로 나누어져 있으며 순차적으로 증가하는 시퀀스 넘버(Log Sequence Nuber)를 갖는다.
    현재 진행중인 트랜잭션에 의해 변경사항이 기록되는 활성 리두 공간과 이미 변경사항이 기록된 비활성 리두 공간(장애시 재사용 가능)이 존재한다.

  4. 체크 포인트 이벤트를 발행한다.
    체크 포인트 이벤트 발생시 현재 활성 리두 로그 번호 중 가장 작은 값보다 작은 비활성 리두 로그들을 디스크의 로그파일에 기록한다.
    이때 리두 로그들은 각각 더티페이지들과 매핑되어있는데 더티 페이지들이 데이터 파일에 반영된다.
    이때 더티 페이지는 즉시 디스크에 반영되지 않을 수 있으며 버퍼 공간에 저장된 이후 write 스레드에 의해 한번에 일괄적으로 반영(플러시) 될 수 있다.

    * 리두 로그 파일은 고정된 크기의 여러개 파일로 구성되며 모든 파일이 가득차면 첫번째 로그 파일이 새로운 리두로그로 덮어씌워지는 순환구조를 갖춘 로그파일 관리 규칙을 갖는다.
[잠깐] 리두로그와 데이터 페이지로 이원화하여 사용하는 이유

디스크에 기록하는 작업은 리두 로그 부터 로그파일에 기록하고 이후에 더티 페이지를 데이터 파일에 기록한다.
mysql default 설정에서는 트랜잭션 커밋시 체크포인트 이벤트가 즉시 발생하여 리두 로그가 로그 파일에 즉시 기록된다. 리두 로그는 데이터 페이지보다 작은 자료 구조를 갖추고 있어 데이터 페이지에 비해  IO 작업의 부하가 적다. 데이터 페이지는 상대적으로 큰 자료구조로 인해 버퍼 공간에 적재되고 버퍼 공간이 차거나 일정시간이 지나면 한번에 일괄적으로 반영하도록 동작한다.
실패하는 건수가 있다면 리두로그가 먼저 반영되어있으면 리두로그를 통해 복구할 수 있다.

즉, 리두로그는 데이터베이스 지속성을 보장하는 안정장치이고,
데이터 페이지의 버퍼는 쓰기 작업의 IO부하를 줄이기 위한 성능 향상을 위한 공간이다.

 

[참고] 로그 버퍼

리두 로그가 저장되는 로그 버퍼는 버퍼라는 용어 처럼 버퍼 공간을 제공하지만

리두 로그는 쓰기 작업의 안정성을 위해 트랜잭션 커밋시 즉시 로그파일에 기록하는 것이 중요하다.

mysql default 설정에서 이를 지원하니 이를 변경하는 작업은 추천하지 않는다.
(innodb_flush_log_at_trx_commit 옵션)

 

반응형