04. 아키텍처
Last updated
Was this helpful?
Last updated
Was this helpful?
MySQL 엔진: 사람의 머리 역할을 담당
스토리지 엔진: 사람의 손과 발 역할을 담당
스토리지 엔진은 핸들러 API를 만족한다면 누구든지 스토리지 엔진을 구현해서 MySQL 서버에 추가해서 사용 가능하다.
이번 장에서는 MySQL 서버에서 기본으로 제공하는 InnoDB 스토리지 엔진
, MyISAM 스토리지 엔진
에 대해서 알아보자.
단순 MySQL 또는 MySQL 서버라고 표현하는 것이 MySQL 엔진 + 스토리지 엔진의 결합을 말하는 것이다.
커넥션 핸들러: 클라이언트 접속 및 쿼리 요청 처리
SQL 파서 및 전처리기
옵티마이저: 쿼리의 최적화된 실행
표준 SQL 문법(ANSI SQL)을 지원하기 때문에 표준 문법에 따라 작성된 쿼리는 타 DBMS와 호환 가능
실제 데이터를 디스크 스토리지에 저장
디스크 스토리지로부터 데이터를 읽어오는 역할
스토리지 엔진은 MySQL 엔진과는 다르게 여러 스토리지 엔진을 동시에 사용할 수 있다.
각 스토리지 엔진은 성능 향상을 위해 키 캐시(MyISAM)나 InnoDB 버퍼 풀(InnoDB)과 같은 기능을 내장하고 있다.
하지만, 여러 스토리지 엔진을 혼용해서 사용할 경우 혼란스러울 수 있고, 주의할 점이 많아 권장하지는 않는다고 한다.
MySQL 엔진의 쿼리 실행기에서 데이터를 쓰거나 읽어야 할 때 각 스토리지 엔진에 쓰기 또는 읽기 요청을 '핸들러(Handler) 요청'이라고 한다. 이 때 사용되는 API를 핸들러 API라고 하는 것이다.
아래의 명령어를 통해 얼마나 많은 데이터 작업이 있었는지 확인할 수 있다.
포그라운드 스레드(클라이언트 스레드)
각 클라이언트 사용자가 요청하는 쿼리 문장을 처리
MySQL 서버에 클라이언트가 접속하게 되면 그 클라이언트의 요청을 처리해 주는 스레드를 의미함
데이터를 MySQL의 데이터 버퍼나 캐시로부터 가져오며, 그렇지 못할 경우 직접 디스크의 데이터나 인덱스 파일로부터 데이터를 읽어와 작업을 처리한다.
백그라운드 스레드
InnoDB는 다음과 같이 여러 가지 작업이 백그라운드로 처리된다.
Insert Buffer 병합 스레드
로그를 디스크로 기록하는 스레드
InnoDB 버퍼 풀의 데이터를 디스크에 기록하는 스레드
데이터를 버퍼로 읽어오는 스레드
잠긍미나 데드락을 모니터링하는 스레드
이 중에서 쓰기 스레드(Write Thread)의 역할이 중요하다. 데이터를 읽는 작업은 주로 클라이언트 스레드에서 처리되기 때문에 읽기 스레드는 많이 설정할 필요가 없지만 쓰기 스레드의 경우 아주 많은 작업을 백그라운드로 설정하기 때문에 스레드의 개수를 충분히 설정하는 것이 좋다.
데이터의 쓰기 작업은 지연(버퍼링)되어 처리될 수 있지만, 데이터의 읽기 작업은 절대 지연될 수 없다.
상용 DBMS에서는 대부분 쓰기 작업을 버퍼링해서 일괄 처리하는 기능이 탑재되어 있으며, InnoDB 또한 이런 방식이다.
따라서 InnoDB에서
INSERT
,UPDATE
,DELETE
쿼리로 데이터가 변경되는 경우 데이터 파일로 완전히 저장될 때 까지 기다리지 않아도 되지만, MyISAM의 경우 일반적인 쿼리에서 쓰기 버퍼링 기능을 사용할 수 없다. 여기서 궁금한건, 쓰기 버퍼링 기능이 무조건적으로 좋은 역할일가? 데이터 정합성과는 상관없나? 캐시가 없었다면?
글로벌 메모리 영역은 MySQL 서버가 시작되면서 운영체제로부터 할당된다.
테이블 캐시
InnoDB 버퍼 풀
InnoDB 어댑티브 해시 인덱스
InnoDB 리두 로그 버퍼
클라이언트 스레드 수와 무관하게 하나의 메모리 공간만 할당된다. 단, 필요에 따라 2개 이상의 메모리 공간을 할당받을 수도 있지만 클라이언트의 스레드 수와는 무관하며 생성된 글로벌 영역 개수와는 무관하게 모든 스레드에 의해 공유된다.
정렬 버퍼(Sort Buffer)
조인 버퍼
바이너리 로그 캐시
네트워크 버퍼
세션 메모리 영역이라고도 표현하며, 클라이언트 스레드가 쿼리를 처리하는데 사용하는 메모리 영역이다. 즉, 각 클라이언트 스레드별 독립적으로 할당되며 절대 공유되어 사용되지 않는다.
각 쿼리의 용도별로 필요할 때만 공간이 할당되고 필요하지 않은 경우에는 MySQL이 메모리 공간을 할당조차 하지 않을 수도 있다.
MySQL 서버에 부가적인 기능을 제공해주는 것으로, 필요에 따라 사용자가 직접 개발하는 것도 가능하다.
MySQL 서버에서 MySQL 엔진이 스토리지 엔진을 조정하기 위해 핸들러(Handler)라는 것을 사용하게 된다. 즉, MySQL 엔진이 각 스토리지 엔진에게 데이터를 읽어오거나 저장하도록 명령하려면 반드시 핸들러를 통해야 한다.
MySQL 8.0부터 지원하는 기능으로, 기존 플러그인 아키텍처를 대체한다.
플러그인은 오직 MySQL 서버와 인터페이스할 수 있고, 플러그인끼리는 통신할 수 없음
플러그인은 MySQL 서버의 변수나 함수를 직접 호출하기 때문에 안전하지 않음 (캡슐화 안 됨)
플러그인은 상호 의존 관계를 설정할 수 없어서 초기화가 어려움
이러한 단점을 보완한 것이 컴포넌트인 것이다.
쿼리 파서
쿼리 문장을 토큰으로 분리해 트리 형태의 구조로 만들어 냄.
기본 문법 오류는 해당 과정에서 발생한다.
전처리기
파서 트리를 기반으로 쿼리 트리의 구조적인 문제점이 있는지 확인
객체의 존재 여부와 접근 권한 등을 확인하는 과정
옵티마이저
쿼리 문장을 가장 저렴한 비용으로 처리하는 역할 담당
옵티마이저가 더 나은 선택을 유도하는게 중요
실행 엔진
만들어진 계획대로 각 핸들러에게 요청해서 받은 결과를 또 다른 핸들러 요청의 입력으로 연결하는 역할 수행
핸들러 (스토리지 엔진)
MySQL 실행 엔진의 요청에 따라 데이터를 디스크로 저장하고 디스크로부터 읽어 오는 역할
빠른 응답을 필요로하는 웹 기반의 응용 프로그램에서 매우 중요한 역할을 담당했다.
SQL 실행 결과를 메모리에 캐시
동일 SQL 쿼리가 실행되면 테이블을 읽지 않고 즉시 결과를 반환
하지만, 캐시의 문제점이 그렇듯 테이블의 변경사항이 발생했을 때 관련된 정보를 삭제해야하며 이는 심각한 동시 처리 성능 저하를 유발하고 많은 버그의 원인이 되기도 했다.
MySQL 8.0으로 올라오면서 해당 기능은 삭제되었다는 것만 알아두자.
내부적으로 사용자의 요청을 처리하는 스레드 개수를 줄여서 동시 처리되는 요청이 많다하더라도 MySQL 서버의 CPU가 제한된 개수의 스레드 처리에만 집중할 수 있게 해서 서버의 자원 소모를 줄이는 것이 목적이다.
커넥션 풀, 스레드 풀과 같은 풀(pool)을 사용하는 이유, 목표가 뭘까?
InnoDB는 MySQL 스토리지 엔진 중 거의 유일하게 레코드 기반의 잠금을 제공하며, 그 때문에 높은 동시성 처리가 가능하고 안정적이며 성능이 뛰어나다. 이는 뒤에서 설명될 MVCC와 많은 관련이 있다.
InnoDB의 모든 테이블은 기본적으로 PK를 기준으로 클러스터링되어 저장된다. 이는 PK 값의 순서대로 디스크에 저장된다는 뜻이며, 모든 세컨더리 인덱스는 레코드의 주소 대신 PK의 값을 논리적인 주소로 사용한다.
기본적으로 다른 보조 인덱스에 비해 비중이 높게 설정된다. (카디널리티 측면에서 보면 당연한 것 같다.)
InnoDB에서 외래 키는 부모 테이블과 자식 테이블 모두 해당 컬럼에 인덱스 생성이 필요하고, 변경 시에는 반드시 부모 테이블이나 자식 테이블에 데이터가 있는지 확인하는 작업이 필요하므로 잠금이 여러 테이블로 전파되고, 그로 인해 데드락이 발생할 때가 많으므로 개발할 때도 외래 키의 존재에 주의하는 것이 좋다.
실제로 현업 환경에서 이러한 문제로 인해, FK를 잘 사용하지 않는 방향으로 개발하는 경우도 많다고 한다.
레코드 레벨이 트랜잭션을 지원하는 DBMS가 제공하는 기능으로, 잠금을 사용하지 않는 일관된 읽기를 제공하는 것이 목적이다. InnoDB에서는 언두 로그(undo log)
를 이용해 이 기능을 구현한다.
여기서 Multi Version
이란 하나의 레코드에 대해 여러 개의 버전이 동시에 관리된다는 의미이다. InnoDB 버퍼 풀과 언두 로그를 적절히 조합해서 잠금 없는 일관된 읽기(Non-Locking Consistent Read)가 가능한 것이다.
MVCC 기술을 이용해 InnoDB에서 읽기 작업은 다른 트랜잭션이 가지고 있는 잠금을 기다리지 않고, 읽기 작업이 가능하다. SERIALIZABLE
이 아닌 격리 수준에서 순수한 읽기(SELECT) 작업의 경우 항상 작므을 대기하지 않고 바로 실행된다.
Lock이 데드락에 빠지지 않았는지 체크하기 위해 잠금 대기 목록을 그래프(Wait-for List) 형태로 관리하며, 주기적으로 검사해 교착 상태에 빠진 트랜잭션을 찾아서 그 중 언두 로그 레코드를 더 적게 가진 트랜잭션을 롤백시킨다.
InnoDB 스토리지 엔진 중 핵심적인 부분으로, 디스크의 데이터 파일이나 인덱스 정보를 메모리에 캐시해 두는 공간이다. 쓰기 작업을 지연시켜 일괄 작업으로 처리한다.
이러한 버퍼 풀은 쿼리의 성능에 매우 밀접하게 연결 돼 있으며, 디스크에 데이터가 버퍼 풀에 적재된 상태를 워밍업(Warming Up)이라고 표현한다.
키 캐시(키 버퍼)를 사용하지만, 데이터 읽기 및 쓰기 작업은 항상 운영체제의 디스크 읽기 또는 쓰기 작업으로 요청된다. InnoDB처럼 PK에 대한 클러스터링 없이 데이터 파일이 힙(Heap) 공간처럼 활용된다.
과거엔 MyISAM이 기본 스토리지 엔진으로 사용되는 경우가 많았지만, MySQL 서버의 모든 시스템 테이블이 InnoDB 스토리지 엔진으로 교체됐고, 모든 기능을 지원하여 MyISAM만이 가지고 있는 장점이 없는 상태이다.