InnoDB 버퍼 풀과 Double Write Buffer

Real MySQL 8.0 스터디-4

💡
이 글은 Real MySQL 8.0공식 문서를 읽고 개인적으로 정리한 내용입니다.

1. InnoDB 버퍼 풀

InnoDB 스토리지 엔진에서 가장 핵심적인 부분으로, 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간입니다. 쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 버퍼 역할도 같이 합니다.

1.1. 버퍼 풀의 크기 설정

MySQL 5.7 버전부터는 InnoDB 버퍼 풀의 크기를 동적으로 조절할 수 있게 개선됐으므로, 크기를 적절히 작은 값으로 설정한 다음 조금씩 상황을 봐 가면서 증가를 시키면 됩니다.

책에서 제시하는 권장안은 OS의 전체 메모리 공간이 8GB 미만이라면 50% 정도만 InnoDB 버퍼 풀로 설정하고, 그 이상이라면 50%에서 시작해 조금씩 올려가면서 최적점을 찾는 방법입니다.

InnoDB 버퍼 풀은 innodb_buffer_pool_size 시스템 변수로 크기를 설정할 수 있습니다.

-- // InnoDB 버퍼 풀 크기 확인
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';

다음 명령으로 버퍼 풀 메모리가 얼마나 쓰이고 있는지 확인할 수 있습니다.

-- // InnoDB 버퍼 풀 메모리 상태 확인
mysql> SHOW STATUS LIKE 'Innodb_buffer_pool%';

실제로 운영 시 버퍼 풀의 크기를 더 크게 변경하는 작업은 시스템 영향도가 크지 않지만, 버퍼 풀의 크기를 줄이는 작업은 서비스 영향도가 매우 큽니다. 가능하면 줄이는 작업은 하지 않는 것이 좋습니다. 또 버퍼 풀의 크기를 지나치게 크게 설정하면, 디스크를 가상 메모리로 쓰는 스와핑이 발생하여 서비스가 매우 느려질 수 있습니다. 적당한 크기는 전체 메모리 공간의 50%~80%입니다.

1.2. 버퍼 풀의 구조

InnoDB 스토리지 엔진은 버퍼 풀이라는 거대한 메모리 공간을 페이지 크기의 조각으로 쪼개어 InnoDB 스토리지 엔진이 데이터를 필요로 할 때 해당 데이터를 읽어서 각 조각에 저장합니다. 버퍼 풀의 페이지 크기 조각을 관리하기 위해 크게 3가지 자료 구조를 사용합니다.

  • LRU(Least Recently Used) 리스트: LRU와 MRU(Most Recently Used) 리스트가 결합 결합된 형태. 디스크로부터 한 번 읽어온 페이지를 최대한 오랫동안 InnoDB 버퍼 풀의 메모리에 유지해서 디스크 읽기를 최소화함.

  • 플러시(Flush) 리스트: 디스크로 동기화되지 않은 데이터를 가진 데이터 페이지(더티 페이지)의 변경 시점 기준의 페이지 목록을 관리함. 버퍼 풀 내의 더티 페이지를 찾기 위한 목적의 리스트.

  • 프리(Free) 리스트: 비어 있는 페이지들의 목록. 쿼리가 새롭게 디스크의 데이터 페이지를 읽어와야 하는 경우 사용됨.

1.3. 버퍼 풀과 리두 로그

InnoDB의 버퍼 풀은 디스크에서 읽은 상태로 전혀 변경되지 않은 클린 페이지(Clean Page)와 함께 INSERT, UPDATE, DELETE 명령으로 변경된 데이터를 가진 더티 페이지(Dirty Page)도 가지고 있습니다.

더티 페이지는 디스크와 메모리의 데이터 상태가 다르기 때문에 언젠가는 디스크로 기록돼야 합니다. 이때 리두 로그는 1개 이상의 고정 크기 파일을 연결해서 순환 고리처럼 사용합니다. 즉, 데이터 변경이 계속 발생하면 리두 로그 파일에 기록됐던 로그 엔트리는 새로운 로그 엔트리로 덮어 쓰이게 됩니다.

그래서 리두 로그 파일에서 재사용 가능한 공간과 당장 재사용 불가능한 공간을 구분해서 관리해야 하는데, 재사용 불가능한 공간을 활성 리두 로그(Active Redo Log)라고 합니다. 그림에서 화살표를 가진 엔트리들이 활성 리두 로그 공간입니다.

리두 로그 파일의 공간은 계속 순환되어 재사용되지만 매번 기록될 때마다 로그 포지션은 계속 증가된 값을 갖게 되는데, 이를 LSN(Log Sequence Number)이라고 합니다. InnoDB 스토리지 엔진은 주기적으로 체크포인트 이벤트를 발생시켜 리두 로그와 버퍼 풀의 더티 페이지를 디스크로 동기화하는데, 이렇게 발생한 체크포인트 중 가장 최근 체크포인트 지점의 LSN이 활성 리두 로그 공간의 시작점이 됩니다.

1.4. 버퍼 풀 플러시(Buffer Pool Flush)

MySQL 5.7 버전을 거쳐 MySQL 8.0 버전이 되면서 대부분의 서비스에서는 더티 페이지를 디스크에 동기화하는 부분(더티 페이지 플러시)에서 예전과 같은 디스크 폭증 현상이 발생하지 않았습니다. 특별히 문제가 발생하지 않는 상태라면 이 시스템 변수들을 조정할 필요는 없습니다.

  • 플러시 리스트 플러시: 플러시 리스트에서 오래전에 변경된 데이터 페이지 순서대로 디스크에 동기화

  • LRU 리스트 플러시: LRU 리스트에서 사용 빈도가 낮은 데이터 페이지들을 제거해서 새로운 페이지들을 읽어올 공간을 생성

1.5. 버퍼 풀 상태 백업 및 복구

MySQL 5.6 버전부터는 버퍼 풀 덤프 및 적재 기능이 도입됐습니다. MySQL 서버를 셧다운하기 전에 다음과 같이 시스템 변수를 이용해 현재 InnoDB 버퍼 풀의 상태를 백업 및 복구할 수 있습니다.

-- // MySQL 서버 셧다운 전에 버퍼 풀의 상태 백업
mysql> SET GLOBAL innodb_buffer_pool_dump_now=ON;
-- // MySQL 서버 재시작 후, 백업된 버퍼 풀의 상태 복구
mysql> SET GLOBAL innodb_buffer_pool_load_on=ON;
-- // 버퍼 풀 복구 진행도 확인
mysql> SHOW STATUS LIKE 'Innodb_buffer_pool_dump_status';
-- // 버퍼 풀 복구 중단
mysql> SET GLOBAL innodb_buffer_pool_load_abort=ON;

버퍼 풀의 백업과 복구를 자동화하려면 innodb_buffer_pool_dump_at_shutdowninnodb_buffer_pool_load_at_startup 설정을 MySQL 서버의 설정 파일에 넣어두면 됩니다.

1.6. 버퍼 풀의 적재 내용 확인

MySQL 8.0 버전에서는 information_schema 데이터베이스에 innodb_cached_indexes 테이블이 새로 추가됐습니다. 이 테이블을 이용하면 인덱스별로 데이터 페이지가 얼마나 InnoDB 버퍼 풀에 적재돼 있는지 확인할 수 있습니다.

2. Double Write Buffer

InnoDB 스토리지 엔진의 리두 로그는 리두 로그 공간의 낭비를 막기 위해 페이지의 변경된 내용만 기록합니다. 이로인해 더티 페이지를 디스크 파일로 플러시할 때 일부만 기록되는 문제(Partial-page)가 발생하면 그 페이지의 내용은 복구할 수 없을 수도 있습니다.

InnoDB 스토리지 엔진에서는 이 같은 문제를 막기 위해 Double-Write 기법을 이용합니다. 'A'~'E'까지의 더티페이지를 디스크로 플러시 할 때 에러가 발생하는 상황을 가정해봅시다.

  1. 실제 데이터 파일에 변경 내용을 기록하기 전에 'A'~'E'까지의 더티 페이지를 우선 묶어서 한 번의 디스크 쓰기로 시스템 테이블스페이스의 DoubleWrite 버퍼에 기록합니다.

  2. 각 더티 페이지를 파일의 적당한 위치에 하나씩 랜덤으로 쓰기를 실행합니다.

  3. 'A'와 'B' 페이지는 정상적으로 기록됐지만 'C' 페이지가 기록되는 도중 운영체제가 비정상적으로 종료됐습니다.

  4. InnoDB 스토리지 엔진이 재시작될 때 DoubleWrite 버퍼의 내용과 데이터 파일의 페이지들을 모두 비교해서 다른 내용을 담고 있는 페이지가 있으면 DoubleWrite 버퍼의 내용을 데이터 파일의 페이지로 복사합니다.

HDD처럼 플래터(Platter)가 회전하는 저장 시스템에서는 어차피 한 번의 순차 디스크 쓰기를 하는 것이기 때문에 별로 부담이 되지 않지만 SSD처럼 랜덤 IO나 순차 IO의 비용이 비슷한 저장 시스템에서는 상당히 부담스러울 수 있습니다. 그럼에도 불구하고 데이터의 무결성이 매우 중요한 서비스에서는 DoubleWrite의 활성화를 고려하는 것이 좋습니다.


🎯정리

  • InnoDB의 버퍼 풀은 빈번한 디스크 I/O 작업의 필요성을 줄이기 위해 디스크 기반 스토리지에서 데이터 및 인덱스를 캐시하는 메모리 영역입니다.

  • 스토리지에서 데이터를 읽거나 데이터베이스에서 수정하면 먼저 버퍼 풀로 가져옵니다.

  • 동일한 데이터가 다시 요청되면 InnoDB는 디스크에 액세스할 필요 없이 버퍼 풀(메모리)에서 데이터를 제공할 수 있으므로 훨씬 빠르고 전체 시스템 대기 시간을 줄이는 데 도움이 됩니다.

  • 데이터 내구성을 보장하기 위해 InnoDB는 주기적으로 체크포인트 이벤트를 발생시킵니다. 체크포인트 이벤트 동안 InnoDB는 버퍼 풀에서 수정된 모든 페이지를 디스크에 기록합니다. 이 프로세스는 커밋된 모든 변경 사항이 디스크에 안전하게 저장되도록 하여 충돌 중에 데이터 손실을 방지합니다.

  • 체크포인트 중 가장 최근 체크포인트 지점의 LSN이 활성 리두 로그 공간의 시작점이 됩니다

  • innodb_buffer_pool_dump_at_shutdowninnodb_buffer_pool_load_at_startup 설정으로 버퍼 풀의 백업 및 복구를 자동화할 수 있습니다.

  • Double Write Buffer은 프로세스 중 갑작스러운 정전이나 기타 시스템 오류로 인해 데이터 불일치 또는 손상이 발생할 경우 데이터 손상을 방지하는 데 사용되는 메커니즘입니다.


🔖참고