[CS Study] Database - MySQL Architecture

Frye 'de Bacon·2023년 12월 21일
0

Computer Science(CS)

목록 보기
34/40

MySQL Architecture

MySQL 서버는 사람의 두뇌 역할을 하는 MySQL Engine과 손발과 같은 역할을 하는 Storage engine으로 구분할 수 있다.
이때 스토리지 엔진의 경우 MySQL에서는 기본적으로 InnoDB 스토리지 엔진MyISAM 스토리지 엔진을 제공한다. 그러나 핸들러 API를 만족한다면 누구나 스토리지 엔진을 구현하여 MySQL 서버에 추가해 사용할 수도 있다.

MySQL engine

MySQL 엔진은 클라이언트로부터의 접속 및 쿼리 요청을 분석 및 최적화하여 처리하는 역할을 담당하며 다음과 같은 요소로 구성된다.

  • 커넥션 핸들러(Connection handler) : 클라이언트로부터의 접속 및 쿼리 요청을 처리
  • SQL 인터페이스(Interface) : DML, DDL, Produce, View 등 SQL 인터페이스 제공
  • SQL 파서(Parser) : 쿼리를 파싱하여 Tree 형태로 변환하고, 쿼리에 문법적인 오류가 있는지를 판별
  • 전처리기 : 파서로부터 전달받은 쿼리 Tree를 기반으로 예약어가 아닌 요소들의 유효성을 검증(테이블 접근 권한 및 테이블 혹으 컬럼의 존재 여부 판별 등)
  • 옵티마이저(Optimizer) : 쿼리를 최적화하여 실행하기 위한 작업 수행
    ※ 각 구성 요소에 대한 자세한 내용은 아래에 별도로 정리한다.

Storage engine

MySQL 엔진이 요청된 SQL 문장을 분석하거나 최적화하는 등의 처리를 수행한다면, 스토리지 엔진은 실제 데이터를 디스크 스토리지에 저장하거나, 혹은 반대로 디스크 스토리지로부터 데이터를 읽어 오는역할을 한다.
각 스토리지 엔진은 성능 향상을 위한 기능을 내장하고 있는데, InnoDB의경우 버퍼 풀을, MyISAM의 경우 키 캐시를 내장하고 있다.

  1. MyISAM Engine
    • MySQL 5.5 이전 버전까지 사용되었던 기본 스토리지 엔진
    • 커밋, 롤백 및 충돌 복구 기능과 table-level의 lock을 제공하지만 트랜잭션은 제공되지 않음
    • 데이터를 매우 효율적으로 저장하여 전체 속도는 InnoDB보다 빠르나, lock이 table-level이므로 갱신 작업 속도가 느려 write 작업이 없는 테이블에 적합함
    • 트랜잭션이 제공되지 않으므로 데이터 무결성을 보장하지 못함
  2. InnoDB
    • MySQL 5.5버전 이후부터 사용되는 기본 스토리지 엔진
    • 커밋, 롤백 및 충돌 복구 기능과 row-level의 lock을 제공하며, MyISAM 엔진과 달리 트랜잭션을 제공함
    • row-level의 lock을 사용하여 MyISAM 대비 갱신 작업 속도가 빠르며, 따라서 write 작업이 많은 테이블에 적합함
    • MVCC 기능을 지원하여 동시성이 뛰어남
    • 트랜잭션을 제공하므로 데이터 무결성을 보장함

Handler API

MySQL 엔진의 쿼리 실행기에서 데이터를 쓰거나 읽을 때는 각 스토리지 엔진에 쓰기 또는 읽기를 요청하는데, 이러한 요청을 핸들러(Handler) 요청이라고 한다. 그리고 핸들러 요청에 사용되는 API를 핸들러 API라고 한다.
InnoDB 등의 스토리지 엔진 역시 핸들러 API를 이용해 MySQL 엔진과 데이터를 주고받는다.


MySQL thread structure

MySQL 서버는 프로세스가 아닌 스레드 기반으로 동작하며, 크게 Foreground threadBackground thread로 구분할수 있다.

Foreground thread

포그라운드 스레드(Foreground thread)는 DBMS의 앞단에서 사용자와 통신하는 스레드로서 클라이언트 스레드(Client thread)라고도 한다.
최소한 MySQL 서버에 접속한 클라이언트의 수만큼 존재하며, 주로 각 클라이언트가 요청하는 쿼리 문장을 처리한다.

작업 중이던 사용자가 작업을 마치고 커넥션을 종료하면 해당 커넥션을 담당하던 스레드는 다시 스레드 캐시(Thread cache)로 돌아가는데, 이때 스레드 캐시에 일정 개수 이상의 대기 중인 스레드가 존재하면 이 스레드는 스레드 캐시로 돌아가지 않고 종료되어 스레드 캐시에는 일정 개수의 스레드만 존재하게 된다. 스레드 캐시에 유지할 수 있는 최대의 스레드 개수는 시스템 변수 thread_cache_size로 설정할 수 있다.
※ 스레드 캐시에 불필요하게 너무 많은 스레드가 있으면 메모리 자원을 낭비하게 된다.

포그라운드 스레드는 데이터를 MySQL의 데이터 버퍼 혹은 캐시로부터 가져오고, 만약 버퍼나 캐시에 데이터가 없다면 직접 디스크의 데이터를 가져오거나 인덱스 파일로부터 데이터를 읽어와 작업을 처리한다.

MyISAM 스토리지 엔진의 경우 데이터를 기록하는 작업 전체를 포그라운드 스레드가 처리한다. 그러나 InnoDB 스토리지 엔진의 경우 데이터 버퍼나 캐시까지만 포그라운드 스레드가 처리하고, 이후 버퍼로부터 디스크까지 기록하는 작업은 백그라운드 스레드가 처리한다.

백그라운드 스레드(Background thread)

InnoDB 스토리지 엔진에서는 다음과 같은 여러 작업을 백그라운드 스레드로 처리한다.

  • 인서트 버퍼(Insert buffer)를 병합하는 스레드
  • 로그(log)를 디스크로 기록하는 스레드
  • InnoDB 버퍼 풀의 데이터를 디스크로 기록하는 스레드
  • 데이터를 버퍼로 읽어 오는 스레드
  • 잠금이나 데드락을 모니터링하는 스레드

이 스레드 중 가장 중요한 것은 로그를 디스크로 기록하는 로그 스레드(log thread)와 버퍼의 데이터를 디스크로 기록하는 쓰기 스레드(write thread)일 것이다.

MySQL 5.5버전부터 데이터 쓰기 스레드와 데이터 읽기 쓰레드의 개수를 2개 이상으로 지정할 수 있게 되었으며, innodb_write_io_threadsinnodb_read_io_threads 시스템 변수를 이용하여 스레드의 개수를 설정할 수 있다. 특히 쓰기 스레드의 경우 아주 많은 작업을 백그라운드로 처리하므로 그 수를 여유롭게 설정하는 것이 좋다. 보통 일반적인 내장 디스크를 사용한다면 2~4 정도, DAS나 SAN과 같은 스토리지를 사용할 경우 디스크를 최적으로 사용할 수 있을 만큼 사용하는 것이 좋다.

사용자의 요청을 처리할 때 쓰기 작업은 지연 처리가 가능하지만 읽기 작업은 지연 처리가 불가능하다. 따라서 일반적인 상용 DBMS는 대부분 쓰기 작업을 버퍼링하여 일괄 처리하는 기능이 탑재되어 있으며, InnoDB 역시 그러한 방식을 사용한다. 따라서 InnoDB에서는 DML(INSERT, UPDATE, DELETE)로 데이터를 변경하는 경우 데이터가 디스크에 완전히 저장될 때까지 기다릴 필요가 없다.


MySQL 메모리 할당 및 사용 구조

MySQL에서 사용되는 메모리 공간은 크게 글로벌 메모리 영역로컬 메모리 영역으로 구분된다.

글로벌 메모리 영역

글로벌 메모리 영역의 모든 메모리 공간은 MySQL 서버가 시작됨과 동시에 운영체제로부터 할당되는 영역이다. 필요에 따라 2개 이상의 메모리 공간을 할당받을 수 있지만 일반적으로는 하나만 할당되며, 글로벌 영역이 많다고 하더라도 모든 스레드에 의해 공유된다.
대표적인 글로벌 메모리 영역은 다음과 같다.

  • 테이블 캐시
  • InnoDB 버퍼 풀
  • InnoDB 어댑티브 해시 인덱스
  • InnoDB 리두 로그 버퍼

로컬 메모리 영역

로컬 메모리 영역은 MySQL 서버상에 존재하는 포그라운드 스레드가 쿼리를 처리하는 데 사용되는 메모리 영역이다. 대표적으로 커넥션 버퍼와 정렬(sort) 버퍼 등이 있다.

사용자가 MySQL 서버에 접속하면 MySQL 서버에서는 클라이언트 커넥션으로부터의 요청을 처리하기 위해 스레드를 하나씩 할당하며, 이처럼 클라이언트 스레드가 사용하는 메모리 공간이라고 하여 클라이언트 메모리 영역이라고도 한다.
※ 클라이언트와 MySQL 서버 간의 연결을 세션이라고 하므로 세션 메모리 영역이라고 불리기도 한다.

로컬 메모리 영역은 각 클라이언트 스레드별로 독립적으로 할당되며, 스레드 간 메모리는 절대 공유되지 않는다. 또 한 가지의 중요한 특징은 각 쿼리의 용도별로 필요할 때만 공간이 할당되고, 필요하지 않은 경우에는 메모리 공간이 아예 할당되지 않을 수도 있다는 것이다.

커넥션 버퍼결과 버퍼의 경우 커넥션이 열려 있는 동안 계속 메모리 공간이 할당된 상태로 남아 있는다. 반면 조인 버퍼소트 버퍼 등은 쿼리를 실행하는 순간에만 메모리 공간이 할당되고, 쿼리가 종료되면 해제된다.


플러그인 아키텍처 & 컴포넌트 아키텍처

플러그인 아키텍처

플러그인 아키텍처는 MySQL의 대표적인 특징 중 하나이다. 스토리지 엔진만 플러그인에서 사용할 수 있는 것은 아니고, 검색 엔진을 위한 검색어 파서나 사용자 인증을 위한 Native Authentication 등도 플러그인으로 구현되어 제공된다.

MySQL은 이미 많은 스토리지 엔진을 가지고 있지만, 사용자의 필요에 따라 추가로 다른 형태의 스토리지 엔진을 개발하여 사용하는 것도 가능하다. 또는 이미 플러그인 형태로 빌드된 스토리이 엔진의 라이브러리를 다운로드해서 사용하는 것도 가능하다.

다만 플러그인 아키텍처의 경우 다음과 같은 단점이 있었다.

  • 오직 MySQL 서버와 인터페이스할 수 있고 플러그인끼리는 통신할 수 없다.
  • 플러그인이 MySQL 서버의 변수나 함수를 직접 호출하므로 보안 등에서도 안전성 문제가 있다.
  • 플러그인은 상호 의존 관계를 설정할 수 없어 초기화가 어렵다.

이러한 단점을 해결하고자 등장한 것이 컴포넌트 아키텍처이다.

컴포넌트 아키텍처

MySQL 8.0부터 기존의 플러그인 아키텍처를 대체하기 위하여 컴포넌트 아키텍처가 지원된다. 컴포넌트 아키텍처는 플러그인 아키텍처가 가진 일부 문제를 극복하기 위해 설계된 것이다.

컴포넌트는 서비스 API를 통해 다른 구성 컴포넌트와 통신할 수 있으며, 다른 컴포넌트가 사용할 서비스 API를 구현할 수도 있다.


쿼리 실행 과정

MySQL에서 쿼리가 실행되는 과정을 간략하게 표현하면 다음 그림과 같이 나타낼 수 있다. 대부분의 작업은 MySQL 엔진에서 처리되고, 데이터의 읽기 및 쓰기 작업만 스토리지 엔진에서 처리된다. 그리고 MySQL이 스토리지 엔진을 컨트롤하기 위해 사용하는 것이 '핸들러'이다.

쿼리의 실행 과정을 조금 더 자세히 나누어 살펴보자.

쿼리 파서

사용자의 요청으로 들어온 쿼리 문장을 토큰(MySQL이 인식할 수 있는 최소 단위의 어휘 혹은 기호)으로 분리하여 트리 형태의 구조, 즉 파서 트리(Parser tree)로 만들어내는 작업을 수행한다.
그리고 이 과정에서 쿼리 문장의 기본 문법 오류를 확인하여, 오류가 있을 경우 사용자에게 오류 메시지를 전달한다.

전처리기

파싱 과정으로 만들어진 파서 트리를 기반으로 쿼리 문장에 '구조적인' 문제가 있는지를 확인한다. 즉, 각 토큰을 테이블 이름이나 칼럼 이름 혹은 내장 함수 등과 같은 객체에 매핑하여 해당 객체의 존재 여부 및 객체에 대한 접근 권한 등을 확인한다. 그리고 실제로 존재하지 않거나 권한상 사용할 수 없는 객체의 토큰은 전처리기를 통해 걸러낸다.

옵티마이저

옵티마이저는 쿼리의 실행 과정 중 성능 측면에서 가장 중요한 과정이라고도 할 수 있다. 옵티마이저는 사용자의 요청으로 들어온 쿼리 문장을 '가장 저렴한 비용'으로 '가장 빠르게' 처리하는 방법을 결정하는 역할을 담당한다.

옵티마이저가 쿼리를 최적화할 때는 규칙 기반 최적화 또는 비용 기반 최적화 방식 중 하나를 선택하여 쿼리의 최적화를 진행하는데, MySQL의 옵티마이저는 비용 기반최적화 방식을 사용한다.

  • 규칙 기반 최적화 : 데이터의 상태는 고려하지 않고, 옵티마이저에 내장된 우선순위대로 최적화하는 방식
  • 비용 기반 최적화 : 쿼리를 처리하기 위한 여러 방법 중 테이블의 통계 정보를 이용하여 DB에 부하가 적은 방향으로 쿼리를 최적화하는방식

실행 엔진

옵티마이저가 DBMS의 두뇌 역할을 한다면 실행 엔진과 핸들러는 손과 발에 해당한다.
실행 엔진은 옵티마이저가 쿼리를 최적화하여 만들어낸 실행 계획대로 핸들러에게 요청을 보내고, 또 핸들러로부터 받은 결과를 다른 핸들러 요청의 입력으로 연결하는 역할을 수행한다.

핸들러(스토리지 엔진)

핸들러는 MySQL 서버의 가장 밑단에서 요청에 따라 데이터를 디스크에 저장하거나 혹은 디스크로부터 읽어들이는 역할을 담당한다. 즉, 핸들러는 결국 스토리지 엔진을 의미한다.


참고 자료

profile
AI, NLP, Data analysis로 나아가고자 하는 개발자 지망생

0개의 댓글