InnoDB 스토리지 엔진은 MySQL 스토리지 엔진중 거의 유일하게 레코드 기반의 잠금을 지원하여 높은 동시성 처리가 가능하여
많은 서비스 어플리케이션에서 가장 대중적으로 사용되고 있다. InnoDB 스토리지 엔진의 특징을 하나씩 살펴보자.
InnoDB 스토리지의 특징
PK 에 의한 클러스터링
InnoDB 의 테이블들은 기본적으로 PK 클러스터 인덱스로 지정되어 저장된다.
즉 PK 순서대로 물리적으로 저장된다는 뜻이다.
그로인해 InnoDB 는 PK 기준 검색 및 레인지 스캔이 상당히 빠르다.
Clustered Index
Clustered Index 는 테이블 당 하나만 지정할 수 있고, 지정한 컬럼을 대상으로 물리적으로 정렬되어 저장되는 인덱스이다.
물리적으로 정렬되어 있어, 빠른 검색속도를 자랑한다. 데이터를 삽입할 경우, 재 정렬을 하므로 삽입성능이 떨어진다.
외래키 지원
InnoDB 는 데이터 무결성을 위해 Foreign key 를 지원한다.
InnoDB 의 외래키는 부모테이블과 자식테이블 모두에 인덱스 생성을 필요로한다.
DDL 수행시, 부모테이블이나 자식테이블에 데이터가 있는지 항상 체크해야하므로 잠금이 여러 테이블에 전파될 수 있어 주의가 필요하다.
만약 외래키로 인한 문제가 라이브에서 발생시, 빠르게 해결하기가 힘든 경우가 있다.
긴급한 경우 foreign_key_checks 옵션을 false 로 설정하면 외래키 체크 작업을 멈출 수 있다.
MVCC (Multi Version Concurrency Control)
레코드 레벨의 트랜잭션이 지원되는 DBMS 는 잠금을 최소화 하기위해 MVCC 를 지원한다.
InnoDB 역시 레코드 레벨의 트랜잭션으로 동작하므로 MVCC 를 지원한다.
InnoDB 의 MVCC 는 언두로그를 활용하여 동작한다.
언두로그는 포스팅아래에서, MVCC 를 활용한 트랜잭션 격리는 추후 다른 포스팅에서 설명한다.
MVCC?
MVCC 란 잠금없이 일관된 읽기를 가능하도록 지원하는 방법이다.
데이터를 트랜잭션 별로 버저닝 하여 일관성을 유지하는 기법이다.
자동 데드락 감지
InnoDB 는 데드락 감지 스레드가 주기적으로 잠금 대기 그래프를 검사하여, 교착상태에 빠진 트랜잭션을 강제로 종료한다.
데드락 감지 스레드는 언두 로그 레코드를 더 적게 가진 트랜잭션을 우선으로 종료시킨다.
(언두 로그가 적다 -> 롤백을 해도 언두 처리할 데이터가 적다. 서버 부하가 적다.)
데드락 감지 스레드는 InnoDB 에 속하므로 MySQL 엔진 잠금인 TABLE 락은 볼수가 없다.
Innodb_tables_locks 시스템 변수를 활성화 해 가시성을 얻을 수 있다.
데드락 감지 스레드는, 동시 처리 스레드가 매우 많아지면 서버에 큰 성능 저하를 준다. innodb_deadlock_detect 옵션을 꺼 데드락 감지스레드를 사용하지 않고, innodb_lock_wait_timeout 을 설정해, 자동으로 요청 실패처리를 하는 방법으로 교착상태를 피할 수 있다.
자동화된 장애 복구
InnoDB 엔진은 예기치 못한 상황으로 종료되어 발생한 문제들을 복구할 여러 장치들을 지니고 있다.
예를들면 예기치 못한 상황으로 종료되어 유실된 데이터는 리두로그를 활용하여 복구한다.
InnoDB 스토리지 엔진 구성 요소
InnoDB 버퍼 풀
InnoDB 스토리지 엔진의 핵심이라고 볼 수 있는 요소이며, 크게 2가지 역할을 한다.
1. 디스크 데이터나, 인덱스 정보를 메모리에 캐싱 해두는 역할
2. 쓰기 지연 후 일괄처리를 할 수 있도록 하는 버퍼 역할
InnoDB 버퍼 풀은 핵심적인 역할을 하는 만큼, 크기 설정에 따라 서버에 큰 성능 향상을 줄 수 있다.
InnoDB 의 메모리는 버퍼풀 외에는 거의 사용되지 않지만 그럼에도 중요한 역할을 하기때문에
운영체제와 클라이언트 스레드가 사용할(읽기 쓰기 작업은 레코드 버퍼에서 메모리를 사용한다) 메모리를 충분히 고려하여 설정해야 한다.
버퍼풀의 크기는 올리기는 쉽지만, 내리기는 어려우므로 신중히 결정하도록 한다.
버퍼풀 구조
버퍼풀은 LRU 리스트, 플러시 리스트, 프리 리스트라는 3가지 자료구조로 관리된다.
- 프리 리스트 - 실제 사용자 데이터로 채워지지 않은 빈 페이지이다. 새롭게 사용자의 쿼리가 데이터를 읽어와야 하는 경우 사용한다.
- LRU 리스트 - 정확히는 LRU (Least Recent Used) 와 MRU (Most Recent Used) 가 결합된 형태이다.
LRU 리스트는 MRU 와 LRU 구조를 이어 붙인 형태로, 새 데이터가 들어오면 두 자료구조 사이에 배치된다.
오래 읽히지 않은 데이터 페이지는 아래쪽으로 Aging 되며 이동하며, 삭제된다.
존재하는 레코드가 읽히면, MRU 리스트 최 상단으로 이동한 후 다시 Aging 된다.
- 플러시 리스트 - 디스크로 동기화 되지 않은 변경된 데이터를 가진 데이터 페이지(더티 페이지)들을 변경시점 기준으로 관리를 하는 리스트이다. 디스크에서 읽은 상태에서 변경이 안되었다면 관리하지 않는다.
리두 로그 (Redo Log)
리두 로그는 DB 장애 발생시, 데이터 복구를 위해 사용된다.
버퍼풀은 메모리 공간이기 때문에, 서버 장애시 내용이 모두 날아간다. 그래서 더티 페이지가 생길 때 마다, 리두 로그를 기록하여 장애 복구시 이용한다.
위에서 버퍼풀의 크기 관리가 중요하다고 하였는데, 단순히 버퍼풀의 크기만 늘리는 것은 데이터 캐시 기능만 향상시킨다.
버퍼풀의 쓰기 지연능력도 항상 시키기 위해서는 리두 로그와 버퍼풀의 관계를 이해해야한다.
리두 로그는 여러개의 리두 로그 파일이 체이닝 된 구조인데, 모든 리두 로그가 꽉 차게되면 리두로그 엔트리중 하나를 삭제해야한다.
리두 로그가 삭제될때는, 아직 플러시 되지 않은 리두로그와 연결된 더티페이지들을 플러시해야한다.
이때 리두 로그 파일 스위칭으로 인한 오버헤드와 데이터 플러시로 인한 오버헤드가 발생한다.
너무 적어 자주 스위칭이 일어나지 않게, 너무 한번에 큰 디스크 쓰기가 발생하지 않도록 적절한 크기로 리두로그 크기를 설정해야 한다.
더티페이지 관련 작업만 기록하므로 버퍼풀보다는 훨씬 적은 크기를 지녀도 된다.
언두 로그 (Undo Log)
위의 MVCC 에서 다루었듯, InnoDB 는 트랜잭션 격리 수준 보장을 위하여 Undo 로그를 활용한다.
언두 로그는, DML 수행시 변경 전 데이터를 버저닝하여 백업하는 로그이다.
언두로그는 2가지의 큰 목적을 위해 이용된다.
1. 트랜잭션의 원자성 보장 (Rollback 시 이전으로 돌리기 위하여)
2. 격리수준 보장 (여러 트랜잭션이 동시에 수행될 때, 트랜잭션 격리 수준에 맞느 데이터를 반환하기 위해)
데이터를 변경하면, 이전 버전의 데이터는 언두 로그에 기록되며, 버퍼풀의 값을 수정한다.
또한 언두 로그는 관련된 트랜잭션이 모두 종료된 후 특정 시점에 삭제되는데, 언두로그의 수가 많다는 것은
트랜잭션이 지속되는 시간이 과도하게 길다는 뜻이다. 주기적으로 확인하여 트랜잭션 이상을 확인하는것이 좋다.
체인지 버퍼 (Change Buffer)
INSERT, UPDATE 시 인덱스도 변경을 해야한다.
인덱스 업데이트는 랜덤하게 디스크 읽기가 필요해 상당히 많은 자원이 필요하다.
InnoDB 는 업데이트 할 인덱스 페이지가 버퍼풀에 있으면 즉시 수행하지만, 없을 경우 체인지 버퍼에 저장한다.
유니크 인덱스의 경우는 즉시 중복 여부를 사용자에게 반환해야하기 때문에, 체인지 버퍼를 사용할 수 없다.
어댑티브 해시 인덱스
일반적으로 테이블의 인덱스는 B-Tree 알고리즘을 활용한다.
InnoDB 스토리지 엔진은, 자주 조회되는 데이터 페이지에 대하여 해시 인덱스를 자동으로 생성한다.
B-Tree 인덱스의 경우 결국 트리노드를 탐색해야 하기 때문에, 해시 인덱스에 비하여 느린편이다.
또한 트리 노드를 탐색해야 하기 때문에 Mutex 로 인한 오버헤드도 발생한다.
InnoDB 는 어댑티브 해시 인덱스를 활용하여 위의 단점을 해결하여 성능 향상을 도모할 수 있다.
어댑티브 해시 인덱스는 버퍼풀에 올려진 데이터에 대해서만 생성되며, 버퍼풀에서 데이터가 사라지면 삭제된다.
이렇게만 보면 어댑티브 해시 인덱스를 사용하는 편이 항상 성능에 유리할 것 같지만 그렇지는 않다.
인덱스를 일단 활성화 시키면, 항상 작업시 해시 인덱스를 우선 학인하여야 하고, 메모리를 이용하기 때문에 큰 사이즈의 데이터가 저장되면 오히려 성능저하가 발생할 수 있다.
어댑티브 해시 인덱스는 다음과 같은 경우 사용하기 좋다.
- 디스크 데이터가 버퍼풀 크기와 비슷한 경우 (디스크 직접읽기가 적은 경우)
- 동등, in 조건 검색이 많은 경우
- 쿼리가 데이터 중에서 일부 데이터에 집중되는 경우
위에서 기술한 내용과 정반대의 상황이라면, 오히려 비활성화 하는편이 나을 수 있다.
'Database' 카테고리의 다른 글
[Real MySQL 8.0] 인덱스 (0) | 2022.05.08 |
---|---|
MySQL Gap Lock, Next key Lock (갭락, 넥스트 키락) 과 데드락 (0) | 2022.04.12 |
RDS Slow Query Log 세팅 및 쿼리 개선 (0) | 2022.03.22 |
Redis 자료구조와 활용 예시 (0) | 2022.03.16 |
[REAL MYSQL 8.0] MySQL 엔진 아키텍쳐 - (1) (0) | 2022.03.14 |