Architecture(아키텍처)
MySQL 서버는 크게 MySQL 엔진과 스토리지 엔진으로 구성된다.
MySQL 엔진: 클라이언트로부터 접속 및 쿼리 요청을 처리
커넥션 헨들러 / SQL 파서 / 전처리기 / 옵티마이저 등을 중심으로 구성
하나의 MySQL 서버에는 하나의 MySQL 엔진만 존재
스토리지 엔진: 실제 데이터를 디스크 스토리지에 저장하고 읽어오는 역할
하나의 MySQL 서버에는 여러 개의 스토리지 엔진을 동시에 사용할 수 있음
스레딩 구조
MySQL 서버는 프로세스 기반이 아닌 스레드 기반으로 동작하며, 포그라운드(Foreground) 스레드와 백그라운드(Background) 스레드로 구분된다.
포그라운드 스레드(클라이언트 스레드): 클라이언트 사용자가 요청하는 쿼리 문장을 처리하는 스레드
최소 MySQL 서버에 접속된 클라이언트 수만큼 존재
요청을 처리 후 커넥션을 종료하면 해당 커넥션을 담당하는 스레드는 다시 스레드 캐시(Thread Cache)에 반환
여기서 이미 스레드 캐시에 일정 개수 이상의 대기 스레드가 있으면, 캐시에 넣지 않고 스레드를 종료 시켜 일정 개수의 스레드만 캐시에 유지
캐시에 유지되는 최대 스레드 개수는
thread_cache_size
시스템 변수로 설정 가능
백그라운드 스레드: MySQL 서버의 여러 작업을 처리하는 스레드
인서트 버퍼(Insert Buffer)를 병합하는 스레드
로그를 디스크로 기록하는 스레드(Log Thread)
InnoDB 버퍼 풀의 데이터를 디스크에 기록하는 스레드(Write Thread)
데이터를 버퍼로 읽어 오는 스레드
잠금이나 데드락을 모니터링하는 스레드
메모리 구조
MySQL 서버는 글로벌 메모리 영역과, 로컬 메모리 영역으로 구분된다.
글로벌 메모리 영역: 클라이언트 스레드 수와 무관하게 하나의 메모리 공간만 할당되며, 모든 클라이언트 스레드가 공유하는 메모리 영역
로컬 메모리 영역: 세션 메모리 영역이라고도 하며, MySQL 서버상에 존재하는 클라이언트 스레드가 쿼리를 처리하는 데 사용하는 메모리 영역
쿼리 실행 구조
MySQL 서버는 클라이언트로부터 쿼리를 받으면 다음과 같은 과정을 거쳐 쿼리를 처리한다.
SQL 요청
쿼리 파서
사용자 요청으로 들어온 쿼리 문장을 토큰(MySQL 인식 최소 단위)을 분리해 트리 형태 구조로 만드는 작업
쿼리 문장의 기본 문법을 검사하고, 문법에 맞지 않는 문장이면 에러를 반환
전처리기
파서 과정에서 만들어진 파서 트리를 기반으로 쿼리 문장에 구조적인 문제점 있는지 검사
각 토큰을 테이블 이름이나 칼럼 이름 또는 내장 함수와 같은 개체를 매핑해 해당 객체 존재 여부 및 접근 권한 등 확인
옵티마이저
쿼리 문장을 저렴한 비용으로 처리할 수 있는 최적의 실행 계획을 생성
쿼리 실행 엔진
옵티마이저의 명령을 받아 각 핸들러에 쿼리를 전달
스토리지 엔진(핸들러)
서버 가장 밑단에서 MySQL 실행 엔진의 요청에 따라 데이터를 읽고 쓰는 역할
스토리지 엔진은 핸들러를 의미
SQL 응답
InnoDB 스토리지 엔진 아키텍처
InnoDB는 MySQL에서 가장 많이 사용되는 스토리지 엔진으로, MySQL 스토리지 엔진 중 거의 유일하게 레코드 기반 잠금 제공하여 높은 동시성 처리가 가능하다. InnoDB의 주요 특징들과 장점은 아래와 같다.
PK 기반 저장
PK 기반으로 테이블에 저장되기 때문에 PK가 필수적으로 존재해야 함
실제론 PK 없이 테이블을 생성할 수 있지만, 내부적으론 아래의 규칙에 따라 PK로 사용할 컬럼을 결정
지정된 PK를 사용
PK가 없다면 NOT NULL + 유니크 인덱스 중 첫 번째 컬럼을 PK로 사용
둘 다 해당하지 않는다면 AUTO_INCREMENT를 사용한 유니크 컬럼을 추가하여 PK로 사용(노출되지 않으며 쿼리에서 사용 불가능)
PK에 의한 클러스터링
유사한 PK(Primary Key)끼리 물리적인 위치에 클러스터링하여 저장하여 PK 값의 순서대로 디스크에 저장되어 있어 PK를 이용한 레인지 스캔을 우선적으로 사용
장점
PK만으로 데이터 레코드를 찾아올 수 있어 다른 인덱스를 통한 조회보다 빠름
연속 조회 시에도 디스크에 PK 값의 순서대로 저장되어 랜덤 IO가 아닌 순차 IO로 읽어오기 때문에 성능 향상
실행 계획에서 다른 보조 인덱스보다 PK를 이용한 레인지 스캔을 우선적으로 사용
단점
물리적인 위치를 순차적으로 유지해야 하기 때문에 레코드의 삽입/삭제 시 성능 저하
모든 인덱스가 PK에 의존하여 PK 값이 큰 경우 인덱스의 크기가 커지게 되면서 페이지의 양도 많아짐
인덱스를 통한 PK와 레코드 접근
리프노드에 저장된 PK 값으로 실제 레코드를 읽어오는 방식으로 동작하여, 두 번의 작업이 필요
인덱스 키를 통해 리프 노드를 찾아 PK 값을 찾아냄
PK 값을 통해 실제 레코드를 찾아냄
반면에, MyISAM은 리프노드가 데이터를 바로 가리키고 있음
이 방식은 PK가 변경될 때 레코드의 주소가 변경되는데 그 때마다 모든 인덱스에 저장된 레코드 주소를 변경해야 하기 때문에 성능 저하가 발생
외래키 지원
외래키를 이용한 무결성 제약 조건을 지원하여 데이터 무결성 보장
foreign_key_checks
시스템 변수를 OFF 하여 제약 조건에 대한 검사를 생략할 수 있게 변경 가능
언두 로그(Undo log)
DML 작업을 수행할 때마다 이전 버전의 데이터를 별도로 백업하는 기능
트랜잭션을 롤백해야할 때 이전 데이터를 복구하기 위해 사용
트랜잭션 격리 수준에 맞게 데이터를 읽어오기 위해 사용
DML을 실행한 트랜잭션이 완료될 때 삭제되는 것이 아닌, 해당 레코드에 접근한 모든 트랜잭션이 완료될 때 삭제
MVCC(Multi-Version Concurrency Control) 지원
언두 로그를 이용하여 하나의 레코드에 대해 여러 개의 버전을 관리하는 기능
여러 버전이 있기 때문에 트랜잭션 격리 수준에 따라 다른 레코드를 읽어 올 수 있음
잠금 없는 일관된 읽기(Non-Locking Consistent Read) 지원
다른 트랜잭션이 가지고 있는 잠금을 기다리지 않고 읽기 작업을 수행할 수 있는 기능
MVCC 기능을 사용해 이용하여 잠금된 레코드의 언두 로그를 읽어오는 방식으로 구현
락을 걸지 않는 순수한 SELECT 쿼리를 사용하여 읽기 작업을 수행할 때 사용됨
자동 데드락 감지
InnoDB 스토리지 엔진의 데드락 감지 스레드가 주기적으로 데드락을 감지
일반적으로 데드락이 발생하면 교착 상태에 빠진 트랜잭션 중 언두 로그가 가장 작은 트랜잭션을 롤백(롤백 시 처리량이 가장 적기 때문)
장애 복구 자동화
손실이나 장애로부터 데이터를 보호하기 위한 기능이 내장되어, 장애 복구를 위한 자동화된 기능을 제공
InnoDB 버퍼 풀
InnoDB의 가장 핵심적인 부분으로, 디스크 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간
쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 버퍼 역할도 수행
DML 작업 시 변경 데이터를 모아서 처리하게 해주어 랜덤 디스크 작업을 줄여 성능 향상
체인지 버퍼
레코드가 INSERT / UPDATE 될 때 인덱스를 변경해야하는데, 이 때 디스크 I/O 작업이 생겨 성능 저하가 발생
변경해야 할 인덱스 페이지를 디스크에 기록하는 체인지 버퍼라는 임시 공간에 저장하여 지연시켜 성능 향상
체인지 버퍼에 저장된 변경 내용은 백그라운드 스레드에 의해 반영
유니크 인덱스는 중복 여부를 체크해야 하기 때문에 체인지 버퍼 사용이 불가능
어댑티브 해시 인덱스
자주 읽히는 데이터 페이지의 키 값을 이용해 해시 인덱스를 생성하여 빠르게 읽을 수 있게 해주는 기능
B-Tree 검색 시간을 줄여주기 위해 도입된 기능으로, 리프 노드까지 탐색하는 비용을 줄여주어 속도 향상 및 자원 사용량 감소 효과
그 외
MySQL 8.0 이전에는 쿼리 캐시 기능이 존재했으나, 현재 8.0 버전에서는 동시 처리 성능 저하로 인해 완전히 제거되고 사용하지 않음
참고자료
Last updated
Was this helpful?