InnoDB 스토리지 엔진 아키텍처 (2)
4. 잠금 없는 일관된 읽기
5. 자동 데드락 감지
6. 자동화된 장애 복구
InnoDB 스토리지 엔진 전체 구조
InnoDB는 MySQL에서 사용할 수 있는 스토리지 엔진 중 유일하게 레코드 기반의 잠금 기능을 제공하기 때문에 안정적으로 높은 동시성 처리가 가능하며, 성능 또한 우수하다.
4. 잠금 없는 일관된 읽기(Non-Locking Consistent Read)
InnoDB 스토리지 엔진은 MVCC 기술을 이용해 잠금을 걸지 않고 읽기 작업을 수행한다. 잠금을 걸지 않기 때문에 InnoDB에서 읽기 작업은 다른 트랜잭션이 갖고 있는 잠금을 기다리지 않고, 읽기 작업이 가능하다.
격리 수준이 SERIALIZABLE이 아닌 READ_UNCOMMITTED나 READ_COMMITTED, REPEATABLE_READ 수준인 경우, INSERT와 연결되지 않은 순수한 읽기(SELECT) 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다.
특정 사용자가 레코드를 변경하고 아직 커밋을 하지 않았을 경우에도, 이 변경 트랜잭션이 다른 사용자의 SELECT 작업을 방해하지 않는다. 이를 '잠금 없는 일관된 읽기'라고 표현하고, InnoDB에서는 변경되지 전의 데이터를 읽기 위해 언두 로그를 사용한다.
오랜 시간동안 활성화된 트랜잭션으로 인해 MySQL 서버가 느려지거나 문제가 발생할 때가 있는데, 이러한 일관된 읽기를 위해 언두 로그를 삭제하지 못하고 계속 유지해야 하기 때문에 발생하는 문제이다. 따라서, 트랜잭션이 시작됐다면 가능한 빨리 롤맥이나 커밋을 통해 트랜잭션을 완료하는게 좋다.
5. 자동 데드락 감지
데드락 감지 스레드
InnoDB 스토리지 엔진은 내부적으로 잠금이 교착 상태에 빠지지 않았는지 체크하기 위해 잠금 대기 목록을 그래프(Wait-for List) 형태로 관리한다. 데드락 감지 스레드를 가지고 있어서 데드락 감지 스레드가 주기적으로 잠금 대기 그래프를 검사해 교착 상태에 빠진 트랜잭션들을 찾아 그중 하나를 강제로 종료한다.
이때, 어느 트랜잭션을 먼저 강제 종료할지 판단하는 기준은 트랜잭션의 언두 로그 양이며, 언두 로그 레코드를 더 적게 가진 트랜잭션이 일반적으로 롤백의 대상이 된다. - 언두 레코드를 적게 가졌다는 말은 롤백을 해도 언두 처리를 해야 할 내용이 적다는 것이며, 트랜잭션 강제 롤백으로 인한 MySQL 서버 부하도 덜 유발한다는 뜻임 -
innodb_table_locks 시스템 변수를 활성화 하면 InnoDB 스토리지 엔진 내부의 레코드 잠금뿐만 아니라 테이블 레벨의 잠금까지 감지할 수 있기 때문에, 특별한 이유가 없으면 Innodb_table_lokcs 시스템 변수는 활성화 해두면 좋다
동시 처리 스레드가 많은 경우에 문제해결
일반적인 서비스에서 데드락 감지 스레드가 트랜잭션의 잠금 목록을 검사해서 데드락을 찾아내는 자겁은 크게 부담되지 않지만, 동시 처리 스레드가 매우 많아지거나 각 트랜잭션이 가진 잠금의 개수가 많아지면 데드락 감지 스레드가 느려진다. 이 경우 서비스 쿼리를 처리 중인 스레드는 더는 작업을 진행하지 못하고 대기하면서 서비스에 악영향을 미치게 된다. 즉, 동시 처리 스레드가 매우 많은 경우, 데드락 감지 스레드는 더 많은 CPU 자원을 소모할 수 있다.
이런 문제해결을 위해 MySQL 서버는 innodb_deadlocak_detect 시스템 변수를 제공하며, innodb_deadlock_detect를 OFF로 설정하면 데드락 감지 스레드는 더는 작동하지 않게 된다. 데드락 감지 스레드가 작동하지 않으면 InnoDB 스토리지 엔진 내부에서 2개 이상의 트랜잭션이 상대방이 가진 잠금을 요구하는 상황(데드락 상황)이 발생해도 누군가가 중재를 하지 않기 때문에 무한정 대기하게 될 것이다.
데드락 감지 OFF 문제의 보완방법
하지만, innodb_wait_timeout 시스템 변수를 활성화하면 이런 데드락 상황에서 일정 시간이 지나면 자동으로 요청이 실패하고 에러 메세지를 반환하게 된다. Innodb_lock_timeout은 초 단위로 설정할 수 있으며, 잠금을 설정한 시간 동안 획득하지 못하면 쿼리는 실패하고 에러를 반환한다.
데드락 감지 스레드가 부담되어 innodb_deadlock_detect를 OFF로 설정해서 비활성화하는 경우라면, innodb_lock_wait_timeout을 기본값인 50초보다 훨씬 낮은 시간으로 변경하여 사용할 것을 권장한다.
6. 자동화된 장애 복구
자동 복구 메커니즘
InnoDB에는 손실이나 장애로부터 데이터를 보호하기 위한 여러 가지 메커니즘이 탑재돼 있다. 그러한 메커니즘을 이용해 MySQL 서버가 시작될 때 완료되지 못한 트랜잭션이나 디스크에 일부만 기록된 데이터페이지 등에 대한 일련의 복구 작업이 자동으로 진행된다.
InnoDB 스토리지 엔진은 견고하여 데이터 파일이 손상되거나 MySQL 서버가 시작되지 못하는 경우는 거의 발생하지 않는다. 다만, 서버와 무관하게 디스크나 서버 하드웨어 이슈로 InnoDB 스토리지 엔진이 자동 복구를 못 하는 경우가 발생할 수 있는데, 일단 한 번 문제가 생기면 복구하기 어렵다.
자동 복구 불가능한 손상 문제
InnoDB 데이터 파일은 기본적으로 MySQL 서버가 시작될 때 항상 자동 복구를 수행하지만, 이 단계에서 자동으로 복구할 수 없는 손상이 있다면, 자동복구를 멈추고 MySQL 서버는 종료된다.
이때, MySQL 서버 설정 파일에 innodb_force_recovery 시스템 변수를 설정해서 MySQL 서버를 시작해야 한다. 이 설정값은 MySQL 서버가 시작될 때 innoDB 스토리지 엔진이 데이터 파일이나 로그 파일의 손상 여부 검사 과정을 선별적으로 진행할 수 있게 한다.
innodb_force_recovery 시스템 변수 설정을 통핸 자동복구 해결 방법
- InnoDB 로그파일이 손상됐다면, 6으로 설정하여 MySQL 서버를 기동
- InnoDB 테이블의 데이터 파일이 손상됐다면, 1로 설정하고 MySQL 서버를 기동
- 어떤 부분이 문제인지 알 수 없으면, innodb_force_recovery 설정값을 1부터 6까지 변경하면서 MySQL을 재시작
MySQL 서버가 기동되고 InnoDB 테이블이 인식된다면 mysqldump를 활용해 데이터를 가능한 만큼 백업하고 그 데이터로 MySQL 서버의 DB와 테이블을 다시 생성하는 것이 좋다.
innodb_force_recovery 설정값별 장애상황 및 해결방법
1 : SRV_FORCE_IGNORE_CORRUPT
- InnoDB 테이블 스페이스의 데이터나 인덱스 페이지에서 손상된 부분이 발견되어도 무시하고 MySQL 서버 시작
- 대부분은 에러 로그 파일에 'Database page corruption on disk or a failed' 메세지가 출력됨
- 이 경우, mysqldump 프로그램이나 SELECT INTO OUTFILE ... 명령을 이용해 덤프해서 DB를 재구축하는 것이 좋음
2 : SRV_FORCE_NO_BACKGROUND
- InnoDB는 쿼리의 처리를 위해 여러 종류의 백그라운드 스레드를 동시에 사용함. 이 복구 모드에서는 이러한 백그라운드 스레드 가운데 메인 스레드를 시작하지 않고 MySQL 서버를 시작함
- InnoDB는 트랜잭션 롤백을 위해 언두 데이터를 관리하는데, 트랜잭션이 커밋되어 불필요한 언두 데이터는 InnoDB 메인 스레드에 의해 주기적으로 삭제됨
- InnoDB 메인 스레드가 언두 데이터를 삭제하는 과정에서 장애가 발생한다면, 2번 모드로 복구하면 된다.
3 : SRV_FORCE_NO_TRX_UNDO
- InnoDB에서 트랜잭션이 실행되면 롤백에 대비해 변경 전의 데이터를 언두영역에 기록함
- 일반적으로 서버는 다시 시작하면서 언두 영역의 데이터를 먼저 데이터 파일에 적용하고 그 다음 리두 로그의 내용을 다시 덮어써서 장애 시점의 데이터 상태를 만들어 냄
- 그리고, 정상적인 서버의 시작에서는 최종적으로 커밋되지 않은 트랜잭션은 롤백을 수행하지만 innodb_force_recovery가 3으로 설정되면 커밋되지 않은 트랜잭션의 작업을 롤백하지 않고 그대로 놔둠. 즉, 커밋되지 않고 종료된 트랜잭션은 계속 그 상태로 남아 있게 MySQL 서버를 시자가는 모드
- 이때도, 우선 서버가 시작되면 mysqldump를 이용해 데이터를 백업해서 DB를 재구축하는 것이 좋음
4 : SRV_FORCE_NO_IBUF_MERGE
- InnoDB는 INSERT, UPDATE, DELETE 등 데이터 변경으로 인한 인덱스 변경 작업을 상황에 따라 즉시 처리할 수도 있고, 인서트 버퍼에 저장해두고 나중에 처리할 수 있음
- 이렇게 인서트 버퍼에 기록된 내용은 언제 데이터 파일에 병합(merge)될지 알 수 없음
- MySQL를 종료해도 병합되지 않을 수 있는데, 만약 MySQL이 재시작되면서 인서트 버퍼에 손상을 감지하면 InnoDB는 에러를 발생시키고, MySQL 서버는 시작하지 못함
- 이 경우에 4번 모드로 설정하는데, InnoDB 스토리지 엔진이 인서트 버퍼의 내용을 무시하고 강제로 MySQL을 시작시킴. 인서트 버퍼는 실제 데이터와 관련된 부분이 아니라 인덱스에 관련된 부분이므로 mysqldump를 이용해 데이터를 백업해서 DB를 재구축하는 것이 좋음
5 : SRV_FORCE_NO_UNDO_LOG_SCAN
- MySQL 서버가 장애나 정상적으로 종료되는 시점에 진행 중인 트랜잭션이 있었다면 MySQL은 단순히 커넥션을 강제로 끊고, 별도의 정리 작업 없이 종료함.
- MySQL이 다시 시작되면 InnoDB 엔진은 언두 레코드를 이용해 데이터 페이지를 복구하고 리두 로그를 적용해 종료시점이나 장애발생시점의 상태를 재현함. 그리고, InnoDB는 커밋되지 않은 트랜잭션에서 변경한 작업은 모두 롤백처리함.
- 이때, MySQL이 언두 로그를 사용할 수 없다면 에러로 MySQL 서버를 시작할 수 없다.
- 5번 모드는 언두 로그를 모두 무시하고 MySQL을 시작할 수 있다. 하지만, 서버가 종료되던 시점에 커밋되지 않았던 작업도 모두 커밋된 것처럼 처리되므로 실제로는 잘못된 데이터가 DB에 남을 수 있다. mysqldump를 이용해 데이터를 백업해서 DB를 재구축하는 것이 좋음
6 : SRV_FORCE_NO_LOG_REDO
- InnoDB 스토리지 엔진의 리두 로그가 손상되면 MySQL 서버가 시작되지 못함
- 6번 모드는 리두 로그를 무시하고 서버가 시작된다. 또한 커밋되더라도 리두 로그에만 기록되고 데이터 파일에 기록되지 않은 데이터는 모두 무시함
- 즉, 마지막 체크포인트 시점의 데이터만 남음. 이때는 기존 리두로그는 모두 삭제하고 서버를 시작하는 것이 좋음. 이때도 mysqldump를 이용해 데이터를 백업해서 DB를 재구축하는 것이 좋음
풀 백업 및 바이너리 로그 활용
위의 6가지 모드를 통해서도 MySQL 서버가 시작되지 않으면, 백업을 이용해 다시 구축하는 방법밖에 없다. 백업이 있다면 마지막 백업으로 데이터베이스를 새로 구축하고, 바이너리 로그를 사용해 최대한 장애 시점까지의 데이터를 복구할 수 있다.
마지막 풀 백업 시점부터 장애시점까지의 바이너리 로그가 있다면 InnoDB의 복구를 이용하는 것보다 데이터 손실이 적을 수 있다.
출처: ⌜Real MySQL 8.0 (개발자와 DBA를 위한 MySQL 실전 가이드)⌟, 백은빈, 이성욱 지음, 위키북스, 2021.09.08 출간
'Data Engineering > MySQL' 카테고리의 다른 글
[Query] JOIN 쿼리문의 이해 (국제표준 ANSI 조인 기준) (0) | 2022.11.12 |
---|---|
[ MySQL 아키텍처 ] 2. InnoDB 스토리지 엔진 아키텍처 (3) (0) | 2022.10.06 |
[ MySQL 아키텍처 ] 2. InnoDB 스토리지 엔진 아키텍처 (1) (0) | 2022.10.05 |
[ MySQL 아키텍처 ] 1. MySQL 엔진 아키텍처 (3) (0) | 2022.10.05 |
[ MySQL 아키텍처 ] 1. MySQL 엔진 아키텍처 (2) (2) | 2022.10.04 |