Real My SQL - 3장 아키텍처

김인회·2021년 6월 6일
0

Real My SQL

목록 보기
2/4

MySQL 아키텍처

해당 Part에서는 MySQL의 아키텍처 전체 구조를 따라가며 MySQL가 어떤 식으로 설계되어 있는 지 확인하고 그 안에 녹아있는 성능 확보전략을 살펴본다.

MySQL의 전체 구조는 크게 MySQL 엔진과 스토리지 엔진으로 나누어볼 수 있다.

MySQL의 엔진부에서는 클라이언트가 날린 쿼리 자체를 분석하고 최적화하여 명령을 수행하는 역할을 하고, 스토리지 엔진은 디스크와 직접적으로 연결되어 디스크에서 데이터를 쓰고 읽는 작업을 수행한다.

MySQL 엔진부 --> 버퍼와 캐시 <-- 스토리지 엔진부 --> 디스크

이때 디스크에 직접적으로 접근하여 데이터를 쓰고 가져오는 작업은 시스템에서 상대적으로 많은 비용을 소모해야 하는 작업이다.

즉 스토리지 엔진과 디스크의 접점이 많아지면 많아질수록 결국 전체 시스템 성능은 하락하게 된다.

따라서 MySQL은 디스크와의 접점 회수를 최대한 줄이기 위해서 메모리단에 존재하는 캐시 혹은 버퍼라는 임시저장소를 활용한다.

자주 쓰는 데이터가 있다면 이 버퍼에 넣어두어 디스크까지 접근 할 필요없이 MySQL 엔진부에서 데이터를 바로 가져갈 수 있도록 설계하고, 또한 디스크에 저장해야 할 데이터가 있다면 버퍼에 모아놨다가 적절한 때가 되었을 때 한 번에 디스크에 저장하는 식으로(지연된 쓰기) 동작하면서 최대한 디스크와의 접점을 피한다.

정말로 디스크에 대한 접근이 필요할 때에만 스토리지 엔진부가 디스크에 접근하여 필요한 데이터들을 가져오고, 이 데이터들을 메모리단의 버퍼에 올려놓으면 MySQL의 엔진부에서는 해당 데이터를 가져다 쓰는 식으로 전체 시스템이 작동하게 된다.

MySQL 엔진

MySQL 엔진부에서는 SQL 파서, SQL 옵티마이저, 캐시&버퍼, SQL 인터페이스 등이 위치하고 있으며 쿼리와 관련된 대부분의 모든 작업은 이곳에서 담당한다.

클라이언트가 내린 명령어를 해석하고, 구체적으로 명령을 어떤 식으로 진행시킬지 계획하고 행동하는 부분을 MySQL 엔진부가 처리한다.

시스템 성능과 관련된 부분을 중점으로 데이터베이스를 공부해야 하는 입장에서 MySQL 엔진부의 가장 눈여겨볼만한 곳은 아마 SQL 옵티마이저가 쿼리를 최적화시키는 부분이 될 것이다.

데이터베이스의 성능 개선을 위해서 옵티마이저가 어떤 식으로 쿼리를 최적화하는지 파악하고, 어떤 식의 특정 상황에서 어떠한 쿼리들이 성능적으로 우수하고 최적화된 것인지 파악하는 것이 중요하기 때문이다.

스토리지 엔진(핸들러)

MySQL 엔진부에서는 쿼리를 해석하고 명령을 실행한다. 그리고 주어진 쿼리에 맞게 필요한 데이터를 디스크에서 가져오거나 기록하는 역할은 스토리지 엔진이 맡고있다.

MySQL에서 선택할 수 있는 스토리지 엔진은 MyISAM, Memory, InnoDB, NDB 등이 존재하는데 대부분의 시스템에서는 주로 InnoDB 스토리지를 사용하는 추세이다.

사실 데이터베이스에 저장된 데이터들은 해당 데이터에 접근하는 불특정 다수의 유저에 의해 시시각각 유동적으로 변하고 있다.

따라서 데이터베이스에 저장되어 있는 데이터들을 마냥 고정되어 있는 정적인 정보들이라고 바라볼 수 없다.

내가 데이터베이스에 저장되어 있는 하나의 레코드를 열람하는 그 순간에도 해당 레코드는 누군가에 의해 업데이트되며 변경되고 있을 수도 있다는 것이다.

따라서 시시각각 변하고 있는 데이터베이스를 일관성 있고 안정성 있게 이용하기 위해서 데이터의 접근에 대한 세부적인 규칙을 지정해 줄 필요가 있다.

위와 같은 세부 규칙을 지정하기 위해서 대부분의 데이터베이스 시스템에서는 데이터를 조작하는 작업의 단위(트랜잭션)라는 개념을 이용한다.

특정한 단위를 만들어서(트랜잭션), 단위에 맞게 데이터를 다루고 처리하며 접근에 대한 규칙을 지정하여 안정성을 확보한다.

(MySQL에서 데이터 잠금 수준과 트랜잭션의 고립 수준을 보면 어떠한 세부적인 규칙이 적용되는지 파악할 수 있다)

이러한 부분도 결국 데이터의 접근에 대한 부분을 설정하여 조절하는 부분이므로 상층부의 MySQL 엔진부가 아니라 데이터 관련 작업을 맡고있는 스토리지 엔진부가 담당하고 구현하게 된다.

그렇기 때문에 결국 스토리지 엔진의 특색에 따라서, 전체 데이터베이스 시스템에서 트랜잭션이 성립되는 방향성이 결정된다고 볼 수 있다.

(데이터베이스 시스템에서 무엇보다 중요한 것이 특정 쿼리가 어떠한 결과값을 가져오게 될지 분명하고 명확하게 파악할 수 있어야 된다는 것이다. 그리고 이것을 잘해내기 위해서는 트랜잭션을 잘 파악하고 있어야한다. 유동적으로 변하고 있는 데이터베이스에서 트랜잭션의 세부적인 규칙이 어떻게 특정되어 있지 모른다면 특정 쿼리가 어떠한 결과값을 반환하게 될지 명확하게 알 수 없게 된다.)

InnoDB (MVCC - Multi Version Concurrency Control)

InnoDB의 가장 특징적인 기능 중 하나는 Undo 로그를 이용한 MVCC 기능이다.

MVCC 기능은 그 이름에서도 대략적으로 추측할 수 있듯이 데이터들을 버전별로 기록하여 보관하는 기능을 수행한다.

InnoDB는 Undo 로그라는 캐시메모리(InnoDB 버퍼풀의 레코드 중 하나)에 특정 데이터가 변경된 이력들을 따로 저장하여 보관해 놓는 식으로 이 기능을 구현하였다.

데이터의 구버전들을 그냥 삭제해버리고 마는 것이 아니라 Undo로그라는 저장소에 따로 보관하여 관리한다는 것이다.

(InnoDB는 데이터가 변경이 될때마다(Update), 자신의 버퍼풀에는 최신 데이터 내용을 갱신하고 이전 내용들은 Undo 로그로 이동시키는식으로 이전 버전에 대한 데이터를 따로 보관해둔다)

MVCC 기능을 이용하여 데이터를 버전별로 분류키면서 얻은 가장 큰 장점은 이 버전 분류를 통해 데이터 접근에 대한 세부적인 규칙을 지정할 수 있게 되었다는 것이다.

예를 들어 현재 어떤 트랜잭션이 진행중이고, 그 트랜잭션은 특정 데이터를 변경하고 있는 상황이라고 한 번 가정해보자.

만약 이 변경된 데이터에 대해서 트랜잭션이 완전히 완료된 이후(커밋된 이후)에나 다른 트랜잭션들이 접근하기를 원한다면, 데이터의 트랜잭션이 진행되는 동안은 오로지 Undo 로그의 데이터를 읽어가게끔 지시하면 된다. (READ-UNCOMMITED 보다 높은 격리수준)

반대로 트랜잭션이 완전히 완료되지 않았더라도(커밋 이전) 변동된 데이터를 다른 트랜잭션에서 곧바로 읽어가도 별 상관이 없는 상황이라면, 그냥 InnoDB 버퍼풀에 있는 최신 데이터를 바로 읽어가도록 하는 식으로 데이터 접근에 대한 흐름을 제어할 수 있을 것이다. (READ-UNCOMMITED 격리수준)

더 나아가 Undo 로그에 저장되어 있는 데이터 중 어떠한 버전을 꺼내올 것인지 지정하는 방식으로도 트랜잭션의 고립 수준을 다르게 할 수 있다.

만약 특정 트랜잭션이 시작된 그 시점을 기준으로 데이터를 고정하여 바라보고 싶다면(각각의 트랜젹션들이 데이터에 대한 별개의 시점들을 가지게 하고 싶다면) UNDO 로그에서 자신보다 늦게 태어난 트랜잭션이 만들어낸 데이터 버전은 무시하고 자신보다 이른 트랜잭션의 데이터 버전만을 취하게 하면 된다. (REPEATABLE-READ 격리수준)

이렇게 InnoDB는 Undo 로그를 활용하여 데이터의 접근에 대한 세부적인 규칙을 적용하고 있다.

MVCC는 이렇게 Undo 로그를 이용한 멀티 버전 데이터 체계를 구축했고, 이러한 체계를 이용해 앞에서 예시로 들었던 것과 같이 현재 작업이 진행 중인 데이터들에 대한 접근규정을 이미 정해놓았기 때문에 이 정해진 규정대로만 데이터를 가져오면 된다.

따라서 불필요한 대기(잠금)없이도 그때 그때 정해진 규칙대로 데이터에 바로 접근하여 자료를 읽어올 수 있다는 장점이 있다. (잠금 없는 일관된 읽기 가능 - Non-Locking consistent read)

추가적으로 알아두면 좋을만한 것

  1. MySQL 서버는 프로세스 기반이 아닌 스레드 기반 - 스레드는 크게 포그라운드와 백그라운드로 나누어지며 포그라운드에서는 주로 클라이언트와의 커넥션 및 데이터를 읽어오는 과정을 처리하고, 백그라운드에서는 디스크에 데이터를 쓰는 작업을 대부분 담당한다. 데이터를 읽는 과정과 쓰는 과정이 두 분류의 스레드로 분리되어 있기 때문에 두 과정이 서로를 방해하지 않고 병렬적으로 실행될 수 있다.

  2. 서버의 확장, 이중화 구성에 쓰이는 복제기능 - 같은 데이터를 보유하고 있는 MySQL 서버를 여러 대 두어 기능 장애에 대비하고 부하를 분산시킬 수 있는 HA 구성을 위해 MySQL에는 복제 기능(실시간 동기화)를 지원한다. - 마스터 서버와 슬레이브 서버 // 릴레이 로그와 바이너리 로그

  3. InnoDB의 인서트 버퍼, 로그버퍼 (Redo 로그 - 지연된 쓰기를 구현하기 위해서 데이터를 디스크에 기록하기 전에 버퍼풀에 먼저 저장해 놓는데, 해당 데이터들을 버퍼에서 디스크로 옮겨 기록해야 할 때 올바르게 데이터를 기록할 수 있도록 Redo 로그를 참고한다. Redo 로그에는 변경된 데이터들이 디스크에 올라가야 할 순차적인 순서가 지정되어 있고 이 로그를 활용하여 디스크에 데이터를 안정적으로 기록한다)

  4. MySQL 엔진의 쿼리캐시

  5. InnoDB 스토리지 엔진을 사용하는 경우 지연된 쓰기를 구현하기 위해 디스크에 데이터를 바로 기록하지 않고 InnoDB 버퍼풀이라는 임시저장소에 변경된 데이터를 먼저 올려두게 된다. 이렇게 디스크에 기록되지 않고 임시저장소에만 등록된 데이터들만으로 구성된 버퍼풀의 페이지를 Dirty Page라 한다.

profile
안녕하세요. 잘부탁드립니다.

0개의 댓글