InnoDB 스토리지 엔진 아키텍처

hs·2025년 11월 2일
  • 레코드 기반의 잠금을 제공
    • 높은 동시성 처리가 가능하고 안정적이며 성능이 뛰어나다

프라이머리 키 클러스터링 인덱스

  • InnoDB의 모든 테이블은 프라이머리 키를 기준으로 클러스터링되어 저장된다.
    • 클러스터링 인덱스: 데이터가 물리적으로 인덱스 키 값 순서대로 저장되는 구조
  • 모든 세컨더리 인덱스는 레코드의 주소 대신 프라이머리 키의 값을 논리적인 주소로 사용한다.
    • 세컨더리 인덱스: 프라이머리 키가 아닌 다른 컬럼에 생성하는 인덱스
  • 프라이머리 키가 클러스터링 인덱스이기 때문에 범위 검색, 정렬이 빠르다.
  • 쿼리 옵티마이저는 실행 계획 수립 시 다른 보조 인덱스보다 프라이머리 키를 우선적으로 고려한다.

외래 키 지원

  • 외래 키 지원은 InnoDB 스토리지 엔진 레벨에서 지원하는 기능
  • 변경 시 부모 테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요
    → 잠금이 여러 테이블로 전파 된다.
    → 데드락 발생하는 경우가 많다.
  • 수동으로 데이터를 다루는 경우 주의
  • foreign_key_checks 변수를 off로 설정하면 외래 키 관계에 대한 체크 작업을 일시적으로 멈출 수 있다.
    → off 로 설정하면 cascade 옵션도 무시

MVCC(Multi Version Concurrency Control)

  • 레코드 레벨의 트랜색션을 지원하는 DBMS가 제공하는 기능
  • 잠금을 사용하지 않는 일관된 읽기를 제공
  • 언두 로그를 이용해 기능을 구현
  • READ_UNCOMMITTED
    • 버퍼 풀의 데이터를 반환
  • READ_COMMITTED 이상의 격리 수준 (REPEATABLE_READ, SERIALIZABLE)
    • 언두 영역의 데이터를 반환
  • MVCC: 하나의 레코드에 대해 2개의 버전이 유지되고, 상황에 따라 다른 데이터를 반환한다.
  • 트랜잭션이 길어지면 언두에서 관리하는 데이터가 삭제되지 못한다.
    • 시스템 테이블 스페이스의 공간이 늘어남
  • 커밋을 실행하면 데이터의 현재 상태를 영구 데이터로 만든다
    • 언두 영영의 데이터가 바로 삭제되진 않고, 언두 영역을 필요로 하는 트랜잭션이 없을 때 삭제된다.
    • 롤백을 실행하면 언두 영역에 있는 데이터를 버퍼 풀로 복구하고 언두 영역의 내용이 삭제된다.

언두 로그

  • 트랜잭션과 격리 수준을 보장하기 위해 DML로 변경되기 이전의 데이터를 별도로 백업한 데이터
  • 트랜잭션 보장
    • 트랜잭션이 롤백되면 데이터를 변경 전 데이터로 복구한다.
  • 격리 수준 보장
    • 특정 커넥션에서 데이터를 변경하는 도중 다른 커넥션에서 데이터를 조회하면 트랜잭션 격리 수준에 맞게 변경 중인 레코드를 읽지 않고 언두 로그에 백업해둔 데이터를 읽어서 반환

잠금(lock)

사용자나 애플리케이션이 동시에 데이터베이스에 접근할 때 데이터 일관성과 무결성을 보호하기 위한 메커니즘

  • 동시성 제어: 여러 트랜잭션이 동시에 같은 데이터에 접근할 때 충돌을 방지
  • 배타적 접근: 특정 데이터를 한 트랜잭션이 수정하는 동안 다른 트랜잭션의 접근을 제한
  • 공유 잠금(Shared Lock)
    • 다른 트랜잭션의 읽기는 허용, 쓰기는 차단
    • 여러 트랜잭션이 동시에 같은 데이터의 공유 잠금을 획득할 수 있지만, 배타적 잠금은 획득할 수 없다.
  • 배타적 잠금(Exclusive Lock)
    • 트랜잭션이 데이터에 대해 배타적 잠금을 획득하면 다른 트랜잭션은 해당 데이터에 대한 어떤 유형의 잠금도 획득할 수 없다.

잠금 없는 일관된 읽기

  • 격리 수준이 SERIALIZABLE 이 아닌 경우 순수한 읽기 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다.
  • 변경 전의 데이터를 읽기 위해 언두 로그를 사용
  • 트랜잭션이 오랜 시간 활성 상태로 있는 경우 서버가 느려지거나 문제가 발생할 수 있다
    • 일관된 읽기를 위해 언두 로그를 삭제하지 못하고 유지하는 경우 발생
    • 트랜잭션은 가능한 빨리 롤백 또는 커밋으로 완료하는 것이 좋다.

자동 데드락 감지

  • 데드락: 두 개 이상의 트랜잭션이 서로 보유한 잠금(lock)을 기다리며 무한정 대기하는 상태
  • 잠금이 교착 상태에 빠지지 않았는지 체크하기 위해 잠금 대기 목록을 그래프 형태로 관리한다.
  • 데드락 감지 스레드를 가지고 있어 주기적으로 잠금 대기 그래프를 검사해 교착 상태에 빠진 트랜잭션들을 찾아서 하나를 강제 종료한다.
  • 강제 종료(롤백)의 판단 기준은 언두 로그 레코드를 더 적게 가진 트랜잭션을 우선으로 삭제한다.
  • 테이블 잠금은 스토리지 엔진의 상위 레이어인 MySQL 엔진에서 관리한다.
    • innodb_table_locks 변수를 활성화해 테이블 레벨의 잠금도 감지할 수 있다. (권장)
  • 동시 처리가 스레드나 트랜잭션이 가진 잠금의 개수가 많아지면 데드락 감지 스레드가 느려진다. → 서비스 쿼리를 처리 중인 스레드는 작업을 진행하지 못하고 대기 → innodb_deadlock_detect 변수를 OFF 로 설정하면 데드락 감지 스레드 작동 중지할 수 있다.
  • 2개 이상의 트랜잭션이 상대방이 가진 잠금을 요구하는 경우 데드락 상태가 된다.
    • innodb_lock_wait_timeout (기본 값, 50초) 변수를 활성화 해서 데드락 상황에서 일정 시간이 지나면 자동으로 요청이 실패하도록 할 수 있다.
    • innodb_deadlock_detect를 OFF 하는 경우 기본값 보다 훨씬 낮은 시간으로 변경하는 것을 권장

자동화된 장애 복구

  • InnoDB 데이터 파일은 기본적으로 MySQL 서버가 시작될 때 항상 자동 복구를 수행한다.
  • 자동 복구될 수 없는 손상이 있다면 MySQL 서버는 종료된다.
    • innodb_force_recovery 변수를 설정해서 MySQL 서버를 구동 시켜 보면서 복구한다.
    • 1 ~ 6, 값이 커질 수록 심각한 상황

InnoDB 버퍼 풀


  • 데이터베이스가 디스크 I/O 작업을 최소화하기 위해 사용하는 메모리 영역
  • 캐싱: 디스크의 데이터 파일이나 인덱스 정보를 메모리에 임시 저장
  • 쓰기 버퍼링: 쓰기 작업을 지연시켜 일괄 작업으로 처리
    • 랜덤한 디스크 작업의 횟수를 줄일 수 있다.
  • innodb_buffer_pool_size: 버퍼 풀의 크기를 설정하는 변수
    • 동적으로 버퍼 풀의 크기 확장 가능
    • 크기 변경 작업은 한가한 시점에, 크기를 줄이는 작업은 지양
    • 내부적으로 128MB 청크 단위로 쪼개어 관리 → 버퍼 풀의 크기를 줄이거나 늘리기 위한 단위 크기로 사용

버퍼 풀 구조

  • 페이지 크기의 조각으로 쪼개 데이터를 필요로 할 때 해당 데이터 페이지를 읽어서 각 조각에 저장한다.
  • 페이지 크기 조각을 관리하기 위해 3개의 자료 구조를 관리한다.
    • LRU(Least Recently Used) 리스트
      • LRU와 MRU(More Recently Used) 리스트가 결합된 형태
      • 한 번 읽어온 페이지를 최대한 오래 버퍼풀의 메모리에 유지해 디스크 읽기를 최소화
      • 사용 빈도가 낮은 데이터 페이지들을 제거해서 새로운 페이지들을 읽어올 공간을 만드는 작업을 수행
    • 플러시 리스트
      • 디스크로 동기화되지 않은 데이터를 가진 페이지(더티 페이지)의 목록을 관리
        • 오래전에 변경된 데이터 페이지 순서대로 디스크에 동기화 하는 작업을 수행
      • 데이터의 변경 내용을 리두 로그에 기록하고 버퍼 풀의 데이터 페이지에도 반영한다.
      • 리두 로그의 각 엔트리는 특정 데이터 페이지와 연결된다.
        • 리두 공간이 지워지려면 InnoDB 버퍼 풀의 더티 페이지가 먼저 디스크로 동기화 돼야 함
      • 스토리지 엔진이 주기적으로 호출
    • 프리 리스트
      • InnoDB 버퍼 풀에서 실제 사용자 데이터로 채워지지 않은 비어 있는 페이지들의 목록
      • 쿼리가 새롭게 디스크의 데이터 페이지를 읽어와야 하는 경우 사용
  • 클린 페이지: 디스크에서 읽어온 상태의 페이지, 변경되지 않은 상태
  • 더티 페이지: 변경된 데이터를 가진 페이지, 디스크에 기록되어야 한다.

버퍼 풀과 리두 로그

  • 리두 로그는 영속성과 밀접한 연관관계
  • 서버가 비정상적으로 종료됐을 때 기록되지 못한 데이터를 잃지 않게 해주는 안전장치
  • 데이터 변경 내용을 로그에 먼저 기록
    • 대부분의 DBMS는 쓰기 보다 읽기 성능을 고려한 자료 구조를 가지고 있음
    • 쓰기는 디스크의 랜덤 액세스가 필요 → 상대적으로 큰 비용 발생 → 쓰기 비용이 낮은 리두 로그를 사용
  • 여러 개의 고정 크기 파일로 구성
  • 파일을 순차적으로 순회하면서 사용한다.

[redo01.log] → [redo02.log] → [redo03.log] → [redo04.log]
    ↑                                              |
    └──────────────────────────────────────────────┘
  • 활성 리두 공간: 디스크에 반영되지 않은 변경사항을 담고 있는 공간(당장 재사용이 불가능)
  • LSN(Log Sequence Number)
    • 각 로그 엔트리에 부여되는 고유 일련번호
    • 변경 사항의 순서를 추적
  • 주기적으로 체크포인트 이벤트를 발생시켜 리두 로그와 버퍼 풀의 더티 페이지를 디스크로 동기화한다.
    • 가장 최근 체크포인트 지점의 LSN이 활성 리두 공간의 시작점이된다.
    • 체크포인트 에이지: 가장 최근 체크포인트의 LSN과 마지막 리두 로그 엔트리의 LSN의 차이
      → 활성 리두 공간의 크기

상태 백업 및 복구

  • 워밍업: 데이터가 버퍼 풀에 적재돼 있는 상태 → 쿼리 성능이 빨라짐(캐싱)
    • 5.6 버전부터 버퍼 풀 덤프 및 적재 기능 도입으로 버퍼 풀의 상태를 백업할 수 있게 됨
      • 백업은 메타 정보만 저장하기 때문에 매우 빠르다.
      • 적재 과정은 InnoDB 버퍼 풀의 크기에 따라 시간이 다소 소요될 수 있다.

체인지 버퍼

  • 인덱스를 업데이트 해야하는 경우 사용하는 임시 메모리 공간

어댑티드 해시 인덱스

  • 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스
  • B-Tree의 검색 시간을 줄이기 위해 도입되었다.
  • 자주 읽히는 데이터 페이지의 키 값을 이용해 해시 인덱스를 만든다.
    • 키: 인덱스 ID
    • 값: 데이터 페이지 주소, 키에 해당하는 데이터가 저장된 버퍼 풀 내 메모리 페이지 위치
  • 버퍼 풀에 로딩된 데이터 페이지에 대해서만 적용된다.
    • 디스크에서 읽는 경우 도움이 되지 않는다.
profile
sh

0개의 댓글