MySQL 스토리지 엔진 아키텍처
- InnoDB는 MySQL에서 사용할 수 있 는 스토리지 엔진 중 거의 유일하게 레코드 기반의 잠금을 제공. 동시성 처리가 가능하고 안정적이며 성능이 뛰어나다
1. 프라이머리 키에 의한 클러스터링
- InnoDB는 기본적으로 프라이머리 키를 기준으로 클러스터링되어 저장된다. 프라이머리 키 순서대로 디스크에 저장된다. 모든 세컨더리 인덱스는 레코드의 주소 대신 프 라이머리 키의 값을 논리적인 주소로 사용.
- 프라이머리 키가 클러스터링 인덱스이기 때문에 프라 이머리 키를 이용한 레인지 스캔은 상당히 빨리 처리됨. 쿼리의 실행 계획에서 프라이머리 키는 기본적으로 다른 보조 인덱스에 비해 비중이 높게 설정됨. 쿼리의 실행 계획에서 다른 보 조 인덱스보다 프라이머리 키가 선택될 확률이 높음.
- MyISAM 스토리지 엔진에서 프라이머리 키는 유니크 제약을 가진 세컨더리 인덱스일 뿐, 클러스터링 키를 지원하지 않는다. 그래서 MyISAM 테이블에서는 프라이머리 키와 세컨더리 인덱스는 구조적으로 아무런 차이가 없음. 모든 인덱스는 물리적인 레코드의 주소 값(ROWID)을 가짐.
2. 왜래키 지원
- InnoDB 스토리지 엔진 레벨에서 지원하는 기능으로 MyISAM이나 MEMORY 테이블에서는 사용할 수 없다.
- 외래 키는 데이터베이스 서버 운영의 불편함 때문에 서비스용 데이터베 이스에서는 생성하지 않는 경우도 자주 있는데, 그렇다 하더라도 개발 환경의 데이터베이스에서는 좋은 가이드 역할을 할 수 있다.
- InnoDB에서 외래 키는 부모 테이블과 자식 테이블 모두 해당 칼럼에 인덱스 생성이 필요하고, 변경 시에는 반드시 부모 테이블이나 자식 테이블에 데이터가 있는지 체크하는 작업이 필요하므로 잠금이 여러 테이블로 전파되고, 그로 인해 데드락이 발생할 때가 많으므로 개발할 때도 외래키의 존재에 주의하는 것이좋다.
foreign_key_checks
로 왜래키 제약을 일시적으로 멈출 수 있음
3. MVCC(Multi Version Concurrency Control)
- 한 레코드에 대해 여러 버전이 동시에 관리된다는 뜻.
- 레코드 레벨의 트랜잭션을 지원하는 DBMS 가 제공하는 기능. InnoDB 에서는 Undo Log 기반으로 구현됨
- DB 메모리에 InnoDB 버퍼풀, 언두 로그가 있고, 데이터 파일(디스크) 로 구성되어 있다. 데이터를 업데이트하는 SQL을 실행하면 변경 전 데이터는 언두 로그로 이동하고 InnoDB 버퍼풀 데이터는 변경이 된다. 커밋이 되면 디스크로 쓰인다. 만약 rollback 이 되면 InnoDB 버퍼풀 변경사항은 삭제되고 언두 로그에 있던 변경 전 데이터가 InnoDB 버퍼풀을 교체한다. 참고로 언두 로그는 커밋되면 삭제되는 것이 아니라 트랜잭션이 더이상 필요 없어질 떄 비워진다. 언제든 rollback을 할 수 있도록.
- 트랜잭션 규모가 클수록 언두 로그 메모리 사용량이 늘어나니 유의할 필요가 있다.
- 디스크의 데이터 파일에는 체크포인트나 InnoDB의 Write 스레드에 의해 새로운 값 으로 업데이트돼 있을 수도 있고 아닐 수도 있다(InnoDB가 ACID를 보장하기 때문에 일반적으로는 InnoDB의 버퍼 풀과 데이터 파일은 동일한 상태라고 가정해도 무방함)
- 위 예시 상태일 때, 다른 트랜잭션에서 레코드를 조회하면 트랜잭션 격리 수준 DB 설정에 따라 다른 결과가 나온다.
READ_UNCOMMITED
로 되어 있다면 커밋 여부를 상관하지 않고 InnoDB 버퍼풀 상태를 그대로 조회가 되고,READ_COMMITED
이상의 격리 수준이면 언두 로그 또는 디스크에 있는 데이터가 조회된다.
4. 잠금 없는 일관된 읽기(Non-Locking Consistent Read)
- InnoDB 스토리지 엔진은 MVCC 기술을 이용해 잠금을 걸지 않고 읽기 작업을 수행한다. 덕분에 InnoDB 에서는 읽는 작업을 할 때는 다른 트랜잭션의 잠금 해제를 기다릴 필요 없이 바로 읽을 수 있다.
- 격리 수준이 SERIALIZABLE이 아닌 READ_UNCOMMITTED나 READ_COMMITTED, REPEATABLE_READ 수준인 경우 INSERT와 연결되지 않은 순수한 읽기(SELECT) 작업은 다른 트랜잭션의 변경 작업과 관계없이 항상 잠금을 대기하지 않고 바로 실행된다
- 오랜시간 트랜잭션이 활성화되면 언두 로그를 비우지 못해 DB가 느려지는 경우가 있으니 유의. 이런 장시간의 트랜잭션은 커밋이나 롤백을 수행하여 언두로그를 비워주는 것이 필요할 수도 있다.
5. 자동 데드락 감지
- 데드락이란? 두 개의 프로세스가 각각 점유 중인 자원을 서로 상대방의 자원을 할당받기 위해 무한 대기하는 상태. 교착 상태
- 프로세스 P1은 리소스R2를 점유중이고, 프로세스 P2는 자원 R1을 점유 중이다. 이 때 프로세스 P1이 프로세스 P2가 점유중인 리소스 R1을 요구하고, 반대로 프로세스 P2가 프로세스 P1의 프로세스인 R2를 요구하고 기다리고 있다. P1과 P2 프로세스 둘 다 무한대기 상태에 빠지게 된다.
- 데드락 예방을 하기 위해 Safe Sequence 를 찾아서 순서를 정렬하여 데드락이 발생하지 않는 Safe State 상태로 만들 수 있다. 데드락이 발생할 확률이 있는 상태를 불안정 상태라 한다.
- 데드락이 발생하면 회복 절차가 필요하다. 그리고 회복을 하려면 탐지가 먼저다.
- InnoDB 데드락 탐지 및 회복 절차: 스토리지 엔진에서는 잠금 대기 목록을 그래프(Wait-for List) 형태로 관리하고, 데드락 감지 스레드가 주기적으로 그래프를 검사해서 교착 상태에 빠진 트랜잭션들을 찾아서 그 중 하나를 종료하는 방식으로 데드락을 해결한다.
- 데드락에 빠진 트랜잭션들 중에 언두Undo 로그 레코드량이 상대적으로 적으면 종료 대상이 된다. 왜냐하면 강제로 롤백할 양이 적기 때문에 서버 부하가 적어지기 때문이다.
innodb_table_locks
시스템 변수를 활성화하면, 스토리지 엔진의 상위 레이어인 MySQL 엔진에서 관리되는 테이블 잠금(Lock Tables로 잠긴 테이블)과 스토리지엔진의 레코드까지 범위를 확대해 데드락 감지를 할 수 있다. 특별한 이유가 없으면 이 변수를 활서오하 시키는 것을 권장한다.- 스레드 수가 많아져서 데드락 스레드가 느려져서 전체 스레드가 느려지는 현상이 발생할 수 있다. 데드락 스레드는 잠금 목록을 잠그고 데드락 탐지를 시작하면, 작업 스레드에서 잠금을 하고 잠금 목록에 추가해야햐는데 락이 걸려있어 대기하게 된다. 결국 데드락 스레드가 탐지를 마칠 떄까지 모든 작업 스레드는 멈춰있게 된다. 전체 스레드 수가 많아져서 데드락 스레드의 탐지가 늦어지면 늦어질수록 모든 작업들이 늦어진다.
- 그래서 작업량이 많아 부하가 많이 생길 때나 예상될때는
innodb_deadlock_detect
변수를 비활성화하여 데드락 탐지를 끄고,innodb_lock_wait_timeout
시간을 기본값 50초보다 짧게 하여 요청 타임아웃 수단을 통해 데드락에서 회복하도록 한다. 시간을 짧게 하지 않으면 데드락이 50초까지 유지되기 때문에 평소보다 줄이는 것이 좋다. innodb_deadlock_detect
설정이 만들어진 배경에는 구글의 요구가 있었다. 구글 서비스에서는 키 기반 조회 및 변경이 아주 높은 빈도로 실행되는 서비스들이 많다보니 동시에 수많은 트랜잭션이 실행되는 환경에 있었는데 데드락 감지 스레드가 상당히 성능을 저하시킨다는 것을 발견하고 데드락 탐지 스레드를 끌 수 있도록 MySQL 소스코드를 고쳐서 사용했었다. 이후에 구글은 MySQL 주인인 오라클에게 요청을 해서 데드락을 끄고 켤 수 있는 기능이 추가되었다.- 이런 배경을 참고해서 구글과 같이 트랜잭션양이 동시에 많은 서비스일 경우 데드락을 끄고 성능 비교 실험을 해보기를 추천한다.
- 그래서 작업량이 많아 부하가 많이 생길 때나 예상될때는
6. 자동화된 장애 복구
- InnoDB에선 MySQL 서버가 시작될 때 이전에 완료되지 못한 트랜잭션이나 디스크에 데이터가 일부분만 기록된 페이지 등에 대한 복구 작업을 진행한다.
- 디스크나 하드웨어 이슈로 InnoDB 스토리지 엔진이 복구를 하지 못하는 문제가 발생할 수 있다. 복구할 수 없는 상태일 경우 자동복구를 멈추고 MySQL 서버는 자동으로 꺼진다.
- 이 떄
innodb_force_recovery
시스템 변수를 설정해서 데이터 파일이나 로그 파일의 손상 여부 겸사 과정을 선별적으로 진행해야 한다. - 시스템 설정값은 1부터 6까지 있으며, 숫자가 높을수록 심각한 상황이여서 손실 가능성이 커서 복구가 힘들 확률이 높다.
1(SRV_FORCE_IGNORE_CORRUPT)
: InnoDB의 테이블스페이스의 데이터나 인덱스가 손상되어도 MySQL 서버를 가동한다. 보통Database page corruption on disk or a failed’
에러로그가 떴을 때 이 문제일 경우가 많다. 이 모드로 서버가 가동되면, 직접 데이터를 덤프하여 데이터베이스를 새로 구축할 수 있다.2(SRV_FORCE_NO_BACKGROUND)
: InnoDB의 메인 백그라운드 스레드들을 실행하지 않는다. InnoDB 의 메인 스레드가 언두 데이터를 삭제하는 작업 도중 장애가 발생했을 때 이 모드를 사용하면 된다. (언두 데이터를 삭제하는 작업은 트랜잭션 커밋이 될 때 언두 데이터를 비우기 위해 실행된다)3(SRV_FORCE_NO_TRX_UNDO)
: 일반적으로 MySQL 서버는 언두로그(트랜잭션 전 데이터 상태)를 데이터에 적용하고 리두로그를 적용하여 장애 시점의 상태를 만들어낸다. MySQL 서버가 정상 시작을 하면 이 상태에서 롤백을 수행한다. 하지만 이 모드를 사용하면, 커밋되지 않는 트랜잭션 작업을 롤백하지 않고 그대로 놔둔 상태로 MySQL 서버가 시작된다. 데이터를 덤프하여 백업해서 다시 데이터베이스를 구축할 수 있다.4(SRV_FORCE_NO_IBUF_MERGE)
: Insert, Update, Delete 데이터 변경에 따른 인덱스 변경 작업을 상황에 따라 즉시 처리할 수 있고, 인서트 버퍼에 잠시 넣어두고 나중에 일시에 데이터 파일에 Merge 처리할 수도 있다. 이 Merge 시기는 언제될지 알 수 없고, 서버가 종료되더라도 Merge 되지 않는 경우도 있다. 그래서 MySQL 서버가 정상적으로 시작되면, 인서트 버퍼 데이터가 손상되었음을 확인하면 서버는 자동 종료된다. 이 때 이 모드를 활성화해서 서버를 시작하면 이 인서트 버퍼 상태를 무시하고 서버를 시작할 수 있다. 이 경우에도 데이터를 덤프해서 데이터베이스를 새로 구축해서 복구할 수 있다.5(SRV_FORCE_NO_UNDO_LOG_SCAN)
: 언두 로그 상태를 무시하고 서버를 시작할 수 있다. 정상적으로 서버 시작하면 완료되지 않는 트랜잭션을 언두로그와 리두로그를 통해 실행 상태로 돌아간다음에 롤백을 수행한다. 하지만 언두로그 데이터 손상이 되면 롤백이 안되어 서버가 종료된다. 이 문제에 이 모드를 사용하면 된다. 하지만 언두로그(트랜잭션 시작 전 상태)를 사용할 수 없기 때문에, 완료되지 않은 트랜잭션이 커밋된 상태에서 서버가 실행된다. 즉 잘못된 데이터가 데이터베이스 남아있는 상태이다. 이 경우에도 덤프를 통해 데이터베이스를 새로 구축해서 복구하는 방법을 사용할 수 있다.6(SRV_FORCE_NO_LOG_REDO)
: 리두로그가 손상됐을 경우 서버가 시작되지 않는다. 리두로그 손상을 무시하고 서버를 시작하는 모드이다. 또한 커밋되었지만 리두로그에만 기록되었고 데이터 파일에 기록되지 않은 데이터는 무시된다. 즉, 마지막 체크 포인트 데이터만 남아있게 된다. 기존 리두로그를 수동으로 모두 삭제(또는 별도 디렉토리에 백업)하고 서버를 시작하는 것을 권장.- 위 여섯가지 모드로도 안될 경우, 백업해둔 데이터를 복구하고 바이너리 로그를 사용해 최대한 장애 시점 근처까지 복구하는 것을 권장. 자세한 내용은
innodb_force_recovery
시스템 변수 참고.
'공부노트 > 데이터베이스' 카테고리의 다른 글
데이터베이스 Lock (0) | 2023.08.25 |
---|---|
트랜잭션 격리 수준 (0) | 2023.08.22 |
ACID 원칙 (0) | 2023.08.21 |
MySQL 메모 (0) | 2022.12.05 |
MYSQL 엔진 아키텍처 (0) | 2022.11.26 |