본 글은 1회독을 겨우한 자의 정리본으로 도움이 되지 않을 수 있습니다
4-1-1. MySQL 전체 구조
mysql은 크게 mysql 엔진 / 스토리지 엔진으로 나뉘어져 있음
1) mysql 엔진 : 커넥션 핸들러 / SQL 파서 / 전처리기 / 옵티마이저
2) 스토리지 엔진 : mysql엔진은 dbms의 두뇌에 해당하는 처리를 수행, 실제 데이터를 디스크 스토리지에 저장하거나 디스크 스토리지로 부터 데이터를 읽어오는 부분은 스토리지 엔진이 전담함.
mysql 엔진과 스토리지 엔진 사이에 데이터를 주고받는 것이 핸들러 API
4-1-2. mysql 스레딩 구조
- mysql 서버는 프로세스 기반이 아니라 스레드 기반으로 작동함
- 이는 포그라운드 스레드와 백그라운드 스레드로 구분할 수 있음
1) 포그라운드 스레드(클라이언트 스레드) : mysql 에 접속된 클라이언트의 수만큼 존재 / 데이터를 mysql의 데이터 버퍼나 캐시로부터 가져온다. 없으면 직접 디스크의 데이터나 인덱스 파일로부터 데이터를 읽어와서 작업을 처리한다.
MyISAM은 디스크 쓰기 작업을 포그라운드 스레드가 처리
InnoDB는 데이터 버퍼나 캐시까지만 포그라운드 스레드가 처리 -> 나머지는 백그라운드 스레드가 처리
2) 백그라운드 스레드 : MyISAM에서는 잘 안쓰지만 InnoDB에서 잘 쓰는 스레드
로그 스레드 / 쓰기 스레드가 중요
4-1-3. 메모리 할당 및 사용구조
MySQL에서 사용되는 메모리 공간은 글로벌 메모리 영역과 로컬 메모리 영역이 있음
1) 글로벌 메모리 영역 : 클라이언트의 스레드 수와 무관하게 하나의 메모리 공간만 할당함
테이블 캐시 / InnoDB버퍼풀 / InnoDB어댑티브해시인덱스 / InnoDB 리두로그 버퍼
2) 로컬 메모리 영역(세션 메모리 영역) :클라이언트 스레드가 쿼리를 처리하는 메모리 영역
각 클라이언트 스레드 별로 독립적으로 할당되며 절대 공유되어 사용되지 않는 다는 특징이 있음
정렬 버퍼 / 조인 버퍼 / 바이너리 로그 캐시 / 네트워크 버퍼
4-1-4. 플로그인 스토리지 엔진 모델
MySQL의 독특한 구조 중 하나 , 플러그인 모델
플러그인 해서 사용할 수 있는것 -> 스토리지 엔진, 검색어 파서, 사용자인증 관련 등등 모두 플러그인으로 재현되어 제공
1) 핸들러 : MySQL엔진이 각 스토리지 엔진에게 데이터를 읽어오거나 저장하도록 명령하려면 반드시 핸들러를 통해야 한다.
4-1-5. 컴포넌트
MySQL 8.0부터는 플러그인을 대체할 컴포넌트 아키텍처가 지원됨.
플러그인보다 컴포넌트가 더 좋다,,
컴포넌트는 플러그인과 다르게 상호의존관계를 설정할 수 있다
4-1-6. 쿼리 실행 구조
1) 쿼리 파서 : 사용자 요청으로 들어온 쿼리문장을 토큰이라는 MySQL이 인식할 수 있는 최소 단위의 어휘로 분리해 트리 구조로 만들어내는 작업을 의미한다. 기본 문법 오류는 이 과정에서 발견된다.
2) 전처리기 : 파서과정에서 나온 파서 트리를 기반으로 쿼리 문장의 구조적인 문제점이 있는지 확인하고, 실제 존재하지 않거나 권한상 사용할 수 없는 개체의 토큰은 이 단계에서 걸러진다.
3) 옵티마이저 : 쿼리 문장을 저렴한 비용으로 가장 빠르게 처리할지를 결정하는 역할을 담당한다. DBMS의 두뇌에 해당한다고 볼 수 있다.
4) 실행 엔진 : 만들어진 계획대로 핸들러에게 요청해서 받은 결과를 또 다른 핸들러 요청의 입력으로 연결하는 역할을 수행한다
4-1-8. 쿼리캐시
동일 SQL 쿼리가 실행되면 테이블을 읽지 않고 즉시 결과를 반환하기 때문에 매우 빠른 성능을 보였는데, 동시 처리 성능 저하를 유발했다. 그래서 MySQL의 많은 버그의 원인이 되기도 했다.
8.0으로 올라오면서 쿼리캐시기능은 완전히 사라졌다.
4.1.9 스레드풀
내부적으로 사용자의 요청을 처리하는 스레드 개수를 줄여서 동시 처리되는 요청이 많다 하더라도
MySQL 서버의 CPU가 제한된 개수의 스레드 처리에만 집중할 수 있게 해서 서버의 자원 소모(오버 헤드)를 줄이는 것이 목적이다.
4.2 InnoDB 스토리지 엔진 아키텍처
InnoDB는 MySQL에서 쓸 수 있는 스토리지 엔진 중에 거의 유일하게 레코드 기반의 잠금을 제공한다
그래서 높은 동시성 처리가 가능하고, 안정적이며 성능이 뛰어나다 => 트랜잭션을 지원한다.
=> 이와 반대로 MyISAM은 테이블 기반 잠금만 제공한다. 트랜잭션은 지원하지 않는다
4.2.1. 프라이머리 키에 의한 클러스터링
InnoDB의 모든 테이블을 기본적으로 프라이머리 키를 기준으로 클러스터링 되어 저장된다
클러스터링 ? 데이터에 대해 유사성을 가진 애들끼리 연관 있는 그룹으로 분류하는 기법
=> 즉. 프라이머리 키 값의 순서대로 디스크에 저장된다는 뜻이며, 모든 세컨더리 인덱스는 레코드의 주소 대신 프라이머리 키의 값을 논리적인 주소로 사용한다.
프라이머리 키가 클러스터링 인덱스이기 때문에, 프라이머리 키를 이용한 레인지 스캔은 상당이 빨리 처리 될 수 있다.
하지만 MyISAM 스토리지 엔진은 클러스터링 키를 지원하지 않는다.
4.2.3. MVCC(Multi Version Concurrency Control)
- 일반적으로 레코드 레벨의 트랜잭션을 지원하는 DBMS가 제공하는 기능이며,
- MVCC의 큰 목적은 잠금을 사용하지 않는 일관된 읽기를 제공하는데 있다.
- InnoDB에서는 언두 로그를 이용해 이 기능을 구현한다.
- 언두 로그는 트랜잭션 격리를 위해 레코드에 대한 여러 버전을 저장해두는 로그다.
- COMMIT 명령이 실행되면 InnoDB는 더 이상 변경 작업 없이 지금의 상태를 영구적인 데이터로 만들어 버린다.
- 롤백을 실행하면 InnoDB는 언두 영역에 있는 백업된 데이터를 InnoDB 버퍼 풀로 다시 복구하고, 언두 영역의 내용을 삭제해 버린다.
- 커밋이 된다고 언두 영역의 백업 데이터가 항상 바로 삭제되는 것은 아니며, 언두 영역을 필요로 하는 트랜잭션이 더는 없을 때 삭제된다.
4.2.4. 잠금없는 일관된 읽기
- InnoDB 스토리지 엔진은 MVCC 기술을 이용해 잠금을 걸지 않고 읽기 작업을 수행한다.
- 격리 수준이 SERIALIZABLE이 아닌 READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ 수준인 경우 INSERT와 연결되지 않은 순수한 읽기 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다.
- InnoDB에서는 변경되기 전의 데이터를 읽기 위해 언두 로그를 사용한다.
- 오랜 시간 동안 활성 상태인 트랜잭션으로 인해 MySQL 서버가 느려지거나 문제가 발생할 때가 가끔 있는데, 일관된 읽기를 위해 언두 로그를 삭제하지 못하고 계속 유지해야 하기 때문에 발생하는 문제다.
- 따라서 트랜잭션이 시작됐다면 가능한 한 빨리 롤백이나 커밋을 통해 트랜잭션을 완료하는 것이 좋다.
4.2.5 자동 데드락 감지
- InnoDB 엔진은 내부적으로 잠금이 교착 상태에 빠지지 않았는지 체크하지 위해 잠금 대기 목록을 그래프 형태로 관리한다.
- InnoDB 스토리지 엔진은 데드락 감지 스레드를 가지고 있어서 데드락 감지 스레드가 주기적으로 잠금 대기 그래프를 검사해 교착 상태에 빠진 트랜잭션들을 찾아서 그중 하나를 강제 종료한다.
이 때 어느 트랜잭션을 먼저 강제 종료할 것 인지를 판단하는 기준은 트랜잭션의 언두 로그 양이며, 언두 로그 레코드를 더 적게 가진 트랜잭션이 일반적으로 롤백의 대상이 된다.
=> 언두 레코드를 적게 가졌다는 이야기는 롤백을 해도 언두 처리를 해야할 내용이 적다는 것이며, 트랜잭션 강제 롤백으로 인한 MySQL 서버의 부하도 덜 유발하기 때문이다.
4.2.7. InnoDB 버퍼 풀
- InnoDB 엔진에서 가장 핵심적인 부분으로, 디스크릐 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간이다.
- 쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 버퍼 역할도 같이 한다.
INSERT, UPDATE, DELETE처럼 데이터를 변경하는 쿼리는 데이터 파일에 랜덤한 디스크 작업을 발생시키는데, 버퍼 풀은 이러한 변경된 데이터를 모아서 처리하므로 랜덤한 디스크 작업의 횟수를 줄일 수 있다.
- InnoDB 버퍼 풀은 내부적으로 128MB 청크 단위로 쪼개어 관리된다. 이는 버퍼 풀의 크기를 줄이거나 늘리기 위한 단위 크기로 사용된다
4.2.7 버퍼 풀의 구조
- InnoDB 스토리지 엔진은 버퍼 풀이라는 메모리 공간을 페이지 크기의 조각으로 쪼개어 InnoDB 스토리지 엔진이 데이터를 필요로 할 때 해당 데이터 페이지를 읽어서 각 조각에 저장한다.
- 버퍼 풀의 페이지 크기 조각을 관리하기 위해 LRU, 플러시, 프리 리스트라는 3개의 자료 구조를 관리한다.
1) 프리(Free)리스트 : InnoDB 버퍼 풀에서 실제 사용자 데이터로 채워지지 않은 비어있는 페이지들의 목록, 사용자의 쿼리가 새롭게 디스크의 데이터 페이지를 읽어와야 하는 경우 사용됨
2) LRU리스트 :
엄밀하게 말해서 LRU와 MRU 리스트가 결합된 형태로 보면 된다.
Old 서브리스트 영역은 LRU에 해당하며, New 서브리스트 영역은 MRU로 이해하면 된다.
LRU 리스트를 관리하는 목적은 디스크로부터 한 번 읽어온 페이지를 최대한 오랫동안 InnoDB 버퍼 풀의 메모리에 유지해서 디스크 읽기를 최소화 하는 것이다.
그래서 처음 한 번 읽힌 데이터 페이지가 이후 자주 사용된다면 그 데이터 페이지는 InnoDB 버퍼 풀의 MRU 영역에서 계속 살아남게 되고, 반대로 사용되지 않으면 새롭게 디스크에서 읽히는 데이터 페이지들에 밀려서 LRU 끝으로 밀려나 결국 InnoDB 버퍼 풀에서 제거된다.
3) 플러시리스트 : 디스크로 동기화되지 않은 데이터를 가진 데이터 페이지(더티페이지)의 변경시점 기준의 페이지 목록을 관리한다.
InnoDB 스토리지 엔진은 체크포인트를 발생시켜 디스크의 리두 로그와 데이터 페이지의 상태를 동기화하게 된다.
체크포인트는 MySQL 서버가 시작될 때 InnoDB 스토리지 엔진이 리두 로그의 어느 부분부터 복구를 실행해야 할지 판단하는 기준점을 만드는 역할을 한다.
4.2.7 버퍼풀과 리두로그
- InnoDB의 버퍼 풀은 서버의 메모리가 허용하는 만큼 크게 설정하면 할수록 쿼리의 성능이 빨라진다.
- InnoDB 버퍼 풀은 디스크에서 읽은 상태로 전혀 변경되지 않은 클린페이지와 함께 변경된 데이터를 가진 더티 페이지도 가지고 있다.
- 더티 페이지는 디스크와 메모리의 데이터 상태가 다르기 때문에 언젠가는 디스크로 기록돼야 한다.
- 리두 로그 파일의 공간은 계속 순환되어 재사용되지만, 매번 기록될때마다 로그 포지션은 계속 증사된 값을 갖게 되는데, 이를 LSN 이라고 한다.
- InnoDB 스토리지 엔진은 주기적으로 체크포인트 이벤트를 발생시켜 리두 로그와 버퍼 풀의 더티 페이지를 디스크로 동기화하는데, 이렇게 발생한 페크포인트 중 가장 최근 체크포인트 지점의 LSN이 활성 리두 로그 공간의 시작점이 된다.
- 가장 최근 체크포인트의 LSN과 마지막 리두 로그 엔트리의 LSN의 차이를 체크포인트 에이지(Checkpoint Age)라고 한다. 즉, 체크포인트 에이지는 리두 로그 공간의 크기를 말한다.
- 리두 로그는 변경분만 가지고 버퍼 풀은 데이터 페이지를 통째로 가지기 때문에 데이터 변경이 발생해도 리두 로그는 훨씬 작은 공간만 있으면 된다.
4.2.7 버퍼 풀 플러시(Buffer Pool Flush)
- InnoDB 스토리지 엔진은 버퍼 풀에서 아직 디스크로 기록되지 않은 더티 페이지들을 성능상의 악영향 없이 디스크에 동기화하기 위해 2개의 플러시 기능을 백그라운드로 실행한다.
=> 플러시 리스트 플러시 / LRU 리스트 플러시
플러시 리스트 플러시
- InnoDB 스토리지 엔진은 리두 공간의 재활용을 위해 주기적으로 오래된 리두 로그 엔트리가 사용하는 공간을 비워야 하는데, 오래된 리두 로그 공간이 지워지려면 반드시 InnoDB 버퍼풀의 더티 페이지가 먼저 디스크로 동기화돼야 한다.
이를 위해 InnoDB 스토리지 엔진은 주기적으로 플러시 리스트 함수를 호출해서 플러시 리스트에서 오래전에 변경된 데이터 페이지 순서대로 디스크에 동기화 하는 작업을 수행한다.
- 언제부터 얼마나 많은 더티 페이지를 한 번에 디스크로 기록하느냐에 따라 사용자의 쿼리 처리가 악영향을 받지 않으면서 부드럽게 처리된다.
LRU 플러시
InnoDB 스토리지 엔진은 LRU 리스트에서 사용 빈도가 낮은 데이터 페이지들을 제거해서 새로운 페이지들을 읽어올 공간을 만들어야 하는데, 이를 위해 LRU 리스트 플러시 함수가 사용된다.
InnoDB 스토리지 엔진은 LRU 리스트의 끝부분부터 시작해서 스캔하여 더티 페이지는 디스크에 동기화하게 하며, 클린 페이지는 즉시 프리 리스트로 페이지를 옮긴다.
언두 로그
- InnoDB 스토리지 엔진은 트랜잭션과 격리 수준을 보장하기 위해 DML로 변경되기 이전 버전의 데이터를 별도로 백업한다. 이런 데이터를 언두로그라고 한다.
- 언두로그는 보통 트랜잭션 보장 / 격리 수준을 보장할 때 쓰인다.
1) 트랜잭션 보장 : 트랜잭션이 롤백되면 트랜잭션 도중 데이터를 변경 전 데이터로 백업해야하는데, 이때 언두로그 데이터를 이용해서 복구한다.
2) 격리수준 보장 : 특정 커넥션에서 데이터를 변경하는 도중에 다른 커넥션에서 데이터를 조회하면 트랜잭션 격리 수준에 맞게 변경중인 레코드를 읽지 않고 언두 로그에 백업해둔 데이터를 읽어서 반환한다.
체인지 버퍼
- InnoDB는 변경해야 할 인덱스 페이지가 버퍼 풀에 있으면 바로 업데이트를 수행하지만 그렇지 않고 디스크로부터 읽어와서 업데이트해야 한다면 이를 즉시 실행하지 않고 임시 공간에 저장해 두고 바로 사용자에게 결과를 반환하는 형태로 성능을 향상 시키게 되는데, 이때 사용하는 임시 메모리 공간을 체인지 버퍼라고 한다.
리두 로그 및 로그 버퍼 (변경된 이력을 저장하는?)
- 리두 로그는 트랜잭션의 4가지 요소인 ACID 중에서 D에 해당하는 영속성과 가장 밀접하게 연관되어 있다.
- 리두 로그는 하드웨어나 소프트웨어 등 여러 가지 문제점으로 인해 MySQL 서버가 비정상적으로 종료됐을 때 데이터 파일에 기록되지 못한 데이터를 잃지 않게 해주는 안전장치다.
- 성능 저하를 막기 위해 데이터베이스 서버는 쓰기 비용이 낮은 자료 구조를 가진 리두 로그를 가지고 있으며, 비정상 종료가 발생하면 리두 로그의 내용을 이용해 데이터 파일을 다시 서버가 종료되기 직전의 상태로 복구한다.
MyISAM 스토리지 엔진 아키텍처
- 키 캐시
InnoDB의 버퍼 풀과 비슷한 역할을 한다.
하지만 이름 그대로 MyISAM 키 캐시는 인덱스만을 대상으로 작동하며, 인덱스의 디스크 쓰기 작업에 대해서만 부분적으로 버퍼링 역할을 한다.
- 운영체제의 캐시 및 버퍼
MyISAM 테이블의 인덱스는 키 캐시를 이용해 디스크를 검색하지 않고도 충분히 빠르게 검색할 수 있지만, MySIAM 테이블의 데이터에 대해서는 디스크로부터의 I/O를 해결해 줄만한 어떠한 캐시나 버퍼링 기능도 가지고 있지 않다.
따라서 MyISAM 테이블의 데이터 읽기나 쓰기 작업은 항상 운영체제의 디스크 읽기 또는 쓰기 작업으로 요청될 수밖에 없다.
최근의 운영체제에는 디스크로부터 읽고 쓰는 파일에 대한 캐시나 버퍼링 메커니즘을 탑재하고 있기 때문에 MySQL 서버가 요청하는 디스크 읽기 작업을 위해 매번 디스크의 파일을 읽지는 않는다.
하지만 운영체제의 캐시 공간은 남는 메모리를 사용하는 것이 기본 원칙이므로, 데이터베이스에서 MyISAM 테이블을 주로 사용한다면 운영체제가 사용할 수 있는 캐시 공간을 위해 충분한 메모리를 비워둬야 한다.
- 데이터 파일과 프라이머리 키 구조
InnoDB 스토리지 엔진을 사용하는 테이블은 프라이머리 키에 의해서 클러스터링되어 저장되는 반면, MyISAM 테이블은 프라이머리 키에 의한 클러스터링 없이 데이터 파일이 힙 공간처럼 활용된다.
즉, MySIAM 테이블에 레코드는 프라이머리 키 값과 무관하게 INSERT 되는 순서대로 데이터 파일에 저장된다.
MyISAM 테이블에 저장되는 레코드는 모두 ROWID라는 물리적인 주소값을 가지는데, 프라이머리 키와 세컨더리 인덱스는 모두 데이터 파일에 저장된 레코드의 ROWID 값을 포인터로 가진다.
'DB > MySQL' 카테고리의 다른 글
[MYSQL] Real MySQL 9장 - 옵티마이저 (0) | 2024.03.13 |
---|---|
[MYSQL] Real MySQL 8장 - 인덱스 (0) | 2024.03.10 |
[MYSQL] Real MySQL 5장 - 트랜잭션 (0) | 2024.02.21 |