콘텐츠로 이동

MySQL8.0 chapter4

4장. 아키텍쳐

개요 (MySQL 서버의 구성)

  • 머리 : MySQL 엔진
    • 요청된 SQL 문장 분석/최적화 등
  • 손발 : 스토리지 엔진 (핸들러 API의 구현으로 InnoDB, MyISAM이 대표적)
    • 실제 데이터를 디스크 스토리지에 저장/읽기
  • 쿼리 실행 과정
    • [SQL 파서 ↔ SQL 옵티마이저 ↔ SQL 실행기]{MySQL 엔진} ←→ [데이터 읽기/쓰기]{스토리지 엔진} ←→ [디스크]

4.1 MySQL 엔진 아키텍쳐

구성

  • 커넥션 핸들러: 클라이언트의 접속/쿼리 요청 처리
  • SQL 파서 및 전처리기
  • 쿼리 옵티마이저

스레딩 구조

  • MySQL은 스레드 기반의 동작
  • 포그라운드 스레드 (클라이언트 스레드)
    • 클라이언트 수 만큼, 클라이언트 사용자가 요청하는 쿼리 처리
  • 백그라운드 스레드
    • 데이터 디스크 기록, 버퍼로 가져오기
    • 데드락 모니터링
    • 대다수가 백그라운드 스레드

메모리 구조

  • 글로벌 메모리 영역 - (모든 스레드에 의해 공유)
    • InnoDB 버퍼 풀
    • MyISAM 키 캐시
    • 바이너리 로그 버퍼
    • 리두 로그 버퍼
    • 테이블 캐시
  • 세션(커넥션) 메모리 영역 - (클라이언트 스레드가 쿼리를 처리하는데 사용하는 메모리 영역, 독립적)
    • 조인 버퍼
    • 정렬 버퍼
    • 네트워크 버퍼
    • 리드 버퍼

플러그인 & 컴포넌트

  • 요구사항에 맞게 직접 만들어 쓸 수 있음 (ex. 스토리지 엔진)
  • 컴포넌트는 플러그인 보다 발전된 아키텍쳐

쿼리 실행 구조

  1. 쿼리 파서 : 쿼리 → 토큰 트리
  2. 전처리기 : 트리 구조와 권한 검증
  3. 옵티마이저 : 비용 낮고 빠르게 처리할 방법 고안
  4. 실행엔진 : 만들어진 계획대로 각 핸들러에게 요청 + 받은 결과를 또 다른 핸들러로 요청
  5. 핸들러(스토리지 엔진) : 디스크 접근

스레드 풀

  • MySQL 커뮤니티 스레드 풀 지원 X
  • Percona는 지원 O
    • CPU 코어 갯수 만큼 스레드 그룹 생성
    • 시스템 변수 thread_pool_stall_limit 밀리초 안에 못끝내면 새로운 스레드 생성해서 스레드 그룹에 추가
    • 추가해도 시스템 변수 thread_pool_max_threads 갯수는 못 넘음
  • 생각보다 쓰레드 풀 도입한다고 성능 향상 눈에 띄진 않을수도

4.2 InnoDB 스토리지 엔진 아키텍쳐

개요

  • MySQL 스토리지 엔진 중 가장 많이 사용되는 InnoDB
  • 스토리지 엔진 중 거의 유일하게 레코드 기반 락 => 높은 동시성 처리 가능

PK에 의한 클러스터링

  • PK 값의 순서대로 디스크에 저장 (클러스터링 인덱스)
    • PK 기반 range scan 빨라짐
  • 모든 세컨더리 인덱스는 레코드의 주소 대신, PK의 값을 논리적인 주소로 사용

FK 지원

  • 데드락 발생할 가능성이 높은데 이유는...
    • InnoDB에서 FK는 부모/자식 테이블의 해당 칼럼에 모두 인덱스 생성이 필요
    • 변경 시에는 부모/자식 테이블에 데이터 있는지 검사하니 락이 여러 테이블로 전파... → 데드락
  • foreign_key_checks 시스템 변수 off 하는 것도 방법

MVCC (Multi Version Concurrency Control)

  • Undo Log의 도움을 받아 잠금을 사용하지 않는 일관된 읽기 기능 제공하고자 함
  • 데이터 변경 시...
    • 새로운 값 → InnoDB 버퍼풀
    • 옛날 값 → Undo Log
  • 이 상태에서 SELECT 시, 격리 수준에 따라 다른 영역에서 데이터 읽어서 반환
  • Multi Version (2개의 버전으로 데이터를 유지하고) Concurrency (동시성 처리를 이를 통해) Control (해냄)
  • 트랜잭션에서...
    • COMMIT → 지금 InnoDB 버퍼 풀 데이터 영구적으로
    • ROLLBACK → Undo Log 데이터 다시 복구
  • Undo Log의 백업 데이터는 이를 필요로 하는 트랜잭션이 더는 없을 때 삭제됨

잠금없는 낙관된 읽기 (Non-Locking Consistent Read)

  • InnoDB에서 SELECT는 락 없이 뚝딱 가능

자동 데드락 감지

  • 데드락 감지 스레드 : 주기적으로 잠금 대기 그래프 검사해 교착 상태 트랜잭션 중 하나 강제 종료
    • 언두 로그 레코드를 더 적게 가진 트랜잭션이 롤백 대상
    • 감지 스레드가 부담스럽다면 innodb_deadlock_detect 를 끄거나, innodb_lock_wait_timeout을 짧게 가져가자

자동화된 장애 복구

  • InnoDB 데이터 파일은 기본적으로 MySQL 서버 시작 시 항상 자동 복구 수행
  • 자동 복구될 수 없다면, MySQL 서버 종료됨
  • innodb_force_recovery설정을 통해 강제 시작 가능
    • 설정값 1 ~ 6. 각기 다른 방법
    • 풀 백업 + 바이너리 로그로 복구하는 것도 방법

InnoDB 버퍼 풀

  • 가장 핵심적인 부분 → 위의 4.1 에서 메모리 구조 전체 스레드가 공유하는 영역에서도 언급됨
  • 역할
    • 디스크의 데이터 파일, 인덱스 정보를 메모리에 캐시해두는 공간
    • 쓰기 지연 + 일괄 처리
    • 랜덤 디스크 작업 횟수 줄임
  • 크기 설정
    • OS, 클라이언트 스레드가 사용할 메모리 고려해서 설정
    • 크게 변경은 OK, 작게 변경은 큰일 날 수 있으니 주의
    • 128MB 단위로 크기 변경 가능
    • 기본적으로 버퍼 풀은 8개로 초기화, 버퍼 풀은 인스턴스당 5GB 정도로 추천
  • 버퍼 풀 구조
    • 메모리를 페이지 조각으로 쪼개서 관리하며, 필요로 할 때 디스크로 부터 읽어서 저장
    • 3개의 자료 구조로 관리 (LRU 리스트, 플러시 리스트, 프리 리스트)
      • 프리 리스트 : 빈 공간. 새로 읽어올 때 사용
      • LRU 리스트 : 캐싱 용도로 디스크 읽기 최소화
      • 플러시 리스트 : 디스크로 동기화 되지 않은 더티 페이지의 목록 관리
  • 버퍼 풀과 리두 로그
    • InnoDB에서 페이지 변경이 일어나면...
      • 변경 내용을 리두 로그에 기록 + 버퍼 풀의 데이터 페이지에도 변경 내용 반영 (더티 페이지)
        • 리두 로그의 각 엔트리는 특정 데이터 페이지와 연결됨
      • 하지만 리두 로그와 더티 페이지는 항상 디스크로 기록이 보장된 것은 아님 (복구 기준점 역할)
    • 다시 역할로...
      1. 데이터 캐시 기능 향상 → 버퍼 풀 사이즈 키우면 향상됨
      2. 쓰기 기능에 대한 버퍼링 기능 향상 → 얘는 꼭 버퍼 풀 사이즈 키운다고 늘지 않음. 리두 로그와 연관 있음
    • 리두 로그
      • 리두 로그는 1개 이상의 더티 페이지를 참조 중인 듯 (확실치 않음)
      • 리두 로그 파일 공간에는 LSN(Log Sequence Number)가 존재
        • 해당 번호는 계속해서 증가
      • 특정 시점에 체크 포인트 발생하면, 이전 반영된 LSN + 1 ~ 현재 체크포인트 LSN 까지 디스크 반영
      • 그렇다보니, 역할 2가지를 모두 잘 이득보려면 버퍼 풀/리두 로그 영역의 적당한 밸런스가 중요
    • 버퍼 풀 플러시
      • 앞서 살펴본 더티 페이지를 디스크에 쓰는 작업
      • 플러시 리스트 플러시
        • 얼마나 많은 더티 페이지를 한번에 디스크에 쓸래?
        • innodb_max_dirty_pages_pct : 더티 페이지 / 버퍼 풀 비율
        • innodb_max_dirty_pages_pct_lwm : 일정 수준 더티 페이지 발생 시 디스크로 기록
        • 이 방식은 약간 고전 방식이고... 발전된 방식으로는
          • 어댑티브 플러시 기능 (default ON)
            • 리두 로그 증가 속도 분석 → 적절한 수준의 더티 페이지가 버퍼 풀에 유지되도록 디스크 쓰기
      • 버퍼 풀 백업 및 복구
        • 쌩으로 시작하면 성능 많이 떨어져
        • 5.6 부터 버퍼 풀 덤프/적재 가능
        • 시스템 변수 innodb_buffer_pool_dump_at_shutdown , innodb_buffer_pool_load_at_startup 설정해서 자동으로 백업/복구 하도록 세팅 권장
      • Double Write Buffer
        • 리두 로그는 공간 아끼려고 더티 페이지의 변경된 내용만 기록
        • 근데 일부만 기록되면 속상함 (Partial-page/Torn-page)
        • 그래서 더티 페이지 기록할 친구들 모아서 Double Write Buffer에 기록하여 안정성 확보

언두 로그

  • 트랜잭션 + 격리 수준 보장 위해 언두 로그로 데이터 백업
    • 트랜잭션 : 롤백 시 언두로그에 있는 친구 가져옴
    • 격리 수준: MVCC에서 본 것 처럼 격리 수준에 따라 알맞은 데이터 반환
  • 원래는 한번 사이즈 늘면 안 줄었는데, 8.0 부터는 디스크 공간 줄일 수 있음
    • 그래도 언두 로그 용량 급격히 느는지 모니터링 대상
  • 언두 로그는 테이블 스페이스 외부의 별도 로그 파일에 기록 됨
  • 언두 로그 슬롯(공간) 이 부족하면 트랜잭션 시작도 못할 수 있으니, 적절히 필요한 동시 트랜잭션 갯수에 맞춰 설정해주세요

체인지 버퍼

  • 인덱스도 변경될 일이 있는데, 많은 자원 소모 대상
  • 인덱스에 따라 다른데
    • 인덱스 페이지가 버퍼풀에 있음 → 즉시 업데이트
    • 디스크로 부터 읽어와야 함 → 임시 공간 저장 (이게 체인지 버퍼)
  • 유니크 인덱스의 경우, 중복 여부를 체크해야 하기에 체인지 버퍼 사용 불가

리두 로그 및 로그 버퍼

  • 리두 로그 : MySQL 갑자기 꺼지더라도 데이터 잃지 않게 해주기 위함
  • 리두 로그는 트랜잭션 커밋 시, 즉시 디스크로 기록되게 해주세요
  • 8.0 부터는 리두 로그 아카이빙도 가능하답니다

어댑티브 해시 인덱스

  • InnoDB에서 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스!
  • B-Tree도 결국 루트 - 리프 까지 가긴해야하는데 이걸 줄여줌
  • 자주 읽히는 데이터 페이지의 키 값으로 해시 인덱스
  • 버퍼 풀에 올려진 데이터 페이지에 대해서만 관리됨
    • 버퍼 풀에서 사라지면 (flush), 해당 정보 사라짐
  • 어댑티브 해시 인덱스 사용해도 데이터 페이지를 디스크에서 하도 많이 읽어오면 도움 안 됨
  • 어댑티브 해시 인덱스 도움을 많이 받을수록 SELECT 는 좋지만, 변경은 치명적인 작업

4.3 MyISAM 스토리지 엔진 아키텍처

키 캐시

  • InnoDB의 버퍼 풀과 비슷한 역할
  • 키 캐시는 인덱스만을 대상으로 작동
    • 인덱스의 디스크 쓰기 작업에 대해서만 부분적으로 버퍼링

운영체제의 캐시 및 버퍼

  • 디스크 I/O 해결해줄 캐시 기능 자체적으로 X
    • 데이터 읽기/쓰기는 항상 OS의 디스크 읽기/쓰기로 이어짐
    • OS 캐싱이 중요하다는 뜻으로도 해석 됨. 메모리 OS에 양보하세요

데이터 파일과 PK(인덱스 구조)

  • InnoDB: PK 클러스터링
  • MyISAM: PK 클러스터링 없이 데이터 파일 Heap 공간 처럼 활용
    • Insert 순서대로 데이터 파일에 저장
    • ROWID라는 물리적인 주솟값 가짐
    • PK, IDX 모두 ROWID 참조

4.4 MySQL 로그 파일

  • MySQL 문제 생기면 로그 파일에 잘 적어줌
    1. 시작 과정 정보성 로그
    2. 비정상적 종료 트랜잭션 복구 로그
    3. 쿼리 처리 도중 문제 로그
    4. 비정상적 종료 커넥션 로그
    5. InnoDB 모니터링 / 상태 조회 명령 로그
    6. MySQL 종료 로그

슬로우 쿼리 로그

  • 쿼리 튜닝
    1. 서비스 적용 전 전체적으로 튜닝 (우리 아지트 DDL 요청)
    2. 서비스 운영 중에 발생하는 슬로우 쿼리 (와치타워 알림 가끔 오는거)
  • long_query_time 시스템 변수에 설정한 시간 넘어가면 슬로우 쿼리로 기록