InnoDB 스토리지 엔진 아키텍처

Ji, SeunghyeonJi, Seunghyeon
5 min read
💡
이 글은 Real MySQL 8.0공식 문서를 읽고 개인적으로 정리한 내용입니다.

1. InnoDB 스토리지 엔진 아키텍처

InnoDB 스토리지 엔진은 MySQL의 스토리지 엔진 가운데 가장 많이 사용되는 엔진입니다. MySQL에서 사용할 수 있는 스토리지 엔진 중 거의 유일하게 레코드 기반의 잠금을 제공하고, 그 때문에 높은 동시성 처리가 가능하고 안정적이며 성능이 뛰어납니다.

1.1. 프라이머리 키에 의한 클러스터링

각 InnoDB 테이블에는 행 데이터를 저장하는 클러스터 인덱스라는 특수한 인덱스가 있습니다. 일반적으로 InnoDB의 모든 테이블은 기본적으로 PK를 기준으로 클러스터링되어 저장되고, 우리는 이걸 클러스터 인덱스라고 부릅니다. PK가 클러스터 인덱스이기 때문에 PK를 이용한 레인지 스캔이 상당히 빨리 처리됩니다.

1.2. 외래 키 지원

InnoDB에서 FK는 부모 테이블과 자식 테이블 모두 해당 칼럼에 인덱스 생성이 필요하고, 변경 시에는 반드시 부모 테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요하므로 잠금이 여러 테이블로 전파되고, 그로 인해 데드락이 발생할 때가 많으므로 FK의 존재에 주의하는 것이 좋습니다.

foreign_key_checks 시스템 변수를 OFF로 설정하면 FK 관계에 대한 체크 작업을 일시적으로 멈출 수 있습니다. 이 변수는 적용 범위를 GLOBAL과 SESSION 모두로 설정 가능한 변수이기 때문에, 반드시 현재 작업을 실행하는 세션에서만 FK 체크 기능을 멈추게 해야 합니다.

제 경험상, 이렇게 외래 키 체크 기능을 멈추게 하고 변경 작업을 진행하기 전엔 꼭 작업했던 테이블과 FK가 얽힌 모든 테이블에 대하여 이해하고 있을 필요가 있었습니다. 외래 키 체크를 일시적으로 중지한 상태에서 외래 키 관계를 가진 부모 테이블의 레코드를 삭제했다면 반드시 자식 테이블의 레코드도 삭제해서 일관성을 맞춰줘야 합니다.

1.3. MVCC(Multi-Version Concurrency Control)

InnoDB는 레코드 단위의 트랜잭션을 지원해 주는데, 이를 위해 MVCC(Multi-Version Concurrency Control)을 사용합니다. MVCC란 하나의 트랜잭션에서 데이터에 접근하는 경우 데이터의 언두 로그(Undo log)를 이용하여 멀티 버전 중 보장되는 버전에 맞는 값을 반환하여 처리하는 방법을 의미합니다. 여기서 멀티 버전이라 함은 하나의 레코드에 대해 여러 버전이 관리된다는 것을 의미하며, 이를 통해 데이터에 대한 변경이 커밋되기 전까지의 변경 사항은 다른 사용자가 볼 수 없도록 하는 등의 제어가 가능합니다.

다음과 같은 테이블에 한 건의 레코드를 INSERT한 다음 UPDATE해서 발생하는 변경 작업 및 절차를 확인해 봅시다.

mysql> INSERT INTO member (m_id, m_name, m_area) VALUES (12, '홍길동', '서울);

mysql> COMMIT;

INSERT 문이 실행되면 DB의 상태는 다음과 같이 바뀔 것입니다.

MEMBER 테이블에 UPDATE 문장이 실행되면 다음 그림과 같은 절차를 거칩니다.

mysql> UPDATE member SET m_area='경기' WHERE m_id=12;

UPDATE 문장이 실행되면 커밋 실행 여부와 관계없이 InnoDB의 버퍼 풀은 새로운 값인 '경기'로 업데이트됩니다. 디스크의 데이터 파일에는 체크포인트나 InnoDB의 Write 스레드에 의해 새로운 값으로 업데이트돼 있을 수도 있고 아닐 수도 있습니다만, 일반적으로는 InnoDB가 ACID를 보장하기 때문에 버퍼 풀과 데이터 파일은 동일한 상태로 보아도 무방합니다.

아직 커밋이나 롤백이 되지 않은 상태에서 다른 사용자가 다음 같은 쿼리로 작업 중인 레코드를 조회하면 어디에 있는 데이터를 조회할까요?

mysql> SELECT * FROM member WHERE m_id=12;

이 질문의 답은 MySQL 서버의 시스템 변수 transcation_isolation에 설정된 격리 수준에 따라 다르다는 것입니다.

  • READ_UNCOMMITED: InnoDB 버퍼 풀이 현재 가지고 있는 변경된 데이터를 읽어서 반환합니다. 즉, 데이터가 커밋됐든 아니든 변경된 상태의 데이터를 반환합니다.

  • READ_COMMITTED 이상: 아직 커밋되지 않았기 때문에 변경되기 이전의 내용을 보관하고 있는 언두 영역의 데이터를 반환합니다.

이러한 과정을 DBMS에서는 MVCC라고 합니다. 즉, 하나의 레코드에 대해 2개의 버전이 유지되고, 필요에 따라 어느 데이터가 보여지는지 여러 가지 상황에 따라 달라지는 구조입니다. 위의 예시에서는 한 개의 데이터만 가지고 설명했지만 관리해야 하는 예전 버전의 데이터는 무한히 많아질 수 있습니다. 따라서 언두에서 관리하는 예전 데이터를 오랫동안 관리해야 하며, 자연히 언두 영역이 저장되는 시스템 테이블스페이스 공간이 많이 늘어나는 상황이 발생할 수도 있습니다.

이 상태에서 COMMIT 명령을 실행하면 InnoDB는 더 이상의 변경 작업 없이 지금의 상태를 영구적인 데이터로 만들고, ROLLBACK 명령을 실행하면 언두 영역에 있는 백업된 데이터를 버퍼 풀로 다시 복구하고, 언두 영역의 내용을 삭제해버립니다.

1.4. 잠금 없는 일관된 읽기(Non-Locking Consistent Read)

InnoDB 스토리지 엔진은 MVCC 기술을 이용해 잠금을 걸지 않고 읽기 작업을 수행합니다. 잠금을 걸지 않기 때문에 읽기 작업은 다른 Tx가 가지고 있는 잠금을 기다리지 않고, 읽기 작업이 가능합니다.

격리 수준이 SERIALIZABLE이 아니라면 INSERT와 연결되지 않은 순수한 SELECT 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행됩니다. 이를 '잠금 없는 일관된 읽기'라고 표현하며, InnoDB에서는 변경되지 전의 데이터를 읽기 위해 언두 로그를 사용합니다.

트랜잭션이 오랫동안 활성 상태인 경우, 일관된 읽기를 위해 언두 로그를 삭제하지 못하고 계속 유지하기 때문에 MySQL 서버가 느려지거나 문제가 발생할 수 있습니다. 따라서 트랜잭션이 시작됐다면 가능한 한 빨리 커밋이나 롤백을 통해 트랜잭션을 완료하는 것이 좋습니다.

1.5. 자동 데드락 감지

InnoDB 스토리지 엔진은 내부적으로 잠금이 교착 상태에 빠지지 않았는지 체크하기 위해 잠금 대기 목록을 그래프 형태로 관리합니다. InnoDB의 데드락 감지 스레드가 주기적으로 잠금 대기 그래프를 검사해 데드락에 빠진 트랜잭션들을 찾아서 그중 하나를 강제로 종료합니다. 이때 어느 트랜잭션을 먼저 종료할 것인지 판단하는 기준은 트랜잭션의 언두 로그 양이며, 언두 로그 레코드를 더 적게 가진 트랜잭션이 일반적으로 롤백의 대상이 됩니다.

동시 처리 스레드가 매우 많아지거나 각 트랜잭션이 가진 잠금의 개수가 많아지면 데드락 감지 스레드가 느려집니다. 데드락 감지 스레드가 느려지면 서비스 쿼리를 처리 중인 스레드는 작업을 진행하지 못하고 대기하면서 서비스에 악영향을 미치게 됩니다.

이런 문제점을 해결하기 위해 innodb_deadlock_detect를 OFF로 설정하면 데드락 감지 스레드는 더는 작동하지 않게 됩니다. 데드락 감지 스레드가 작동하지 않으면 데드락이 발생해도 누군가가 중재를 하지 않기 때문에 무한정 대기하게 될 것입니다. 하지만 innodb_lock_timeout 시스템 변수를 활성화하면 이런 데드락 상황에서 일정 시간이 지나면 자동으로 요청이 실패하고 에러 메시지를 반환하게 됩니다.

1.6. 자동화된 장애 복구

InnoDB에는 손실이나 장애로부터 데이터를 보호하기 위한 여러 가지 메커니즘이 탑재돼 있습니다. 그러한 메커니즘을 이용해 MySQL 서버가 시작될 때 완료되지 못한 트랜잭션이나 디스크에 일부만 기록된(Partial write) 데이터 페이지 등에 대한 일련의 복구 작업이 자동으로 진행됩니다.

InnoDB 데이터 파일은 기본적으로 MySQL 서버가 시작될 때 항상 자동 복구를 수행하고, 자동으로 복구될 수 없는 손상이 있다면 자동 복구를 멈추고 MySQL 서버가 종료돼 버립니다. 이때는 innodb_force_recovery 시스템 변수를 설정해서 MySQL 서버를 시작해야 합니다. 이 설정값은 MySQL 서버가 시작될 때 InnoDB 스토리지 엔진이 데이터 파일이나 로그 파일의 손상 여부 검사 과정을 선별적으로 진행할 수 있게 합니다.


🎯정리

  • InnoDB의 모든 테이블은 기본적으로 PK가 클러스터 인덱스이고, 이 클러스터 인덱스를 이용한 레인지 스캔은 상당히 빨리 처리됩니다.

  • InnoDB에서는 외래 키를 지원합니다.

  • InnoDB의 MVCC는 언두 로그를 이용하여 멀티 버전 중 보장되는 버전에 맞는 값을 반환하여 처리합니다. 애플리케이션 개발 및 운영에 있어 특히 중요한 개념으로 보입니다.

  • InnoDB에서는 격리 수준이 SERIALIZABLE이 아닌 경우, 읽기 작업이 잠금을 대기하지 않고 바로 실행됩니다.

  • InnoDB의 데드락 감지 스레드는 데드락에 빠진 트랜잭션들을 찾아서 강제 종료합니다.

  • InnoDB에는 손실이나 장애로부터 데이터를 보호하기 위한 기능이 탑재돼 있습니다.


🔖참고

0
Subscribe to my newsletter

Read articles from Ji, Seunghyeon directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ji, Seunghyeon
Ji, Seunghyeon