Mysql 서버 = Mysql 엔진 + Storage 엔진
- Handler API(핸들러 API)를 구현하여, (추가적인) Storage 엔진을 개발/적용할 수 있다.
4.1 MySQL 엔진 아키텍처 #
Mysql 서버는 다른 DBMS 에 비해 구조가 독특하다고 볼 수 있다.
아래와 같이 구조를 나눠볼 수 있다.
Mysql 엔진 | Storage 엔진 |
---|---|
- 커넥션 핸들러 - SQL 파서 - SQL 옵티마이저 - 캐시 & 버퍼 - … | - InnoDB - MyISAM - … |
커넥션 핸들러
SQL 파서 , SQL 옵티마이저 , 캐시 & 버퍼
스토리지 엔진 (InnoDB, MyISAM, ...)
DISK (Data File, Log File, ...)
* 단, ‘Mysql 엔진’ 이라는 용어는 공식적인 용어는 아닌 것 같기도하다.
* ‘Storage 엔진’ 은 공식적인 용어인 것 같다.
Mysql 엔진
DBMS 두뇌역할 (쿼리 분석, 최적화 등)
- group by, order by 등의 복잡한 처리는 Mysql 엔진(쿼리 실행기)에서 처리
*‘데이터 쓰기/읽기 작업’은 (반드시) ‘핸들러 API’ 통해 스토리지 엔진에 작업 요청
스토리지 엔진
실제로 디스크로부터 데이터를 읽어오는 역할
* 각 스토리지 엔진은 성능 향상을 위해 키 캐시(MyISAM), 버퍼 풀(InnoDB) 와 같은 기능을 내장
Mysql 스레딩 구조
- 프로세스 기반 X
- 스레드 기반 O
- 포그라운드 스레드
- 백그라운드 스레드
Mysql 커뮤니티 에디션 : 전통적인 Thread 모델 = Thread Pool 모델 사용 X
Mysql 엔터프라이즈 에디션 (혹은 Percona Mysql 서버) : Thread Pool 모델 사용 가능
포그라운드 Thread
- (Thread Pool을 사용하지 않았다는 가정하에) 최소 Mysql 서버에 접속된 클라이언트 수만큼 존재
- 각 클라이언트의 요청(쿼리) 처리
- 커넥션 종료 시 (해당 커넥션을 담당하던) Thread는 Thread Cache 되돌아가거나 혹은 소멸
- 데이터를 Buffer 혹은 Cache 로부터 가져온다.
- Buffer 혹은 Cache에 없는 경우 디스크, 인덱스로부터 데이터 가져온다.
- MyISAM : 포그라운드 Thread 가 처리
- InnoDB : 백그라운드 Thread 에 위임
- Buffer 혹은 Cache에 없는 경우 디스크, 인덱스로부터 데이터 가져온다.
백그라운드 Thread
MyISAM 의 경우 해당하지 않는다.
아래의 작업들이 백그라운드 Thread 처리
- Insert Buffer Merge(병합) 스레드
- Log 를 Disk 로 기록하는 스레드 (로그 스레드)
- InnoDB Buffer Pool 의 데이터를 disk 에 기록 (쓰기 스레드)
- 데이터를 버퍼(buffer)로 읽어오는 스레드
- Lock, DeadLock 모니터링 스레드
이 중 ‘로그 스레드’, ‘쓰기 스레드’ 가 가장 중요하다고 한다.
사용자의 요청을 처리하는 중 ‘데이터 쓰기 작업’ 은 지연될 수 있다.
- 대부분의 DBMS 에서는 쓰기 지연 기능(버퍼링)이 포함되어 있다. (대부분의 경우 응답이 더 빠르다.)
- MyISAM 의 경우 포그라운드 스레드(사용자 스레드)가 ‘쓰기 작업’까지 처리한다. (따라서, 쓰기 지연 기능 X, 바로바로 처리한다.)
사용자의 요청을 처리하는 중 ‘데이터 읽기 작업’ 은 지연될 수 없다.
메모리 할당 및 사용 구조
각 운영체제의 메모리 할당 방식은 매우 복잡하다.
요청된 메모리 공간을 100% 할당해줄 수도 있고, 요청된 공간을 예약만 해두고 실제 필요할 때마다 할당해주는 경우도 있다.
Mysql 서버가 실제로 사용하는 정확한 메모리의 양을 측정하는 것도 쉽지 않다고 한다. (따라서 단순하게 시스템 변수로 설정해둔만큼 메모리를 할당받는다고 생각해도 된다고 함)
Mysql 메모리 공간 = 글로벌 메모리 영역(Global Memory Area) + 로컬 메모리 영역(Local/Session Memory Area)
글로벌 메모리 영역
- 모든 쓰레드(클라이언트)가 공유
- 즉, 오직 하나의 메모리 공간만 할당
- 단, (필요에 따라) 2개 이상의 메모리 공간을 할당 받을 수 있음
- 메모리 영역의 설정값에 따라 (최악의 경우) 서버가 죽을 수 있음
- Mysql 서버 시작 시 운영체제로부터 할당
- InnoDB 버퍼 풀 / MyISAM 키 캐시
- bin_log 버퍼
- redo_log 버퍼
- table cache
- …
로컬 메모리 영역
- 클라이언트 접속(연결) 시 할당
- 클라이언트 쓰레드가 쿼리를 처리하기 위해 사용하는 메모리 영역
- (클라이언트 쓰레드 별로) 독립적인 영역
- ‘클라이언트의 메모리 영역의 설정값’은 ‘글로벌 메모리 영역의 설정값’보다 비교적 덜 주의해서 설정해도 됨
- 각 쿼리의 용도별로 공간이 할당
- 필요 시에만 공간이 할당되는 개념
- 필요하지 않은 경우 공간이 할당되지 않을 수 있음
- 커넥션 후 계속 할당되어 있는 영역이 있음 (커넥션 버퍼, 결과 버퍼)
- 커넥션 후 쿼리 실행 순간에만 할당되고 곧바로 해제되는 영역이 있음 (조인 버퍼, 소트 버퍼)
- 조인 버퍼 (join buffer)
- 정렬 버퍼 (sort buffer)
- 리드 버퍼 (read buffer)
- 네트워크 버퍼 (network buffer)
- …
플러그인 모델
Mysql 독특한 구조(기능) 중 대표적인 기능 : ‘플러그인 모델’
단순히 스토리지 엔진 뿐만 아니라, 다양한 기능을 플러그인 모델을 통해 지원한다.
플러그인의 종류
- 스토리지 엔진
- ARCHIVE
- BLACKHOLE
- MyISAM
- FEDERATED
- InnoDB
- MEMORY
- CSV
- PERFORMANCE_SCHEMA
- …
- 인증
- 파서 (전문 검색 파서 등)
- 커넥션 제어
- …
컴포넌트
Mysql 8.0 부터는 아래의 ‘플러그인 단점’을 보완하기 위해 ‘컴포넌트’로 대체/지원한다.
플러그인 단점
- Mysql 서버와 통신 OK, 플러그인끼리는 통신할 수 없음
- Mysql 서버의 변수, 함수를 직접 호출 (캠슐화 X)
- 상호 의존 관계 설정할 수 없음 => 초기화 어려움
- (* 누가 먼저 실행이 되어야 하는지 일일히 확인해줘야 하는 의미인 것 같다.)
쿼리 실행 구조
SQL 요청 -> MySQL 엔진
- 쿼리 파서
- 전처리기
- 옵티마이저 (쿼리 변환, 쿼리 최적화, 실행 계획 수립)
- 쿼리 실행기 -> 스토리지 엔진
- InnoDB
- MyISAM
- ...
쿼리 파서
요청온 쿼리의 기본적인 문법을 확인한다.
- 쿼리 문장을 토큰화
- 트리 형태의 구조로 생성
전처리기
쿼리 파서에서 만들어진 파서 트리를 기반으로 쿼리 문장에 구조적인 문제가 있는지 확인한다.
- 실제로 해당 테이블, 컬럼이 존재하는지?
- 접근 권한이 있는지?
옵티마이저
* DBMS 의 두뇌라고 한다. 아주 중요하다.
가장 저렴한 비용으로, 최적화된 쿼리로 실행할 수 있도록 한다.
실행 엔진
실행 엔진은 ‘핸들러 API’를 통해 (옵티마이저에 의해)최적화된 쿼리를 처리한다.
쿼리 캐시
Mysql 8.0 부터 해당 기능을 제거했다.
아래와 같은 이유이다.
- Key : SQL 쿼리
- Value : SQL 실행 결과 (Memory)
해당 쿼리에 연관된 테이블의 데이터가 변경되면, 관련된 캐시들은 모두 삭제해야 했다.
* (따라서) 오히려 성능(동시성) 저하, 버그가 많았다고 한다.
스레드 풀
‘Percona Server (Thread Pool 플러그인)’ 알아볼 것
Thread Pool 의 스레드 수를 무작정 많이 늘리는 것은 좋지 않다.
- 스케줄링 대상 많아짐
- 불필요한 컨텍스트 스위칭 비용 커짐
메타데이터(데이터 딕셔너리) 관리
메타데이터(데이터 딕셔너리) : 테이블 구조 정보, 스토어드 프로그램 등의 정보
- Mysql 5.7 : File 관리
- Mysql 8.0 :
- InnoDb : (InnoDB 스토리지 엔진의)DB,Table로 관리 (mysql db) (트랜잭션 OK)
- 그 외 : File 관리
File 관리 시 문제점 : 중간에 오류 시 ‘일관성’ 보장할 수 없다. (= DB가 깨졌다. 테이블이 깨졌다.)
* mysql DB 에 테이블 조회 시 실제로는 존재하되, 보이지 않음(select 불가) (사용자가 수정하지 못하게 하기 위함)
대신 information_schema DB의 TABLES, COLUMNS 와 같은 view 를 통해 조회 가능
4.2 InnoDB 스토리지 엔진 아키테처 #
Mysql 스토리지 엔진 중 거의 유일하게 ‘레코드 기반 잠금’ 을 제공한다.
- 높은 동시성 처리 (높은 성능)
- 높은 안정성
프라이머리 키에 의한 클러스터링 #
PK = 클러스터링 인덱스
- PK 를 이용한 스캔은 빠르게 처리된다.
- 실행 계획 시, PK 를 이용한 인덱스는 선택될 확률이 높다. (비중이 크게 설정된다.)
(기본적으로) InnoDB의 모든 테이블은 PK 를 기준으로 클러스터링되어 저장된다.
(= PK 순서대로 디스크에 저장된다는 의미이다.)
모든 세컨더리 인덱스는 ‘레코드의 주소’ 대신 ‘PK 값’을 주소로 사용한다.
(= 세컨더리 인덱스 => PK => 물리 주소)
" MyISAM 의 경우 클러스터링 키(인덱스)를 지원하지 않는다. 따라서, PK 와 세컨더리 인덱스는 차이가 없다.(PK 인덱스는 유니크 제약을 갖는 세컨더리 인덱스일 뿐이다.) 또, PK 와 세컨더리 인덱스 모두 물리 주소를 갖고 있다. “
외래 키 지원 #
” MyISAM 스토리지 엔진은 지원하지 않는다. “
‘운영 상의 불편함’, ‘데드락을 유발하는 원인’이 되기도 해서 실제로 외래키를 생성하지 않는 경우도 종종 있다고 한다.
데드락을 유발하는 원인
- 참조하는 테이블, 참초 테이블 모두 해당 컬럼에 대한 ‘인덱스 생성’이 필요
- 변경 시, 관련된 테이블에 데이터가 있는지 체크 (= 잠금, 처리가 여러 테이블로 전파)
foreign_key_checks
시스템 변수
- on : default
- off : (CASCADE 포함) 외래키 처리(확인) X
- 외래 키 확인(일관성)을 위한 부가적인 처리가 필요 없어지기 때문에 빠르게 동작할 것이다.
GLOBAL, SESSION 모두에 적용 가능한 변수이다. 주의한다.
MVCC (Multi Version Concurrency Control) #
MVCC의 핵심 목표 : 잠금을 사용하지 않고 일관된 읽기 제공
일반적으로 레코드 기반 잠금을 지원하는 DBMS 에서 제공하는 기능이다.
InnoDB 는 언두 로그를 이용해 MVCC 를 구현한다.
값이 업데이트 되었을 때 아래와 같이 동작한다.
- 버퍼풀 / 데이터 파일(디스크)는 값을 변경
- 언두 로그에는 이전 값을 저장(기록)
동작 | 설명 |
---|---|
커밋 시 | 언두 로그 삭제(다른 트랜잭션에 의해 해당 언두로그가 더 이상 사용되지 않을 때) (언두 로그의 내용 바로 삭제되는 것 아님) |
롤백 시 | 1. 언두 로그 적용 2. 언두 로그 삭제 |
버퍼 풀의 값이 데이터 파일(디스크)에 적용되는 정확한 시점은 알 수 없다. 다만 (통상적으로) 버퍼 풀의 값은 데이터 파일(디스크)의 값과 동일하다고 보아도 무방하다.
고립 레벨 | 동작 |
---|---|
READ UNCOMMITED | 버퍼풀의 데이터를 읽는다. |
READ COMMITED REPEATABLE READ SREALIZABLE | 언두로그의 데이터를 읽는다. |
” 트랜잭션이 길어지면, 언두로그에 데이터가 많이 쌓이게 되고 그만큼의 시간동안 관리해주어야 한다. 따라서 성능 저하(문제 발생)로 이루어질 수 있다. “
* 트랜잭션은 가능한 빨리 커밋/롤백을 완료하는 것이 좋다.
잠금 없는 읽관된 읽기 (Non-Locking Consistent Read) #
MVCC 기술을 통해 잠금 없이 ‘일관된 읽기’(읽기 작업)를 지원한다.
- 다른 Tx가 진행중인 상황에서, 또 다른 Tx가 값을 읽을 때 전혀 방해받지 않는 것이다.
” 단, ‘SERIALIZABLE’ 레벨은 잠금을 건다. (= 공유 잠금일 듯) “
자동 데드락 감지 #
(데드락을 모니터링하기 위해) 잠금 대기 목록을 그래프 형태로 관리한다.
이것을 주기적으로 확인하는 **‘데드락 감지 스레드’**가 존재한다.
데드락이 감지되면 그 중 하나의 트랜잭션을 강제 종료한다.
- ‘언두 로그 양’을 기준으로 종료할 트랜잭션을 선택한다.
= 처리 대상이 적다는 의미
= Mysql 서버 부하가 적다는 의미
” 참고로 InnoDB 스토리지 엔진은 (상위 레이어인) Mysql 엔진에서 관리되는 테이블 잠금은 볼 수가 없어서 데드락 감지가 북활실할 수도 있는데,
innodb_table_locks
시스템 변수를 활성화하면 테이블 잠금까지 감지할 수 있다. 특별한 이유가 없다면innodb_table_locoks
시스템 변수를 활성화하자. “
일반적인 경우, 데드락 감지 스레드가 데드락을 확인하는 작업은 크게 부담되지 않는다.
다만, 동시 처리량이 매우 많거나, 잠금의 수가 많아지는 경우 데드락 감지 스레드가 느려진다.
시스템 변수 | 설명 |
---|---|
innodb_deadlock_detect | 데드락 감지 스레드 동작 활성화/비활성화 |
innodb_lock_wait_timeout | 데드락 상황에서 일정 시간 지난 경우 자동으로 실패 처리 (second 단위) |
” 데드락 감지 스레드가 부담되어
innodb_deadlock_detect
를 off 로 설정했다면,innodb_lock_wait_timeout
을 기본값인 50초보다 훨씬 낮은 값으로 변경할 것을 권장한다. “
” 데드락 감지 스레드는 잠금 목록을 검사할 때 잠금 상태가 변경하지 않도록 잠금 목록이 저장된 리스트(잠금 테이블)에 새로운 잠금을 걸고 처리를 한다. 이 상황에서 데드락 감지 스레드가 느려지면 전체적인 처리(일반 쿼리 등) 작업에 영향을 미치게 된다. “
자동화된 장애 복구 #
Mysql 서버는 시작될 때, ‘완료되지 못한 트랜잭션’, ‘Partial write(디스크에 일부만 기록된 데이터 페이지)’ 등에 대한 복구 작업이 자동으로 진행된다. 복구 작업이 실패하면 서버는 시작되지 않고 종료된다.
또, 커밋되지 않은 데이터를 롤백하거나, 종료된 시점의 데이터 상태를 만들어낸다..?
” InnoDB 스토리지 엔진은 매우 견고해서 데이터 파일이 손상되거나 Mysql 서버가 시작되지 못하는 경우는 거의 발생하지 않는다. 하지만 Mysql 서버와 무관하게, 디스크와 같은 서버 HW 이슈로 인해 Innodb 스토리지 엔진이 자동으로 복구를 못 하는 경우도 발생할 수 있다. “
(서버 시작 시의)복구 작업이 실패할 경우, innodb_force_recovery
값을 설정해 Mysql 서버를 강제로 시작시킬 수 있다. (이후 dump 등의 작업을 통해 새로운 mysql 서버 구축, 데이터 import 하는 것을 권장)
level | 설명 & 시도할 수 있는 케이스 |
---|---|
1 (SRV_FORCE_IGNORE_CORRUPT) | InnoDB 테이블스페이스, 데이터 파일(디스크), 인덱스 페이지가 손상되었을 때Database page corruption on disk or a failed 와 같은 메시지가 출력될 때 |
2 (SRV_FORCE_NO_BACKGROUND) | 메인 스레드가 언두 로그의 데이터를 삭제하는 과정에서 장애가 발생했을 때 메인 스레드를 시작하지 않고 mysql 서버를 시작 |
3 (SRV_FORCE_NO_TRX_UNDO) | 커밋되지 않은 트랜잭션의 작업을 롤백하지 않고 그대로 놔둠 즉, 커밋되지 않고 종료된 트랜잭션은 그 상태 그대로 남아 있게 함 |
4 (SRV_FORCE_NO_IBUF_MERGE) | 인서트 버퍼를 사용할 수 없을 때(손상되었을 때) 인서트 버퍼의 내용을 무시 후 mysql 서버를 시작 * 인덱스와 관련된 부분이므로 테이블 덤프 시 데이터의 손실 없이 복구 가능하다. |
5 (SRV_FORCE_NO_UNDO_LOG_SCAN) | 언두 로그 사용할 수 없을 때(손상되었을 때) 언두 로그 무시하고 mysql 서버 시작 * 언두로그를 사용하지 않았기 때문에 -> 정상적으로 롤백되어야 할 것들이 안되었을 것 -> 데이터 파일에 잘못된 데이터가 남아있는 것으로 볼 수 있음 |
6 (SRV_FORCE_LOG_REDO) | 리두 로그 사용할 수 없을 때(손상되었을 때) 리두 로그 무시하고 mysql 서버 시작 * 즉, 마지막 체크포인트 시점의 데이터만 남아있는 것으로 볼 수 있음 |
값이 높을 수록 심각하고, 데이터 복구 가능성 낮음
* 인서트 버퍼 : insert, update, delete 등의 데이터 변경으로 인한 인덱스 변경 작업 시 인서트 버퍼에 저장하고 나중에 처리할 수 있다. (즉시 처리할 수도 있다.) 인서트 버퍼에 기록된 내용은 언제 디스크에 병합(merege) 될 지 알 수 없다.
* Mysql 서버 장애, 종료 시점에 진행 중인 트랜잭션은 mysql이 그 커넥션을 강제로 끊어버리고 별도의 정리 작업 없이 종료한다. mysql 재시작 시 언두 로그, 리두 로그를 이용해 종료(장애) 시점의 상태를 재현한다.
위와 같은 방식으로 데이터를 복구하거나, 백업이 있을 경우 마지막 풀 백업 시점 + binlog 통해 데이터를 복구할 수 있다.