티스토리 뷰

MySQL 아키텍처

MySQL의 전체 구조

MySQL 서버는 크게 MySQL 엔진과 스토리지 엔진으로 구분해서 볼 수 있다.

  • MySQL 엔진 : 클라이언트로부터의 접속 및 쿼리 요청을 처리하는 커넥션 핸들러, SQL 파서 및 전처리기, 그리고 쿼리의 최적화된 실행을 위한 옵티마이저가 중심을 이룬다. 또한 성능 향상을 위해 캐시나 버퍼 풀과 같은 보조 저장소 기능이 포함돼 있다.
  • 스토리지 엔진 : 위의 MySQL 엔진이 쿼리 분석 및 최적화 등의 두뇌 활동을 담당하고, 실제 데이터를 디스크에 저장하거나 디스크에서 데이터를 읽어오는 부분은 스토리지 엔진이 처리한다.
    • MySQL 서버에서 MySQL 엔진은 하나지만 스토리지 엔진은 여러 개를 동시에 사용할 수 있다.
    • 'CREATE TABLE test_table (col1 INT, col2 INT) ENGINE = INNODB' 와 같은 식으로 테이블을 생성하면, 해당 테이블의 모든 CRUD 작업은 InnoDB 스토리지 엔진이 처리한다.
  • 핸들러 API : MySQL 엔진의 쿼리 실행기에서 데이터의 쓰기/읽기가 필요한 경우에는 스토리지 엔진에게 쓰기/읽기를 요청하는데, 이러한 요청을 핸들러 요청이라고 하고, 여기서 사용되는 API 를 핸들러 API 라고 한다.

MySQL 스레딩 구조

MySQL 서버는 프로세스 기반이 아니라 스레드 기반으로 작동하며, 크게 포그라운드(Foreground) 스레드와 백그라운드(Background) 스레드로 구분할 수 있다.

  • 포그라운드 스레드 (클라이언트 스레드) : 최소 서버에 접속한 클라이언트의 수만큼 존재하며, 각 사용자가 요청한 쿼리 문장을 수행하는 역할을 갖고 있다.
    • 사용자가 작업을 마치고 커넥션을 종료하면, 해당 커넥션을 담당하던 스레드는 스레드 캐시(Thread Pool)로 돌아가는데, 이 때 스레드 캐시에 일정 개수 이상의 대기 스레드가 있으면 해당 스레드는 종료시킨다.
    • thread_cache_size 가 해당 스레드 캐시의 스레드 수를 설정하는 값이다.
    • 데이터를 데이터 버퍼나 캐시로부터 가져오며, 해당 부분에 데이터가 없는 경우에는 디스크나 인덱스로부터 데이터를 읽어와서 작업을 처리한다.
  • 백그라운드 스레드 : InnoDB의 경우에는 여러 작업이 백그라운드로 처리된다.
    • 인서트 버퍼를 병합하는 스레드, 로그를 디스크로 기록하는 스레드, InnoDB 버퍼 풀의 데이터를 디스크에 기록하는 스레드, 데이터를 버퍼로 읽어들이는 스레드, 잠금이나 데드락을 모니터링하는 스레드, 이 모든 것을 총괄하는 메인 스레드 등이 있다.
    • 가장 중요한 것은 로그 스레드와 버퍼의 데이터를 디스크로 내려쓰는 작업을 하는 쓰기 스레드이다.

메모리 할당 및 사용 구조

MySQL 에서 사용되는 메모리 공간은 크게 글로벌 메모리 영역과 로컬 메모리 영역으로 나눌 수 있다.
글로벌 메모리 영역의 모든 메모리 공간은 MySQL 서버가 시작되면서 무조건 OS로부터 할당된다.

  • 글로벌 메모리 영역 : 클라이언트 스레드 수와 무관하게 일반적으로 하나의 메모리 공간만 할당된다.
  • 로컬 메모리 영역 : 세션 메모리 영역이라고도 한다. 클라이언트 스레드가 쿼리를 처리하는 데 사용하는 메모리 영역이다.
    • 대표적으로 커넥션 버퍼와 소트(정렬) 버퍼가 있다.
    • 각 클라이언트 별로 독립적으로 할당된다.

플러그인 스토리지 엔진

MySQL 에서 쿼리가 실행되는 과정은 다음과 같다.

  1. SQL 파서
  2. SQL 옵티마이저
  3. SQL 실행기
  4. 데이터 읽기/쓰기
  5. 디스크 스토리지

이 중에서 1~3번과 같이 대부분의 작업이 MySQL 엔진에서 처리되고, 4번 데이터 읽기/쓰기 작업만 스토리지 엔진에 의해 처리된다.
비유하자면 MySQL 엔진은 사람 역할, 각 스토리지 엔진은 자동차 역할인데, 이 때 MySQL 엔진이 스토리지 엔진을 조정하기 위한 객체를 핸들러라고 한다.

중요한 것은 하나의 쿼리 작업이 여러 하위 작업으로 나뉘는데, 각 하위 작업이 MySQL 엔진 영역에서 처리되는지 아니면 스토리지 엔진 영역에서 처리되는지 구분할 줄 아는 것이다.
예를 들어 실질적인 GROUP BY나 ORDER BY 등 많은 복잡한 처리는 스토리지 엔진 영역이 아니라 MySQL 엔진의 쿼리 실행기에서 처리된다.

쿼리 실행 구조

쿼리가 실행되는 순서대로 각 부분에 대해 알아보자.

  • 파서 : 사용자 요청으로 들어온 쿼리 문장을 토큰으로 분리해 트리 형태의 구조로 만들어 내는 작업이다.
    • 쿼리 문장의 기본 문법 오류는 이 과정에서 발견된다.
  • 전처리기 : 파서 트리를 기반으로 쿼리 문장에 구조적인 문제점을 확인한다.
    • 각 토큰을 테이블 이름, 칼럼 이름, 내장 함수 등을 매핑해서 해당 객체의 존재 여부와 접근 권한 등을 확인한다.
  • 옵티마이저 : DBMS의 두뇌로, 쿼리 문장을 어떻게 저렴한 비용으로 가장 빠르게 처리할지 결정하는 역할을 한다.
  • 실행 엔진 : 만들어진 계획대로 각 핸들러에게 요청해서 받은 결과를 또 다른 핸들러 요청의 입력으로 연결하는 역할을 한다.
  • 핸들러(스토리지 엔진) : MySQL 서버의 가장 밑단에서 실행 엔진의 요청에 따라 데이터를 디스크로 저장하고 읽어오는 역할을 한다.

복제

복제는 2대 이상의 MySQL 서버가 동일한 데이터를 담도록 실시간으로 동기화하는 기술이다.

  • 마스터 : MySQL 의 바이너리 로그가 활성화되면 마스터가 될 수 있다.
    • 마스터 서버에서 실행되는 DML, DDL 중 데이터의 구조나 내용을 변경하는 모든 쿼리 문장은 바이너리 로그로 기록되었다가, 슬레이브 서버에서 변경 내역을 요청하면 해당 로그를 읽어 슬레이브로 넘긴다.
  • 슬레이브 : 바이너리 로그를 받아올 마스터의 접속 정보를 가지고 있는 경우 슬레이브가 된다.
    • 슬레이브 서버는 릴레이 로그를 가지고 있다.
    • 보통 읽기 전용 설정을 활성화한다.
    • 슬레이브 서버는 마스터에 접속해 변경 내역을 받아서 릴레이 로그에 기록하고, SQL 스레드가 릴레이 로그에 기록된 변경 내역을 재실행함으로써 데이터를 마스터와 동기화한다.

복제에서 주의해야 하는 점은 다음과 같은 것들이 있다.

  • 슬레이브는 하나의 마스터만 설정 가능
  • 데이터 동기화를 위해 슬레이브는 읽기 전용으로 설정
  • 슬레이브 서버는 마스터와 동일한 사양이 적합
    • 슬레이브가 마스터보다 사양이 더 적어도 될 것이라 생각할 수도 있지만, 마스터에서 수많은 사용자가 동시 실행한 데이터 변경 쿼리를 슬레이브에서 하나의 스레드로 처리해야하기 때문에 오히려 슬레이브가 사양이 더 높아야 한다.
    • 하지만 데이터 변경이 조회보다는 현저히 적기 때문에 마스터와 슬레이브 서버는 보통 같은 사양으로 구성하면 된다.
  • 복제가 불필요한 경우에는 바이너리 로그를 중지한다.
    • 바이너리 로그는 성능에 많은 영향을 준다.

InnoDB 스토리지 엔진 아키텍처

InnoDB 는 MySQL 에서 사용할 수 있는 스토리지 엔진 중에서 거의 유일하게 레코드 기반 잠금을 제공하며, 덕분에 높은 동시성 처리가 가능하고 안정적이며 성능이 뛰어나다.

InnoDB 스토리지 엔진의 특성

  • 프라이머리 키에 의한 클러스터링 : InnoDB의 모든 테이블은 기본적으로 프라이머리 키를 기준으로 클러스터링되어 저장된다.
    • 프라이머리 키 값의 순서대로 디스크에 저장되어 프라이머리 키에 의한 레인지 스캔은 상당히 빠르게 처리된다.
  • 잠금이 필요 없는 일관된 읽기 : MVCC(Multi Version Concurrency Control) 라는 기술을 통해 락을 걸지 않고 읽기 작업을 수행한다.
    • InnoDB에서의 읽기 작업은 다른 트랜잭션이 가지고 있는 락을 기다리지도 않는다.
  • 외래 키 지원 : InnoDB 에서 외래 키는 부모 테이블과 자식 테이블 모두 해당 칼럼에 인덱스 생성이 필요하고, 변경 시에는 반드시 부모 테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요하므로 잠금이 여러 테이블로 전파되고, 이로 인해 데드락이 발생할 때가 많다.

InnoDB 버퍼 풀

가장 핵심적인 부분으로 크게 두 가지 역할을 한다.

MyISAM 키 캐시가 인덱스의 캐시만을 주로 처리하는 데 비해 InnoDB 버퍼 풀은 디스크의 데이터 파일과 인덱스 정보 모두를 메모리에 캐시한다.
또한 쓰기 작업을 지연시켜 일괄 작업으로 처리할 수 있게 해주는 버퍼 역할도 한다.
데이터를 변경하는 쿼리는 데이터 파일의 이곳저곳에 위치한 레코드를 변경하기 때문에 랜덤한 디스크 작업을 발생시키는데, 버퍼 풀이 이러한 변경된 데이터를 모아서 처리하면 랜덤한 디스크 작업의 횟수를 줄일 수 있다.

일반적으로 전체 물리 메모리의 50% ~ 80% 수준에서 버퍼 풀의 메모리 크기를 결정한다.

언두(Undo) 로그

UPDATE 나 DELETE 로 데이터를 변경했을 때 변경되기 전의 데이터를 보관하는 곳이다.
크게 두 가지 용도로 사용되는데, 첫 번째는 트랜잭션의 롤백 대비용이고, 두 번째는 트랜잭션의 격리 수준을 유지하면서 높은 동시성을 제공하는 데 사용된다.

인서트 버퍼

INSERT, UPDATE 시에는 데이터 파일의 변경 작업뿐 아니라 해당 테이블에 포함된 인덱스를 업데이트하는 작업도 필요하다.
InnoDB는 변경해야 할 인덱스 페이지가 버퍼 풀에 있으면 바로 업데이트하지만, 디스크로부터 결과를 가져와야 한다면 임시 공간에 저장해두고 사용자에게는 바로 결과를 반환하는 식으로 성능을 향상시킨다.
이 때 사용하는 임시 공간을 인서트 버퍼 라고 한다.

리두(Redo) 로그 및 로그 버퍼

변경된 데이터를 버퍼링하기 위한 버퍼 풀이 있어도 데이터의 ACID 를 보장하기는 어렵기 때문에, 변경된 내용을 순차적으로 기록하는 로그 파일인 리두 로그가 존재한다.
리두 로그는 사람의 눈으로 열어서 읽을 수 없기 때문에 굳이 열어볼 필요는 없다.

리두 로그 덕분에 DBMS 는 버퍼링을 통해 한꺼번에 디스크에 변경된 내용을 처리할 수 있게 되어 상당한 성능 향상을 얻었다.
다만 데이터 변경 작업이 많은 서버의 경우에는 리두 로그 기록 작업이 오히려 문제가 되는데, 이를 보완하기 위해 최대한 ACID 속성을 보장하는 수준에서 버퍼링하게 된다.
이 리두 로그 버퍼링에 사용되는 공간이 로그 버퍼다.

ACID 는 데이터베이스에서 트랜잭션의 무결성을 보장하기 위해 반드시 필요한 4가지 요소를 의미한다.

A : Atomic. 트랜잭션은 원자성 작업
C : Consistent. 일관성
I : Isolated. 격리성
D : Durable. 한번 저장된 데이터는 지속적으로 유지돼야 한다.

일관성과 격리성은 서로 다른 두 개의 트랜잭션에서 동일 데이터를 조회하고 변경하는 경우에도 상호 간섭이 없어야 한다는 의미이다.

MVCC (Multi Version Concurrency Control)

MVCC 의 가장 큰 목적은 잠금을 허용하지 않는 일관된 읽기 를 제공하는 데 있다.
언두 로그를 이용해 이 기능을 구현한다.

예를 들어 특정 데이터 레코드를 1번 사용자가 'A' 내용에서 'B' 내용으로 UPDATE 한다고 가정해 보자.
이 때의 상황은 다음과 같다.

  • InnoDB 버퍼 풀 : 수정된 'B' 내용이 반영되어 있다. (레코드 전체)
  • Undo 로그 : 수정 전의 'A' 내용이 반영되어 있다. (PK, 메타정보 및 수정된 칼럼만 백업)

이 때 커밋이나 롤백이 아직 일어나지 않는 상황에서, 2번 사용자가 해당 데이터를 읽으려고 하면 어떻게 될까?

이 질문의 답은 격리 수준에 따라 다르다.

  • READ_UNCOMMITTED : InnoDB 버퍼 풀이나 데이터 파일로부터 변경된 'B' 데이터를 읽어서 반환한다.
  • READ_COMMITTED 이상 : 아직 커밋되지 않았기 때문에 언두 영역의 'A' 데이터를 반환한다.

이러한 과정을 DBMS 에서는 MVCC 라고 표현한다.

잠금 없는 일관된 읽기

격리 수준이 SERIALIZABLE 이 아닌 그 이하의 격리 수준에서는 순수한 읽기 SELECT 작업은 다른 트랜잭션의 변경 작업과 관계 없이 잠금을 실행하지 않고 바로 실행된다.
특정 사용자가 레코드를 변경하고 아직 커밋을 수행하지 않았다 하더라도 이 변경 트랜잭션이 다른 사용자의 SELECT 를 방해하지 않는다.
이를 잠금 없는 일관된 읽기 라고 표현하고, 변경되기 전의 데이터를 읽기 위해 InnoDB 에서는 언두 로그를 사용한다.


참고

위 내용은 이성욱님의 Real MySQL (위키북스) 에서 일부 내용을 간단하게 정리한 것입니다.
세부적인 내용은 책을 직접 구입하셔서 읽어보시는 것을 추천드립니다.

최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday