4. 아키텍처 (2) - 스토리지 엔진 아키텍처 (InnoDB)

Yany Choi·2023년 4월 8일
0

DB

목록 보기
3/4

InnoDB 아키텍처

InnoDB의 특징

  • MySQL 스토리지 엔진 중 가장 많이 사용된다.
  • MySQL에서 사용할 수 있는 스토리지 엔진 중 거의 유일하게 레코드 기반 잠근을 제공하여 높은 동시성 처리가 가능하다.

1. Primary Key에 의한 클러스터링

InnoDB의 모든 테이블은 기본적으로 Primary Key를 기준으로 클러스터링되어 저장된다. 즉, Primary Key 값의 순서대로 디스크에 저장된다. 따라서 세컨더리 인덱스는 레코드의 주소가 아닌 Primary Key의 값을 주소로 사용한다.

→ Primary Key의 값을 이용한 스캔은 상당히 빨리 처리된다.

→ 쿼리의 실행 계획에서 Primary Key가 다른 보조 인덱스에 비해 높은 비중으로 설정된다.

클러스터링 키 기능은 MyISAM에서는 지원하지 않아서 Primary Key와 세컨더리 인덱스의 구조적 차이가 없다.

2. 외래 키 지원

외래 키는 InnoDB의 스토리지 엔진에서 지원하는 기능으로, MyISAM, MEMORY 테이블에서는 사용할 수 없다.

InnoDB에서 외래 키는 부모 테이블, 자식 테이블 모두 데이터가 있는지 체크하는 작업을 거치므로 Lock이 여러 테이블로 전파되면서 이로 인해 데드락이 발생할 가능성이 높다.
그래서 서비스에서 외래 키를 불편함 때문에 생성하지 않는 경우도 자주 있다.

수동으로 데이터 적재, 스키마 변경 등 관리 차원의 작업이 외래 키가 복잡하게 얽힌 경우 간단하게 처리되지 해결되지 않는다. foreign_key_checks 시스템 변수를 OFF로 설정 시 외래 키 체킹을 멈출 수 있으나 (ON DELETE CASCADE, ON UPDATE CASCADE 포함, 다시 활성화하기 이전에 레코드의 일관성을 맞춰줘야 한다.

3. MVCC (Multi Version Concurrency Control)

목적

Lock을 사용하지 않는 일관된 읽기를 제공한다. → Non-Locking Consistent Read

InnoDB → 언두 로그로 구현

데이터 변경시 변경 전 값을 언두 로그로 복사, InnoDB의 버퍼 풀에는 변경 후로 덮어쓰기

COMMIT, ROLLBACK이 시행되기 전 읽기 작업이 들어오면 격리 수준에 따라 다른 결과가 일어난다.

READ_COMMITED 이상의 강한 격리이면 언두 로그에서 COMMIT 이전의 값을, 그 아래이면 버퍼 풀에서 수정되고 있는 값을 가져온다.

COMMIT을 하면 버퍼 풀의 데이터를 데이터 파일에 저장, ROLLBACK을 하면 언두 로그의 데이터를 다시 버퍼 풀로 가져오고, 언두 로그를 지운다. 그러나 이는 이 언두 영역을 필요로 하는 트랜잭션이 없을 때가 되어야 삭제된다.

4. Non-Locking Consistent Read

SERIALIZABLE 격리 수준이 아닌 이상 순수 SELECT 작업은 Lock을 거치지 않는다. 그리고 타 트랜잭션들이 이 읽기 작업을 방해하지 않는다.

5. 자동 데드락 감지

InnoDB에서는 Deadlock을 감시하기 위해 Lock 대기 목록을 그래프(Wait-for List)로 확인한다.

데드락 감지하는 스레드가 따로 존재하며, Deadlock에 빠진 트랜잭션 중 언두 로그 레코드가 제일 적은 트랜잭션을 강제종료한다. → 언두처리를 할 레코드의 양이 적어서 리소스를 절약할 수 있음

이 스레드는 LOCK TABLES 명령으로 잠기는 테이블을 감지하지는 못하는데, innodb_table_locks 변수를 통해 감지 가능하게 만들 수 있다.

동시처리하는 스레드가 너무 많아지거나 잠금의 개수가 너무 많아지면 감지 스레드 또한 느려지는데, 이러면 다른 스레드의 처리속도 또한 느려져 악순환이 된다. 이를 방지하지 위해 timeout을 걸어 데드락 상황에서 시간이 지나면 실패하도록 설정할 수 있다.

6. 자동화된 장애 복구

InnoDB는 MySQL 서버가 시작될 때 완료되지 못한 트랜잭션, 일부만 기록된 데이터 등에 대한 복구 작업을 자동으로 진행한다.

디스크, 하드웨어 이슈로 발생한 손상은 자동으로 복구하지 못한다.

7. InnoDB 버퍼 풀

디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐싱하는 공간

버퍼 역할을 맡아서 분산된 수정 대상의 레코드들을 한곳으로 모아서 한번에 처리해서 디스크 작업의 횟수를 최적화한다.

기존 버퍼 풀은 세마포어로 관리되어 버퍼 풀에서의 잠금 경합이 많이 일어났는데, 버퍼 풀을 여러 개로 쪼개어 관리할 수 있게 됐다.

구조

버퍼 풀을 페이지 크기(innodb_page_size)로 쪼개어 데이터를 필요로 할 때 해당되는 데이터 페이지를 읽어서 조각에 저장한다.

  • LRU (Least Recently Used) 디스크로부터 한 번 읽어온 페이지를 최대한 오래 메모리에 유지해서 디스크 읽기를 최소화
  • Flush 데이터가 변경 된 더티 페이지의 변경 시점 기준 목록을 관리
  • Free 실제 사용자 데이터로 채워지지 않은, 비어 있는 페이지의 목록 새로 디스크의 데이터 페이지를 읽어 올때 사용한다.

버퍼 풀과 리두 로그

버퍼 풀 확장은 데이터 캐시, 쓰기 버퍼링 중 데이터 캐시만 향상됨

리두 로그 공간의 재사용을 통해 쓰기 버퍼링을 향상시킴

버퍼 풀 플러시

플러시 : 더티 페이지를 디스크로 동기화하는 것

플러시 리스트 플러시 : 플러시 리스트에서 오래된 데이터 페이지부터 디스크에 동기화, 리두 로그 공간을 확보

LRU 리스트 플러시 : LRU 리스트 끝부분부터 시작해서 innodb_lru_scan_depth 변수만큼 동기화해서 LRU 리스트의 공간 확보

버퍼 풀 내용 확인

information_schema 데이터베이스의 innodb_buffer_page 테이블 조회를 통해 확인

그러나 버퍼 풀이 크면 조회 시 엄청난 부하가 발생한다.

8.0 버전 이후 information_schema의 innodb_cached_indexes 테이블을 통해 테이블 인덱스별로 데이터 페이지의 적재된 수를 확인할 수 있다.

8. Double Write Buffer

리두 로그는 페이지의 변경된 내용만 기록한다. 이를 플러시할때 일부만 기록되면 페이지의 내용을 복구할 수 없게 되는데, 이를 방지하는게 Double-Write 기법이다.

플러시 이전에 미리 DoubleWrite 버퍼에 기록을 해놓아서 만약 문제가 발생할 시 MySQL이 재시작하면서 실패시점부터 DoubleWrite에서 다시 쓰는 것이다.

9. 언두 로그

InnoDB 엔진 차원에서 트랜잭션, 격리 수준을 보장한다.

INSERT, UPDATE, DELETE로 변경되기 이전의 데이터를 별도로 백업한 것이 언두 로그이다.

트랜잭션이 길어지면 그만큼 언두 로그의 사용량이 많아지며, 누적되어서 장애를 일으킨다.

8.0 이전에는 트랜잭션 종료 이후에도 이 언두 로그의 크기를 줄일 수 없었다.

8.0 부터는 언두 테이블스페이스를 동적으로 추가, 삭제 할 수 있게 되어 공간을 관리할 수 있게 되었다.

10. 체인지 버퍼

버퍼 풀에 해당되는 인덱스 페이지가 없으면, 이를 디스크에서 불러올 때 체인지 버퍼로 가져온 후 결과를 반환해서 중간 캐싱을 통해 속도를 향상시킨다.

11. 리두 로그 및 로그 버퍼

서버가 비정상적으로 종료됐을 때 데이터 파일에 기록되지 못한 데이터를 잃지 않게 해주는 안전장치

  1. 커밋됐으나 데이터 파일에 기록이 안됨 → 리두 로그에 저장된 데이터를 데이터 파일에 다시 복사
  2. 롤백됐지만 데이터 파일에 이미 기록이 됨 → 리두 로그로 트랜잭션 중단 위치를 파악하고, 언두 로그에서 가져와서 복사

8.0 부터는 리두 로그의 아카이빙해서 리두 로그까지 백업해서 데이터 백업의 일관성을 유지할 수 있게 해준다.

리두 로그는 비활성화 하기보다, 모든 트랜잭션을 로깅하는 것이 아닌 시간 단위로 로깅하는 것을 찾는게 낫다.

( innodb_flush_log_at_trx_commit 변수)

12. 어댑티브 해시 인덱스

인덱스 : 테이블에 사용자가 생성해둔 B트리 인덱스

B트리 검색 시간 향상을 위한 기능이 어댑티브 해시 인덱스이다.

자주 읽히는 데이터 페이지의 키 값으로 해시 인덱스를 만들어 필요할 때 해시 인덱스를 검색해서 데이터 페이지를 즉시 찾아간다.

해시 인덱스로 찾아가기 vs B트리 노드 내려가면서 찾기

그러나 테이블의 레코드를 폭넓게 읽는 등 디스크를 많이 읽는 경우 성능 향상에 도움이 되지 않으며, 데이터 중 일부에 집중된 읽기 작업이 존재하는 경우 좋다.

→ 어댑티브 해시 인덱스 또한 메모리를 차지하며, 해시 인덱스를 어쨋든 유지해야 한다. 그리고 데이터의 수정 시 일반적인 인덱스와 같이 이 또한 처리 성능을 느려지게 만든다.

profile
생각하자

0개의 댓글