본문 바로가기

DB/MySQL

InnoDB Strage Engine

MVCC (Multi Version Concurrency Control)

하나의 레코드에 대해 여러 개의 버전이 동시에 관리된다.

격리 수준 (Isolation level)이 READ_COMMITTED인 MySQL 서버에서 InnoDB 스토리지 엔진을 사용하는 테이블의 변경을 어떻게 처리하는지 살펴보자.

UPDATE 문장이 실행되면 커밋 실행 여부와 관계없이 InnoDB의 버퍼 풀은 새로운 값으로 업데이트된다.

아직 COMMIT이나 ROLLBACK이 되지 않은 상태에서 다른 사용자가 다음 같은 쿼리로 작업 중인 레코드를 조회하면 어디에 있는 데이터를 조회할까?

이는 격리 수준에 따라 다르다. READ_UNCOMMITTED인 경우에는 변경된 상태의 데이터를 반환한다.

READ_COMMITTED 이상의 격리 수준에서는 언두 영역의 데이터를 반환한다.

언두 영역은 이 영역을 필요로하는 트랜잭션이 없을 때 삭제된다.

Non-Locking Consistent Read

InnoDB 스토리지 엔진은 MVCC 기술을 이용해 잠금을 걸지 않고 읽기 작업을 수행한다.

격리 수준이 REAPEATABLE_READ 이하일 경우 SELECT 작업은 다른 트랜잭션의 변경 작업과 관계 없이 잠금을 대기하지 않고 바로 실행된다.

오랜 시간 동안 활성 상태인 트랜잭션으로 인해 MySQL 서버가 느려지거나 문제가 발생할 때가 있는데, 바로 일관된 읽기를 위해 언두 로그를 삭제하지 못하고 계속 유지해야 하기 때문이다. 따라서 트랜잭션이 시작됐다면 가능한 한 빨리 트랜잭션을 완료해야 한다.

InnoDB 버퍼 풀

InnoDB 스토리지 엔진에서 가장 핵심적인 부분이다.

디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간이다.

쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 버퍼 역할도 한다.

(데이터를 변경하는 쿼리는 데이터 파일의 이곳 저곳에 위치한 레코드를 변경하기 때문에 랜덤한 디스크 작업을 발생시킨다. 버퍼 풀이 이러한 변경된 데이터를 모아서 처리해 랜덤한 디스크 작업의 횟수를 줄인다.)

버퍼 풀의 구조

버퍼 풀의 페이지 크기 조각을 관리하기 위해 InnoDB 스토리지 엔진은

  • LRU 리스트
  • Flush 리스트
  • Free 리스트

의 자료구조를 관리한다.

Free List

InnoDB 버퍼 풀에서 실제 사용자 데이터로 채워지지 않는 비어 있는 페이지들의 목록이며, 사용자의 쿼리가 새롭게 디스크의 데이터 페이지를 읽어와야 하는 경우 사용된다.

LRU List

InnoDB 스토리지 엔진에서 데이터를 찾는 과정

  1. 필요한 레코드가 버퍼 풀에 있는지 확인
    Adaptive Hash Index를 사용해 페이지 검색
    B-Tree를 이용해 버퍼 풀에서 페이지 검색
    버퍼 풀에 데이터 페이지가 있다면 해당 페이지의 포인터를 MRU 방향으로 승급
  2. 디스크에서 필요한 데이터 페이지를 버퍼 풀에 적재하고, 적재된 페이지의 포인터를 LRU 헤더에 추가

버퍼 풀에 있는 페이지는 사용자 쿼리가 얼마나 최근에 접근했었는지에 따라 Age가 부여된다. 오랫동안 사용되지 않으면 Aging되어 버퍼 풀에서 제거된다. 버퍼 풀의 데이터 페이지가 사용되면 나이가 초기화되고 MRU 헤더 부분으로 옮겨진다.

데이터가 자주 접근되면 페이지의 인덱스 키를 Adaptive Hash 인덱스에 추가한다.

Flush List

디스크로 동기화되지 않은 데이터를 가진 페이지 (더티 페이지)의 변경 시점 기준의 페이지 목록을 관리한다.

데이터가 변경되면 InnoDB는 변경 내용을 리두 로그에 기록하고 버퍼 풀의 데이터 페이지에 변경 내용을 반영한다.

InnoDB 스토리지 엔진은 체크포인트를 발생시켜 디스크의 리두 로그와 데이터 페이지의 상태를 동기화한다.

버퍼 풀과 리두 로그

InnoDB 버퍼 풀은 데이터베이스 서버의 성능 향상을 위해 데이터 캐시와 쓰기 버퍼링이라는 두 가지 용도가 있다.

버퍼 풀의 메모리 공간을 늘리는 것은 캐시 기능을 향상시킨다.

리두 로그는 쓰기 버퍼링 기능을 향상시킨다.

버퍼 풀의 더티 페이지는 특정 리두 로그 엔트리와 관계를 가지고, 체크포인트가 발생하면 체크포인트 LSN보다 작은 리두 로그 엔트리와 관련된 더티 페이지는 모두 디스크로 동기화된다.

언두 로그

InnoDB 스토리지 엔진은 트랜잭션과 격리 수준을 보장하기 위해 DML로 변경되기 이전 버전의 데이터를 별도로 백업한다. 이렇게 백업된 데이터를 언두로그라고 한다.

  • 트랜잭션 보장
  • 트랜잭션이 롤백되면 트랜잭션 도중 변경된 데이터를 변경 전 데이터로 복구해야 하는데, 이 때 언두 로그에 백업해둔 이전 버전의 데이터를 이용해 복구한다.
  • 격리 수준 보장
  • 특정 커넥션에서 데이터를 변경하는 도중에 다른 커넥션에서 데이터를 조회하면 트랜잭션 격리 수준에 맞게 변경중인 레코드를 읽지 않고 언두 로그에 백업해둔 데이터를 읽어서 반환하기도 한다.

체인지 버퍼

RDBMS에서 레코드가 INSERT되거나 UPDATE될 때는 데이터 파일을 변경하는 작업뿐 아니라 해당 테이블에 포함된 인덱스를 업데이트하는 작업도 필요하다. 그런데 인덱스를 업데이트 하는 작업은 랜덤하게 디스크를 읽는 작업이므로 테이블에 인덱스가 많으면 많은 자원을 소모하게 된다. 따라서 InnoDB는 변경해야 할 인덱스 페이지가 버퍼 풀에 있으면 바로 업데이트를 수행하지만 그렇지 않고 디스크로부터 읽어와서 업데이트 해야 한다면 임시 공간에 저장해두고 사용자에게 결과를 반환하는 형태로 성능을 향상시키게 되는데, 이때 사용하는 임시 메모리 공간을 체인지 버퍼라고 한다.

사용자에게 결과를 전달하기 전에 반드시 중복 여부를 체크해야 하는 유니크 인덱스는 체인지 버퍼를 사용할 수 없다.

체인지 버퍼에 임시로 저장된 인덱스 레코드 조각은 이후 백그라운드 스레드에 의해 병합되는데, 이를 체인지 버퍼 머지 스레도라고 한다.

MySQL 5.5 이전 버전까지는 INSERT에 대해서만 버퍼링이 가능했는데, MySQL 8.0에서는 INSERT, DELETE, UPDATE로 키를 추가하거나 삭제하는 작업에 대해서도 버퍼링이 가능하도록 개선되었다.

리두 로그 및 로그 버퍼

리두 로그는 트랜잭션의 Durable에 해당하는 영속성과 가장 밀접하게 연관돼 있다. 하드웨어나 소프트웨어 등 여러가지 문제점으로 인해 MySQL 서버가 비정상적으로 종료됐을 때 데이터 파일에 기록되지 못한 데이터를 잃지 않게 해주는 안전장치다.

MySQL 서버를 포함한 대부분의 데이터베이스 서버는 데이터 변경 내용을 로그로 먼저 기록한다. 거의 모든 DBMS에서 데이터 파일은 쓰기보다 읽기 성능을 고려한 자료구조를 가지고 있기 때문에 데이터 파일 쓰기는 디스크의 랜덤 액세스가 필요하다. 이로 인한 성능 저하를 막기 위해 데이터베이스 서버는 쓰기비용이 낮은 리두 로그를 가지고 있으며 비정상 종료가 발생하면 리두 로그로 데이터 파일을 서버가 종료되기 직전의 상태로 복구한다.

어댑티브 해시 인덱스

어댑티브 해시 인덱스는 B-Tree의 검색 시간을 줄여주기 위해 InnoDB 스토리지 엔진이 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스이다.

해시 인덱스는 인덱스의 키 값과 인덱스 키 값이 저장된 데이터 페이지 주소의 쌍으로 관리되는데, 인덱스 키 값은 B-Tree 인덱스의 고유 번호와 B-Tree 인덱스의 키 값 조합으로 생성된다.

데이터 페이지 주소는 버퍼 풀에 로딩된 페이지의 주소를 의미한다. 어댑팁즈 해시 인덱스는 버퍼 풀에 올려진 데이터 페이지에 대해서만 관리되고, 버퍼 풀에서 해당 데이터 페이지가 없어지면 어댑티브 해시 인덱스에서도 해당 페이지의 정보는 사라진다.

어댑티브 해시 인덱스가 성능 향상에 도움이 되는 경우

  • 디스크 읽기가 많지 않은 경우
  • 동등 조건 검색 (동등 비교와 IN 연산자) 이 많은 경우
  • 쿼리가 데이터 중에서 일부 데이터에만 집중되는 경우

어댑티브 해시 인덱스가 성능 향상에 도움이 되지 않는 경우

  • 디스크 읽기가 많은 경우
  • 특정 패턴의 쿼리가 많은 경우 (JOIN 이나 LIKE 패턴 검색)
  • 매우 큰 데이터를 가진 테이블의 레코드를 폭넓게 읽는 경우

'DB > MySQL' 카테고리의 다른 글

바이너리 로그의 복제 데이터 포맷  (1) 2023.11.09
[Real MySQL] 인덱스 (2)  (0) 2023.08.31
[Real MySQL] 트랜잭션과 잠금  (0) 2023.08.31
Isolation Levels  (0) 2023.07.29
[Real MySQL] 인덱스 (1)  (0) 2023.02.24