4.1 MySQL 엔진 아키텍처
4.1.1 MySQL의 전체 구조
MySQL 엔진
요청된 SQL 문장을 분석하거나 최적화하는 등 DBMS의 두뇌에 해당하는 처리를 수행한다.
MySQL엔진은 커넥션 핸들러, SQL 파서, 전처리기, 옵티마이저로 구성되어 있다.
또한 MySQL은 표준 SQL 문법을 지원하기 때문에 표준 문법에 따라 작성된 쿼리는 타 DBMS와 호환되어 실행될 수 있다.
스토리지 엔진
디스크 스토리지에 실제 데이터를 저장하고 읽어오는 부분을 전담한다.
하나의 MySQL 서버에서 MySQL 엔진은 하나이지만, 스토리지 엔진은 여러 개를 동시에 사용할 수 있다.
각 스토리지 엔진은 성능 향상을 위해 키 캐시(MyISAM 스토리지 엔진)나 InnoDB 버퍼 풀(InnoDB 스토리지 엔진) 같은 기능을 내장하고 있다.
핸들러 API
MySQL엔진의 쿼리 실행기에서 스토리지 엔진으로의 요청을 핸들러 요청이라고 하고, 이때 사용되는 API를 핸들러 API라고 한다.
SHOW GLOBAL STATUS LIKE 'Handler%' 명령어를 통해 데이터 작업의 양을 알 수 있다.
4.1.2 MySQL 스레딩 구조
MySQL 서버는 프로세스 기반이 아닌 스레드 기반으로 동작하며, 포그라운드와 백그라운드로 구분한다.
MySQL 서버에서 실행중인 스레드 목록은 performance_schema.threads 테이블을 통해 확인할 수 있다.
포그라운드 스레드(클라이언트 스레드)
포그라운드 스레드는 최소한 MySQL 접속된 클라이언트 수만큼 존재하며, 각 클라이언트 사용자가 요청하는 쿼리 문장을 처리한다.
커넥션이 종료된 스레드는 스레드 캐시로 돌아간다. 스레드 캐시에 유지할 수 있는 최대 스레드 개수는 thread_cache_size 시스템 변수로 설정한다.
데이터를 MySQL의 버퍼나 캐시로부터 가져오며, 버퍼나 캐시에 없는 경우 디스크 혹은 인덱스 파일에서 읽어온다.
MyISAM은 디스크 쓰기 작업까지 포그라운드 스레드가 처리하지만, InnoDB는 백그라운드 스레드가 처리한다.
백그라운드 스레드
InnoDB의 백그라운드 작업 목록
•
Insert Buffer 병합
•
로그를 디스크로 기록
•
InnoDB 버퍼 풀의 데이터를 디스크로 기록
•
데이터를 버퍼로 읽기
•
잠금, 데드락 모니터링
innodb_write_io_threads, innodb_read_io_threads를 통해 시스템 변수로 스레드 개수를 설정할 수 있다.
데이터를 읽는 작업은 주로 클라이언트 스레드에서 처리되기 때문에 읽기 스레드를 많이 설정할 필요는 없다.
이에 비해 쓰기 스레드는 많은 작업을 백그라운드로 처리하기 때문에, 일반 내장 디스크에선 2~4 정도, DAS나 SAN과 같은 스토리지에선 충분히 설정하는 것이 좋다.
일반적인 상용 DBMS(InnoDB 포함)에선 읽기 작업과 달리 쓰기 작업은 버퍼링하여 일괄 처리하지만 MyISAM의 경우 일반적인 쿼리는 버퍼링 기능을 사용할 수 없다.
4.1.3 메모리 할당 및 사용 구조
글로벌 메모리 영역
글로벌 메모리 영역은 MySQL 서버가 시작되면서 운영체제로부터 할당된다.
운영체제의 종류에 따라 MySQL 서버의 메모리 할당 방식이 상당히 복잡하기 때문에, MySQL 서버가 사용하는 정확한 메모리의 양을 측정하는 것은 쉽지 않다.
클라이언트 스레드 수와 무관하게 하나의 메모리 공간만 할당되며, 생성된 메모리 영역이 N개라 하더라도 모든 스레드에 의해 공유 된다.
•
테이블 캐시
•
InnoDB 버퍼 풀
•
InnoDB 어댑티브 해시 인덱스
•
InnoDB 리두 로그 버퍼
로컬 메모리 영역
세션 메모리 영역이라고도 하며, MySQL 서버 상에 존재하는 클라이언트 스레드가 쿼리를 처리하는데에 사용하는 메모리 영역이다.
클라이언트 스레드별로 독립적으로 할당되며 절대 공유되지 않는다.
쿼리의 용도별로 필요할 때만 공간이 할당되기 때문에 MySQL이 메모리 공간을 할당조차 하지 않을 수도 있다.
•
정렬 버퍼
•
조인 버퍼
•
바이너리 로그 캐시
•
네트워크 버퍼
4.1.4 플러그인 스토리지 엔진 모델
플러그인 모델은 MySQL의 독특한 구조 중 하나이다.
스토리지 엔진 뿐만 아니라 검색어 파서, 사용자 인증 등도 모두 플러그인으로 구현되어 제공된다.
다음의 사진을 보면 거의 대부분의 작업이 MySQL엔진에서 처리되고, 마지막 ‘데이터 읽기/쓰기’ 작업만 스토리지 엔진에 의해 처리된다.
‘Handler’로 시작하는 변수는 ‘MySQL엔진이 각 스토리지 엔진에게 보낸 명령의 횟수를 의미하는 변수’라고 이해하면 된다.
MySQL에서 MyISAM이나 InnoDB와 같은 다른 스토리지 엔진을 사용하는 테이블에 대해 쿼리를 실행하더라도 MySQL의 처리 내용은 대부분 동일하며, 단순히 데이터 읽기/쓰기 영역의 처리만 차이가 있을 뿐이다.
4.1.5 컴포넌트
MySQL 8.0부터는 기존의 플러그인 아키텍처를 대체하기 위해 컴포넌트 아키텍처가 지원된다.
컴포넌트는 다음의 MySQL 단점들을 보완한다.
•
오직 MySQL 서버와 통신할 수 있고, 플러그인 끼리는 통신할 수 없다.
•
MySQL 변수나 함수를 직접 호출하기 때문에 안전하지 않다.
•
상호 의존 관계를 설정할 수 없어 초기화가 어렵다.
SELECT * FROM mysql.component 명령어를 통해 설치된 컴포넌트를 확인할 수 있다.
mysql> SELECT * FROM mysql.component;
+--------------+--------------------+------------------------------------+
| component_id | component_group_id | component_urn |
+--------------+--------------------+------------------------------------+
| 1 | 1 | file://component_validate_password |
+--------------+--------------------+------------------------------------+
SQL
복사
4.1.6 쿼리 실행 구조
위 구조를 다음과 같이 기능별로 나눠볼 수 있다.
쿼리 파서
사용자 요청으로 들어온 쿼리 문장을 토큰으로 분리하여 트리 형태의 구조로 만들어 내는 작업을 의미한다.
쿼리 문장의 문법 오류는 이 과정에서 발견되어 사용자에게 전달된다.
전처리기
파서 과정에서 만들어진 파서 트리를 기반으로 쿼리에 구조적 문제점이 있는지 확인한다.
쿼리의 테이블, 컬럼, 내장 함수 들을 객체로 매핑하여 각 객체의 존재 여부와 접근 권한을 확인한다.
사용할 수 없는 개체의 토큰은 여기서 걸러진다.
옵티마이저
옵티마이저는 사용자의 요청으로 들어온 쿼리 문장을 가장 빠르고 적은 비용으로 처리하는 방법을 결정하는 역할을 담당한다. DBMS의 두뇌에 해당한다고 볼 수 있다.
실행 엔진
옵티마이저가 두뇌라면 실행 엔진과 핸들러는 손과 발에 비유할 수 있다. 옵티마이저에서 만들어진 계획대로 각 핸들러들을 연결하는 역할을 수행한다.
핸들러(스토리지 엔진)
핸들러는 MySQL 서버의 가장 밑단에서 실행 엔진의 요청에 따라 디스크에 데이터를 저장하고 읽어오는 역할을 담당한다.
결국 스토리지 엔진을 의미하며, MyISAM 테이블을 조작하는 경우에는 MyISAM 스토리지 엔진이 되고, InnoDB 테이블을 조작하는 경우에는 핸들러가 InnoDB 스토리지 엔진이 된다.
4.1.7 복제
매우 중요하므로 추후 따로 작성.
4.1.8 쿼리 캐시
쿼리 캐시는 동일 SQL 쿼리가 실행되면 테이블을 읽지 않고 즉시 결과를 반환하기 때문에 매우 빠른 성능을 보이지만, 데이터의 변경이 빈번히 일어날수록 동시 처리 성능 저하를 유발하게 되었다.
따라서 MySQL은 8.0 버전부터 쿼리 캐시를 삭제했다.
4.1.9 스레드 풀
스레드 풀은 CPU가 제한된 개수의 스레드 처리에만 집중할 수 있도록 서버의 자원 소모를 줄이는 것이 목적이다.
스레드 풀을 사용함으로써 CPU 프로세서의 친화도도 높이고 불필요한 컨텍스트 스위치를 줄여서 오버헤드를 낮출 수 있다.
하지만 CPU 시간을 제대로 확보하지 못하는 경우에는 쿼리 처리가 오히려 더 느려지는 사례도 발생할 수 있으므로 주의하자.
MySQL의 스레드 풀은 엔터프라이즈 에디션에서만 지원한다. 이를 대신하여 플러그인 중 하나인 Percona Server의 스레드 풀을 사용할 수 있다.
Percona Server의 스레드 풀
4.1.10 트랜잭션 지원 메타데이터
데이터베이스 서버의 테이블 구조 정보와 스토어드 프로그램 등의 정보를 데이터 딕셔너리 또는 메타데이터라고 한다.
MySQL 5.7 버전까지는 테이블의 구조를 FRM 파일에 저장하고 일부 스토어드 프로그램 또한 파일 기반으로 관리했다.
하지만 이러한 파일 기반의 메타데이터는 트랜잭션을 지원하지 않기 때문에 생성 또는 변경 도중 서버가 종료되면 일관성이 깨진다.
하지만 MySQL 8.0 버전부터는 이를 해결하기 위해 메타데이터 정보를 모두 InnoDB 테이블에 저장한다.
이는 mysql DB에 저장하고 mysql DB는 mysql.idb 라는 이름의 테이블스페이스에 저장된다.
실제로 information_schema의 테이블 구조와 관련된 테이블은 모두 mysql 테이블의 뷰로 만들어져있는 것을 알 수 있다.
MySQL 8.0 버전부터 데이터 딕셔너리와 시스템 테이블이 모두 트랜잭션 기반의 InnoDB 스토리지 엔진에 저장되도록 개선되면서 이제 스키마 변경 작업 중간에 MySQL 서버가 비정상적으로 종료된다고 하더라도 스키마 변경이 완전한 성공 또는 완전한 실패로 정리된다.