MySQL의 스토리지 엔진 가운데 가장 많이 사용되는 InnoDB 스토리지 엔진을 살펴보자.
-MySQL에서 사용할 수 있는 스토리지 엔진 중에서 거의 유일하게 레ㅗ드 기반의 잠금을 제공해서 높은 동시성 처리가 가능하고 안정적이며 성능이 뛰어나다.
주요 특징
1. 프라이머리 키에 의한 클러스터링
- InnoDB의 모든 테이블은 기본적으로 PK를 기준으로 클러스터링되어서 저장된다. (클러스터드 인덱스)
- PK 값 순서대로 디스크에 저장된다는 의미
- 세컨더리 인덱스(논클러스터드 인덱스)는 PK 값을 논리적 주소로 사용
2. 외래 키 지원
- InnoDB에서의 외래키
- 부모 테이블, 자식 테이블 모두 해당 칼럼에 대한 인덱스 생성이 필요함
- 변경 시에는 반드시 부모 또는 자식 테이블에 데이터가 있는지 확인하는 작업이 필요함
- 이에 따라서 잠금이 여러 테이블로 전파, 데드락 발생 가능성이 높아짐
- 이러한 이유로, 외래 키의 존재는 개발할 때 조심해야함
3. MVCC (Multi Version Concurrency Control)
- 레코드 레벨의 트랜잭션을 지원하는 기능
- 잠금을 사용하지 않는 일관적 읽기를 제공하는 것이 목적
- InnoDB에서는 언두 로그를 통해서 기능을 구현
- 격리 수준에 따라서 사용자에게 제공하는 데이터의 버전이 달라짐
- 트랜잭션이 길어지면 언두에서 관리하는 예전 데이터(버전)가 오랫동안 관리되는 상황이 발생 -> 성능 저하
- 언두 영역의 백업데이터는 더 이상 필요로하는 트랜잭션이 없을 때 삭제됨
4. 잠금 없는 일관된 읽기(Non-Locking Consistent Read)
- 격리 수준이 SERIALIZABLE이 아니라면 순수한 읽기 작업은 잠금을 대기하지 않고 실행됨
5. 자동 데드락 감지
- 잠금 대기 목록을 그래프 형태로 관리(Wait-for List)
- 데드락 감지 스레드를
6. 자동화된 장애 복구
- 손실이나 장애로부터 데이터를 보호하기 위한 여러가지 메커니즘이 탑재
- MySQL 서버가 실행될 때, 완료되지 못한 트랜잭션이나 partial write 데이터 페이지에 대해서 복구 작업을 자동으로 실행
InnoDB 버퍼 풀
- 가장 핵심적인 부분
- 디스크의 데이터 파일, 인덱스 정보를 메모리에 캐시해두는 곳
- 쓰기 지연을 처리해주는 버퍼 역할
- 쓰기 지연을 통해서 랜덤한 디스크 작업의 횟수를 줄인다.
- 5.7 버전부터 버퍼 풀의 크기를 동적으로 조절할 수 있게 개선되었음
- 적절히 작은 값으로 설정해서 조금씩 증가시키는 방법이 좋다.
- 버퍼 풀의 크기를 작게 조정하는 것은 서비스 영향도가 매우 크다.
- 128MB 청크 단위로 쪼개서 관리되는데 크기를 조정하는 단위 크기로 사용된다.
버퍼 풀의 구조
- 버퍼 풀의 페이지 크기 조각을 관리하기 위해서 LRU 리스트, Flush 리스트, Free 리스트 3개의 자료 구조를 관리한다.
- Free 리스트 : 비어 있는 페이지들의 목록
- LRU 리스트 : LRU와 MRU 리스트가 결합된 형태
- Old 서브리스트 : LRU
- New 서브리스트 : MRU
- LRU 리스트를 관리하는 목적은 한번 읽어온 페이지를 최대한 메모리에 유지해서 디스크 읽기를 최소화하는 것
- Flush 리스트 : 더티 페이지 목록을 관리
- 더티 페이지는 특정 시점에 디스크에 기록
- 데이터가 변경되면 리두 로그에 기록, 버퍼 풀의 데이터 페이지에도 변경 내용을 반영
스토리지 엔진이 데이터를 찾는 과정
- 필요한 레코드가 저장된 데이터 페이지가 버퍼 풀에 있는지 검사b. 테이블의 인덱스(B-Tree)를 이용해서 버퍼 풀에서 페이지를 검색
- c. 버퍼 풀에 데이터 페이지가 있었다면, 포인터를 MRU 방향으로 승급
- a. 어댑티브 해시 인덱스를 이용해서 페이지를 검색
- 디스크에서 필요한 데이터 페이지를 버퍼 풀에 적재, 포인터를 LRU 헤더 부분에 추가
- LRU 헤더 부분에 적재된 페이지가 읽히면, MRU 헤더 부분으로 이동
- 버퍼 풀에 상주하는 데이터 페이지는 age가 부여되며, 너무 오래된 페이지는 삭제되고 다시 사용되면 age 초기화
- 자주 접근된 페이지는 인덱스 키를 어댑티브 해시 인덱스에 추가
버퍼 풀과 리두 로그
- 버퍼 풀의 메모리 공간을 크게 만들면, 데이터 캐시 기능만 성능 향상
- 쓰기 버퍼링 기능의 성능 향상을 위해서는 버퍼 풀의 리두 로그 관계를 이해해야 함
조금 더 이해하고 정리하기로..
버퍼 풀 플러시
- 더티 페이지 플러시(더티 페이지를 디스크에 동기화하는 것)
- 2개의 플러시 기능을 백그라운드로 실행
- Flush 리스트 플러시
- LRU 리스트 플러시
플러시 리스트 플러시
- 클리너 스레드 : 더티 페이지를 디스크로 동기화하는 스레드
- innodb_pate_cleaners 와 innodb_buffer_pool_instances설정을 동일하게 맞춰주자 (인스턴스 당 스레드를 하나씩!)
- 기본적으로 버퍼 풀에 있는 페이지의 90%까지 더티 페이지로 가지고 있을 수 있음
- 하지만 이렇게 되면 디스크 I/O 폭증 현상(Disk IO Burst) 발생
- innodb_max_dirty_pags_lwm : 일정 수준의 더티 페이지가 생기면 조금씩 디스크로 기록하도록 하는 변수
- 기본적으로 10%, 디스크 쓰기가 많고, 더티 페이지 비율이 낮다면 변수를 높게 조정
LRU 리스트 플러시
- LRU 리스트에서 사용 빈도가 낮은 데이터 페이지들을 제거
- LRU 리스트 끝부분부터 innodb_lru_scan_depth에 설정된 갯수만큼 페이지 스캔
- 더티 페이지는 디스크 동기화, 클린 페이지는 Free 리스트로 옮김
- 인스턴스마다 페이지 스캔 -> 인스턴스 갯수 * 설정된 페이지 갯수 스캔
버퍼 풀 상태 백업 및 복구
- 버퍼 풀 덤프 및 적재 기능
- Innodb_buffer_dump_now 시스템 변수를 이용해 백업 가능
- innodb_buffer_pool_load_now 변수를 이용해서 상태를 다시 복구할 수 있음
- 버퍼 풀 백업은 데이터 디렉토리에 ib_buffer_pool 이라는 파일로 생성
Double Write Buffer
- 리두 로그는 변경된 내용만 기록
- 더티 페이지를 플러시할 때, Partial-page, Torn-page 현상 발생 가능 -> 복구 불가능
- Double-Write 기법을 이용
- 더티 페이지를 묶어서 한 번의 디스크 쓰기로 시스템 테이블스페이스의 더블라이트 버퍼에 기록
언두 로그
- 트랜잭션과 격리 수준을 보장하기 위해서 백업 -> 언두 로그
더 이해하고 정리
체인지 버퍼
- 레코드가 insert 혹은 update 되면 데이터 파일 변경 및 인덱스 변경 작업이 필요하다.
- 인덱스 업데이트는 랜덤하게 디스크를 읽는 작업이 필요해서 성능 향상을 위해서 체인지 버퍼에 저장해둔다.
- 유니크 인덱스는 체인지 버퍼를 사용할 수 없다.
- 인덱스 레코드 조각은 체인지 버퍼 머지 스레드에 의해서 병합된다.
리두 로그
- 하드웨어나 소프트웨어 등 여러 가지 문제로 인해서 MySQL 서버가 비정상적으로 종료했을 때, 기록되지 못한 데이터를 잃지 않도록 하는 역할
- MySQL을 포함해서 대부분의 데이터베이스 서버는 데이터 변경 내용을 로그로 먼저 기록한다.
- 일부 DBMS에서는 Write Ahead Log(WAL)이라고도 부른다.
- 커밋됐지만 데이터 파일에 기록되지 않은 데이터 -> 리두 로그 내용을 통해서 데이터 파일에 복사
- 롤백됐지만 데이터 파일에 이미 기록된 데이터 -> 언두 로그 내용을 복사해서 해결 (리두 로그에서 커밋, 롤백, 트랜잭션 상태를 알기위해서 필요)
- 변경 작업이 많은 DBMS 서버의 경우 리두 로그 기록 작업이 문제가 된다.
- 최대한 ACID 속성을 보장하는 수준으로 버퍼링 하게 된다. -> 버퍼링에 사용되는 공간이 로그 버퍼
어댑티브 해시 인덱스
- 사용자가 수동으로 생성하는 인덱스가 아니라 innoDB가 사용자가 자주 요청하는 데이터에 대해서 자동으로 생성하는 인덱스
- B-Tree 검색 시간을 줄이기 위해서 도입된 기능
- 데이터 페이지의 키 값을 이용해서 해시 인덱스를 만들고, 어댑티브 해시 인덱스를 검색해서 레코드가 저장된 데이터 페이지를 즉시 찾아갈 수 있도록 한 것
출처) https://velog.io/@cmsskkk/realmysql42
댓글 영역