MySQL 서버
MySQL 서버는 크게 `MySQL 엔진` 과 `스토리지 엔진` 으로 나뉜다. MySQL 엔진이 사람의 머리 역할을 하고 스트로지 엔진은 손발 역할을 하는데 구성 요소는 다음과 같다.
MySQL 엔진
클라이언트 접속 및 쿼리 요청을 처리하는 `커넥션 핸들러`, `SQL 파서 및 전처리기`, `옵티마이저` 가 해당된다.
스토리지 엔진
실제 데이터를 디스크 스토리지에 저장하거나 디스크 스토리지로부터 데이터를 읽어오는 부분이다. MySQL 엔진은 하나지만 스트리지 엔진은 여러 개 동시 사용 가능하다.
MySQL 엔진은 `핸들러 API` 를 통해 스토리지 엔진에 읽기 쓰기 요청을 하는데, 그렇게 되면 스토리지 엔진의 `핸들러` 가 실제 액션을 실행한다.
MySQL 스레딩 구조
MySQL 서버는 프로세스 기반이 아니라 `스레드 기반`으로 동작한다. 그리고 스레드는 크게 `포그라운드 스레드`와 `백그라운드 스레드`로 나뉘는데 스토리지 엔진이 뭔지에 따라 역할이 조금 다르다.
포그라운드 (클라이언트) 스레드
- 최소한 MySQL 서버에 접속된 클라이언트의 수만큼 존재
- 각 클라이언트가 요청하는 쿼리 문장 처리
- 커넥션을 종료하면 스레드 캐시로 되돌아가는데 이미 스레드 캐시에 최대 스레드 개수만큼 존재한다면, 스레드를 종료시킴
- MySQL 데이터 버퍼나 캐시로부터 데이터를 가져옴
- MyISAM 의 경우 버퍼나 캐시에 없는 경우 포그라운드 스레드가 직접 디스크에 접근하지만, InnoDB 는 백그라운드 스레드가 처리
백그라운드 스레드
MyISAM 의 경우에는 포그라운드 쓰레드가 읽기/쓰기 작업을 하기 때문에 해당 없지만 InnoDB 의 경우 다음과 같은 작업이 처리된다.
- 인서트 버퍼를 병합하는 스레드
- 로그를 디스크로 기록하는 스레드
- InnoDB 버퍼 풀의 데이터를 디스크에 기록하는 스레드
- 데이터를 버퍼로 읽어오는 스레드
- 잠금이나 데드락을 모니터링하는 스레드
사용자의 요청을 처리하는 도중 데이터의 쓰기 작업은 버퍼링(지연)되어 처리될 수 있지만 데이터의 읽기 작업은 절대 지연될 수 없다. 그렇기 때문에 대부분의 DBMS 에는 쓰기 작업을 버퍼링해서 일괄 처리하는 기능이 탑재되어있다.
- InnoDB 에서는 데이터가 디스크의 데이터 파일로 완전히 저장될 때까지 기다리지 않아도 됨
- MyISAM 에서 일반적인 쿼리는 버퍼링 기능 사용 불가. 대신 포그라운드 스레드에서 직접 쓰기 작업을 처리
메모리 할당 및 사용 구조
글로벌 메모리 영역
모든 스레드에 의해 공유되는 공간이다.
- 테이블 캐시
- InnoDB 버퍼 풀
- InnoDB 어댑티브 해시 인덱스
- InnoDB 리두 로그 버퍼
로컬 메모리 영역
세션 메모리 영역라고도 불리며 클라이언트 스레드가 쿼리를 처리하는데 사용하는 메모리 영역이다. 스레드별로 독립적으로 할당되며 절대 공유되지 않는다는 특징이 있다.
커넥션이 열려있는 동안 할당된 상태로 남아있을 수도 있으며 (커넥션 버퍼, 결과 버퍼), 쿼리를 실행하는 순간에만 할당했다가 다시 해제될 수도 있다. (소트 버퍼나 조인 버퍼)
- 조인 버퍼
- 정렬 버퍼
- 네트워크 버퍼
- 리드 버퍼
플러그인 스토리지 엔진 모델
MySQL 은 세상의 수많은 사용자 요구조건을 만족시킬 수는 없기에 따로 개발해서 플러그인 형태로 주입하는 형태을 지원한다. 이를 `플러그인 아키텍처`라고 부른다. 스토리지 엔진, 전문 검색 엔진, 사용자 인증 플러그인 등이 존재하며 이를 직접 서버에 설치해서 사용하면 된다. 하지만 플러그인은 다음과 같은 단점이 존재하기 때문에 MySQL 8.0 부터는 기존의 플러그인 아키텍처를 대체하기 위해 `컴포넌트 아키텍처`가 지원된다.
플러그인 단점
- 플러그인은 오직 MySQL 서버와 인터페이스할 수 있고, 플러그인끼리는 통신 불가
- 플러그인은 MySQL 서버의 변수나 함수를 직접 호출하기 때문에 안전하지 않음 (캡슐화 안됨)
- 플러그인은 상호 의존 관계를 설정할 수 없어서 초기화가 어려움
쿼리 실행 구조
쿼리가 실행되는 과정을 쿼리 파서, 전처리기, 옵티마이저, 쿼리 실행기, 핸들러로 나눠서 살펴보도록 하자. 중요한 것은 각 부분이 어디에 해당하는지 (MySQL 엔진 또는 스토리지 엔진) 구별하는 것이다.
1. 쿼리 파서
사용자 요청으로 들어온 쿼리 문장을 토큰으로 분리해 트리 형태의 구조로 만들어 내는 작업을 한다. 이렇게 만들어진 트리를 `파서트리` 라고 한다. 쿼리 문장의 기본 문법 오류는 이 과정에서 발견되고 사용자에게 오류메세지 전달한다.
2. 전처리기
파서 트리를 기반으로 쿼리 문장에 구조적인 문제점이 있는지 점검하는 구간이다. 테이블 이름, 칼럼 이름, 또는 내장 함수와 같은 개체를 매핑해 해당 객체의 존재 여부와 객체의 접근 권한을 확인한다. 존재하지 않거나 권한상 사용할 수 없는 개체의 토큰은 이 단계에서 걸러져 오류메세지를 전달한다.
3. 옵티마이저
쿼리를 가장 저렴한 비용으로 어떻게 빠르게 처리할지 결정하는 역할을 하는 구간으로 MySQL 엔진의 두뇌와 같은 곳이다.
4. 실행 엔진
`옵티마이저`에서 내려진 결정을 실행하는 중간 관리자에 해당된다. `실행엔진`(중간관리자)가 지시한 업무를 `핸들러`가 실무자로서 직접 실행한다. 그리고 실행엔진은 만들어진 계획대로 각 핸들러에게 요청해서 받은 결과를 또 다른 핸들러 요청의 입력으로 연결하는 역할을 한다.
예를 들어, 옵티마이저가 group by 를 처리하기 위해 임시 테이블을 사용하기로 결정한 경우를 살펴보도록 하자.
- 실행 엔진이 핸들러에게 임시 테이블 만들라고 요청
- 다시 실행 엔진은 where 절에 일치하는 레코드를 읽어오라고 핸들러에게 요청
- 읽어온 레코드를 1번에서 준비한 임시 테이블로 저장하라고 핸들러에게 요청
- 데이터가 준비된 임시 테이블에서 필요한 방식으로 데이터를 읽어오라고 핸들러에게 다시 요청
- 최종적으로 실행 엔진은 결과를 사용자나 다른 모듈로 넘김
5. 핸들러 (스토리지 엔진)
실행엔진의 요청에 따라 데이터를 디스크로 저장하고 디스크로부터 읽어오는 역할을 한다. 스토리지 엔진 영역에 해당되는 구간이다.
🔍 쿼리 캐시?
캐시 기능은 조회 성능에 도움이 되었지만 데이터가 변경되면 캐시에 저장된 결과 중에서 변경된 데이터들을 invalidate 해야되는데 이 과정에서 성능 저하 문제 발생했다.
따라서, MySQL 8.0 으로 올라오면서 쿼리 캐시 기능은 제거되었다.
버퍼 풀과 리두 로그
`버퍼 풀`은 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해두는 공간이다. 따라서 조회하는 데이터가 버퍼 풀에 존재하는 경우 디스크 IO 없이 즉각적으로 조회가 가능하다. 또한, 쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있도록 변경된 데이터를 버퍼 풀에 저장해두는데 이를 `더티 페이지` 라고 한다. 이렇게 모인 여러 개의 더티 페이지를 디스크에 한 번에 동기화시켜주는 작업을 하도록 한다.
하지만 만약에 데이터 변경이 일어나서 버퍼 풀에는 더티 페이지로 반영되었지만 디스크에 반영이 되지 못한 상태로 서버가 다운되면 어떻게 될까? 이렇게 되면 변경된 데이터는 손실이 되기 때문에 `리두 로그 (Redo Log)` 가 존재한다. 데이터가 변경되면 변경점이 버퍼 풀에 반영됨과 동시에 로그 버퍼를 통해 리두 로그 파일로 저장된다. 그렇기 때문에 디스크에 반영이 되지 않고 서버가 꺼져버리면 리두 로그 파일을 확인하면 되기 때문에 리커버리가 가능한 것이다.
'💻Computer Science > Database' 카테고리의 다른 글
[Database] MySQL 인덱스 (0) | 2024.06.24 |
---|---|
[Database] B-Tree 와 B+Tree 데이터베이스 (2) | 2023.08.05 |