-
InnoDB 스토리지 엔진의 리두로그는 로그 공간의 낭비를 막기 위해 페이지의 변경된 내용만 기록
-
InnoDB 스토리지 엔진에서 더티 페이지를 디스크 파일로 플러시할 때 일부만 기록되는 문제가 발생하면(하드 웨어의 오작동, 시스템 비정상 종료등 으로 인해) 그 페이지의 내용은 복구가 불가능 할 수도...
-
이렇게 페이지가 일부만 기록되는 현상을 -> 파셜페이지(Partial-page) 또는 톤 페이지(Torn-page)
=> InnoDB 스토리지 엔진에서는 이러한 문제를 막기 위해 Double-Write 기법 이용
- 변경된 데이터 페이지를 모아서 한 번의 디스크 쓰기로 시스템 테이블스페이스의 Double Write 버퍼에 기록
- 데이터 페이지를 개별로 파일로 기록
-
페이지 기록 중 비정상적 종료가 되었을 경우, InnoDB 스토리지 엔진은 재시작될 때 항상 Double Write 버퍼의 내용과 데이터 파일의 페이지들을 모두 비교해서 다른 내용을 담고 있는 페이지가 있으면 Double Write 버퍼의 내용을 데이터 파일의 페이지로 복사.
-
innodb_doublewrite 라는 시스템 변수로 사용할지 여부 제어 가능
-
안정성을 위해 주로 사용
-
그러나 기존의 회전하는 저장 시스템 방식이 아닌 SSD에서는 부담스러움
-
데이터의 무결성이 매우 중요한 서비스에서 고려하자~
-
추가적으로 무결성에 민감한 서비스라면 해당 Double Write뿐만 아니라 리두 로그, 복제를 위한 바이너리 로그 등 트랜잭션을 COMMIT하는 시점에 동기화할 것들이 많다는 것에 주의하자!
-
백업된 데이터
-
어떻게 사용?
- 트랜잭션 보장
- 격리 수준 보장
-
중요한 역할을 담당하지만 관리 비용이 크다.
-
update 문을 이용해서 name을 'HelloMan'으로 바꾼다고 가정
-
트랜잭션을 거밋하지 않아도 실제 데이터 파일 내용은 'HelloMan'으로 변경
-
그리고 기존 값이 언두 영역에 백업
-
커밋하면 그대로 유지, 롤백하면 언두 영역의 데이터로 복구
-
대용량의 데이터를 처리하는 트랜잭션 OR 트랜잭션이 오랜 시간 동안 실행될 때 언두로그 양이 급격히 증가
-
트랜잭션이 완료됐다고 해서 해당 트랜잭션이 생성한 언두 로그를 즉시 삭제할 수 있는 것은 아니다. 먼저 실행한 트랜잭션이 해당 언두로그를 참조할 수 있기에 지속적으로 유지 -> 언두 로그양 증가
-
이렇게 언두 로그양이 증가하면 공간적 문제 뿐 아니라 빈번하게 변경된 레코드를 조회하는 쿼리가 실행되면 InnoDB 스토리지 엔진은 언두 로그의 이력을 필요한 만큼 스캔해야 필요한 레코드를 찾을 수 있기에 쿼리 성능이 전반적으로 떨어짐
-
MySQL 5.5까지는 언두 로그의 사용 공간이 한번 늘어나면 서버를 새로 구축하지 않는 한 줄일 수 없었지만 8.0인 현재 언두 로그 공간 문제 완전히 해결(언두 로그 순차적 사용, MySQL 서버가 필요한 시점에 사용공간 자동으로 줄이기 등으로)
-
그래도 여전히 MySQL 서버에서 활성 상태의 트랜잭션이 장시간 유지되는 것은 성능상 안 좋다! -> 즉 항상 언두 로그 레코드가 얼마나 되는지 항상 모니터링 해야한다! 안정적인 시점의 언두 로그 레코드 건수를 기준으로!
-
언두 테이블스페이스 -> 언두 로그가 저장되는 공간
-
기존 언두 로그는 시스템 테이블스페이스에 저장 -> 확장의 한계
-
8.0부터 기본적으로 시스템 테이블스페이스 외부의 별도 로그 파일에 기록
-
언두 테이블스페이스 구조 : 언두 테이블스페이스 > 1~128개 롤백 세그먼트 > 1개 이상의 언두 슬롯
-
하나의 롤백 세그먼트는 InnoDB의 페이지 크기를 16바이트로 나눈 값의 개수 감큼 언두 슬롯을 가짐.
-
하나의 트랜잭션이 필요로 하는 언두 슬롯의 개수는 트랜잭션이 실행하는 INSERT, UPDATE, DELETE 문장의 특성에 따라 최대 4개 -> 일반적으로 대략 2개
-
최대 동시 트랜잭션 수 = (InnoDB페이지 크기 / 16) * (롤백 세그먼트 개수) * (언두 테이블스페이스 개수)
-
위처럼 대략 각 트랜잭션이 언두 슬롯 2개를 먹는다고 가정하면 나누기 2를 해야겠죠?
-
Undo tablespace truncate -> 언두 테이블스페이스 공간을 필요한 만큼만 남기고 불필요하거나 과도하게 할당된 공간을 운영체제로 반납
-
자동 모드: InnoDB 스토리지 엔진의 퍼지 스레드가 주기적으로 깨어나서 언두 로그 공간에서 불필요해진 언두 로그를 삭제하고 언두 로그 파일에서 사용되지 않는 공간을 잘라내고 운영체제로 반납. -> 이 작업을 언두 퍼지라고 함. 언두 퍼지를 사용할지, 얼마나 자주 실행되게 할지는 시스템 변수로 설정 가능
-
수동 모드: 시스템 변수로 언두 퍼지의 자동 실행을 off했거나 생각보다 자동 모드로 언두 테이블스페이스의 공간 반납이 부진한 경우 -> 아예 언두 테이블스페이스를 비활성화 -> 퍼지 스레드가 비활성 상태의 언두 테이블스페이스를 찾아서 불필요한 공간을 잘라내서 운영체제로 공간 반납 -> 반납 완료후 다시 언두 테이블스페이스 활성화 (수동 모드는 언두 테이블스페이스가 최소 3개이상은 돼야 작동)
-
- READ UNCOMMITTED : A 트랜잭션의 변경 내용이 Commit이나 Rollback 상관없이 B 트랜잭션에서 보여짐
- READ COMMITTED : A 트랜잭션의 변경 내용이 Commit되어야만 B 트랜잭션에서 보여짐
- REPETABLE READ : 각 트랜잭션이 시작되기 전에 커밋된 내용에 대해서만 조회 가능
- SERIALIZABLE : 가장 단순하고 엄격 -> 순수한 SELECT 작업에도 공유 잠금
- 아래로 내려갈수록 트랜잭션간 고립정도는 높아지지만, 성능은 떨어짐
-
레코드가 INSERT되거나 UPDATE될 때는 데이터 파일을 변경하는 작업 뿐 아니라 해당 테이블에 포함된 인덱스를 업데이트하는 작업도 필요 -> 랜덤하게 디스크를 읽는 작업이 필요 -> 테이블 인덱스가 많다면 소모가 큼
-
변경할 인덱스 페이지가 버퍼 풀에 있으면 바로 업데이트를 수행
-
없으면? -> 즉시 실행하지 않고 임시 공간에 저장 -> 바로 사용자에게 결과를 반환 -> 성능 향상 (원래는 디스크로부터 읽어와야했으니까!)
-
이때 사용하는 임시 메모리 공간 => 체인지 버퍼(Change Buffer)
-
단 중복 여부 체크가 필요한 유니크 인덱스는 체인지 버퍼 사용 불가
-
체인지 버퍼에 임시로 저장된 인덱스 레코드 조각은 이후 백그라운드 스레드에 의해 병합 -> 이 역할을 수행하는 버퍼 머지 스레드
-
8.0부터 해당 버퍼링 기능이 INSERT, DELETE, UPDATE로 인해 키를 추가하거나 삭제하는 작업에 대해서도 가능하게 개선.
-
또한 시스템 변수로 체인지 버퍼의 사용이 비효율적일 때는 사용하지 않을 수 있게 개선.
-
체인지 버퍼의 공간 크기도 시스템 변수로 설정 가능.
-
리두 로그는 트랜잭션의 4가지 요소인 ACID 중에서 D에 해당하는 영속성과 가장 밀접하게 관련
-
리두 로그 -> 서버가 비정상적으로 종료됐을 때, 데이터 파일에 기록되지 못한 데이터를 잃지 않게 해주는 안전 장치의 역할
-
모든 DBMS의 데이터 파일은 쓰기보다 읽기 성능을 고려한 자료구조 -> 파일 쓰기는 디스크의 랜덤 엑세스 필요 -> 상대적으로 큰 비용
-
성능 저하를 막기 위해 쓰기 비용이 낮은 자료구조를 가진 리두 로그를 가짐.
-
서버의 비정상적 종료 -> 데이터 파일은 2가지의 일관되지 않은 데이터
- 커밋 -> 그러나 데이터 파일에 기록 X
- 롤백 -> 그러나 데이터 파일에 기록 X
-
1 -> 리두 로그 사용
-
2 -> 언두 로그 사용 -> 그러나 이 때도 커밋됐는지, 롤백됐는지, 아니면 트랜잭션 실행 중간 상태였는지 확인하기 위해 리두 로그 필요
-
DB서버에서 리두 로그는 트랜잭션이 커밋되면 즉시 디스크로 기록되도록 시스템 변수를 설정하는 것을 권장 -> 그래야 비정상적 종료시 직전까지의 커밋 내용이라도 리두 로그에 남고 해당 시점까지 복구가 가능하니까
-
근데 커밋될 때마다 리두 로그를 디스크에 기록하고 동기화하는 작업은 많은 부하를 유발 -> 시스템 변수로 어느 주기로 디스크에 동기화할지 결정
-
리두 로그 파일들의 전체 크기는 InnoDB 스토리지 엔진이 가지고 있는 버퍼 풀의 효율성을 결정하기에 신중히 결정 -> 시스템 변수로 결정 가능
-
적절히 리두 로그 파일의 전체 크기가 설정되어 있어야 버퍼 풀에 모았다가 한 번에 모아서 디스크로 기록이 원활
-
사용량이 매우 많은 DBMS의 경우 리두 로그 기록 작업이 문제가 될 수도... -> 이러한 부분을 보완하기 위해 최대한 ACID 속성을 보장한느 수준에서 버퍼링 -> 이 버퍼링에 사용되는 공간이 로그 버퍼
-
트랜잭션의 무결성을 보장하기 위해 꼭 필요한 4가지 요소
- A -> Atomic : 트랜잭션은 원자성 작업이어야 한다.(모든 작업이 반영되거나 롤백)
- C -> Consistent : 일관성
- I -> Isolated : 격리성
- D -> Durable : 한 번 저장된 데이터는 지속적으로 유지돼야한다. (지속성)
-
일관성과 격리성은 서로 다른 두 개의 트랜잭션에서 동일 데이터를 조회하고 변경하는 경우에도 상호 간섭이 없어야한다는 것을 의미!
-
8.0부터 리두 로그를 아카이빙할 수 있는 기능 추가
-
리두 로그 아카이빙 기능은 데이터 변경이 많아서 리두 로그가 덮어쓰여도 백업이 실패 X
-
백업 툴이 리두 로그 아카이빙을 사용하기 위해서 먼저 아키이빙된 리두 로그가 저장될 디렉터리를 시스템 변수에 설정 -> 해당 디렉터리는 MySQL 서버를 실행하는 유저만 접근이 가능해야함!
-
리두 로그 아카이빙이 비정상적으로 끊어지면 해당 아카이빙 파일도 지워지게 된다!
-
MySQL 서버에서 트랜잭션이 커밋돼도 데이터 파일은 즉시 디스크로 동기화되지 않는 반면, 리두 로그(트랜잭션 로그)는 항상 디스크로 기록
-
8.0부터는 수동으로 리두 로그를 활성화, 비활성화 가능
-
데이터를 복구하거나 대용량 데이터를 한번에 적재하는 경우, 리두 로그를 비활성화해서 데이터의 적재 시간을 단축시킬 수 있음.
-
위처럼 비활성화 후 다시 로그 활성화 필수! (데이터 복구의 문제, 체크 포인트 시점의 문제등 발생 가능)
-
어댑티브 해시 인덱스 -> 사용자가 수동으로 생성하는 인덱스가 아니라 InnoDB 스토리지 엔진에서 사용자가 자주 요청하는 데이터에 대해 자동으로 생성하는 인덱스 -> 시스템 변수를 통해 활성화, 비활성화 가능
-
B-Tree 인덱스에서 특정 값을 찾기 위해 -> Root노드 -> 브랜치 노드 -> 리프 노드 순으로 탐색
-
적당한 사양의 컴퓨터에서 해당 작업을 동시에 몇 개 실행하는 것은 상관없으나 동시에 몇천 개의 스레드로 실행하면 컴퓨터의 CPU는 엄청난 프로세스 스케줄링을 하게 되고 자연히 쿼리의 성능이 떨어짐.
-
어댑티브 해시 인덱스는 이러한 B-Tree 검색 시간을 줄여주기 위한 기능
-
InnoDB 스토리지 엔진은 자주 읽히는 데이터 페이지의 키 값을 이용해 해시 인덱스를 만들고, 필요할 때마다 어댑티브 해시 인덱스를 검색해서 레코드가 저장된 데이터 페이지를 즉시 찾아갈 수 있음 -> B-Tree를 루트 노드부터 리프 노드까지 찾아가는 비용이 사라짐 -> CPU가 더 적은 일을 하게되고 쿼리의 성능이 빨라짐. -> 더 많은 쿼리를 동시 처리 가능
-
해시 인덱스는 "인덱스 키 값, 데이터 페이지의 주소"쌍으로 관리
-
인덱스 키 값 : B-Tree 인덱스의 고유번호(Id)와 B-Tree 인덱스의 실제 키 값의 조합으로 생성
-
B-Tree 인덱스의 고유번호가 들어가는 이유는 InnoDB 스토리지 엔진에서 어댑티브 해시 인덱스는 하나만 존재하기 때문
-
즉 모든 B-Tree 인덱스에 대한 어댑티브 해시 인덱스가 하나의 해시 인덱스에 저장되기 때문에 -> 특정 키 값이 어느 인덱스에 속한 것인지 구분 필요함
-
데이터 페이지의 주소는 실제 키 값이 저장된 데이터 페이지의 메모리 주소를 가짐 -> 버퍼 풀에 로딩된 페이지의 주소를 의미 -> 그러므로 어댑티브 해시 인덱스는 버퍼 풀에 올려진 데이터 페이지에 대해서만 관리 -> 버퍼 풀에서 해당 데이터 페이지가 없어지면 어댑티브 해시 인덱스에서도 해당 페이지 정보가 사라짐.
-
어댑티브 해시 인덱스는 하나의 메모리 객체인 이유로 경합이 상당히 심함 -> 8.0부터 파티션 기능을 제공 -> 시스템 변수로 파티션 개수 변경 가능
-
어댑티브 해시 인덱스를 의도적으로 비활성하는 경우도 많음
-
어댑티브 해시 인덱스가 성능 향상에 큰 도움이 안되는 경우
- 디스크 읽기가 많은 경우
- 특정 패턴의 쿼리가 많은 경우 (조인 or Like패턴 검색)
- 매우 큰 데이터를 가진 테이블의 레코드를 폭넓게 읽는 경우
-
성능향상에 도움이 되는 경우
- 디스크의 데이터가 InnoDB 버퍼 풀 크기와 비슷한 경우(디스크 읽기가 많지 않은 경우)
- 동등 조건 검색(동등 비교와 IN 연산자)이 많은 경우
- 쿼리가 데이터 중에서 일부 데이터에만 집중되는 경우
- 데이터 페이지를 디스크에서 읽어오는 경우가 빈번한 경우(어댑티브 해시 인덱스는 데이터 페이지를 메모리(버퍼 풀)내에서 접근하는 것을 더 빠르게 만드는 기능이기 때문)
-
해당 기능도 결국 메모리를 사용하는 기능임. 그것도 때로는 상당히 큰 메모리를 차지할 수 있음 -> 그렇기에 성능향상에 도움이 안될 경우 비활성화하는 것이 좋음
-
어댑티브 해시 인덱스 활성화시: 데이터 페이지의 인덱스 키가 해시 인덱스로 만들어져야하고 불필요한 경우 제거도 필요 -> InnoDB 스토리지엔진은 해당 기능이 활성화 중에는 그 키 값이 해시 인덱스에 있든 없든 검색을 해봐야 알 수 있음. 즉 해시 인덱스의 효율이 없는 경우에도 InnoDB는 계속 해시 인덱스를 사용
-
삭제 작업 시에도 해당 데이터를 삭제시 어댑티브 해시 인덱스에서도 삭제해야하기에 CPU자원을 많이 사용해야함
-
즉 어댑티브 해시 인덱스의 도움을 많이 받을수록 테이블 삭제 또는 변경 작업이 더욱 치명적인 작업이 됨
-
즉 우리 서비스 패턴에 맞게 도움이 되는지 아니면 불필요한 오버헤드만 만들고 있는지 판단 필요
- 8.0버전이 되면서 사실상 MyISAM, MEMORY 스토리지 엔진은 InnoDB에 비해 가지는 장점이 사라짐
- MyISAM 스토리지 엔진의 성능에 영향을 미치는 요소
- 키 캐시
- 운영체제의 캐시/버퍼
-
InnoDB의 버퍼 풀과 비슷한 역할
-
인덱스만을 대상으로 작동(버퍼 풀은 인덱스 뿐만 아니라 데이터 파일도..)
-
인덱스의 디스크 쓰기 작업에 대해서만 부분적으로 버퍼링 역할
-
키 캐시 히트율 = 100 - (Key_reads / Key_read_requests * 100)
-
Key_reads : 인덱스를 디스크에서 읽어 들인 횟수를 저장하는 상태 변수
-
Key_read_requests : 키 캐시로부터 인덱스를 읽은 횟수를 저장하는 상태 변수다.
-
설정을 통해 키 캐시 크기를 변경 가능하고 제한된 값 이상의 크기를 쓰고 싶으면 별도로 키 캐시 공간을 설정하여 부여할 수도 있다.
-
MyISAM 테이블의 데이터에 대해서는 디스크로부터의 I/O를 해결해 줄 만한 어떠한 캐시나 버퍼링 기능도 X
-
그냥 운영체제에 의존
-
운영 체제의 캐시 기능은 남는 메모리를 사용하는 것이 기본 원칙
-
MyISAM 테이블은 데이터 파일이 Heap 공간(순서X)처럼 활용
-
MyISAM 테이블에 레코드는 프라이머리 키 값과 무관하게 INSERT되는 순서대로 데이터 파일에 저장
-
MyISAM 테이블에 저장되는 레코드는 모두 ROWID라는 물리적인 주솟값을 가짐
-
PK와 세컨더리 인덱스는 모두 데이터 파일에 저장된 레코드의 ROWID값을 포인터로 가짐
-
MyISAM에서 ROWID는 가변 길이와 고정 길이 두 가지 방법으로 저장 가능
- 로그 파일을 이용하면 MySQL 서버의 깊은 내부 지식이 없어도 MySQL의 상태나 부하를 일으키는 원인을 쉽게 찾아서 해결 가능
-
MySQL이 실행되는 도중에 발생하는 에러나 경고 메시지가 출력되는 로그 파일
-
MySQL 설정 파일(my.cnf)에서 log_error라는 이름의 파라미터로 정의된 경로에 생성
-
별도 정의 X -> 데이터 디렉터리(datadir 파라미터에 설정된 디렉터리)에 .err라는 확장자가 붙은 파일로 생성
- MySQL의 설정 파일을 변경하거나 데이터베이스가 비정상적으로 종료된 이후 다시 시작하는 경우에는 반드시 MySQL 에러 로그 파일을 통해 설정된 변수의 이름이나 값이 명확하게 설정되고 의도한 대로 적용됐는지 확인 필요
-
InnoDB의 경우 MySQL 서버가 비정상적 또는 강제적으로 종료시 다시 시작되면서 완료되지 못한 트랜잭션을 정리하고 디스크에 기록되지 못한 데이터가 있다면 다시 기록하는 재처리 작업을 진행
-
이 과정에 대한 간단한 메시지가 출력되는데, 간혹 문제가 있어서 복구되지 못할 때는 해당 에러 메시지를 출력하고 MySQL 다시 종료
-
이 단계에서 발생한 문제는 상대적으로 어려운 문제인 경우가 많음
-
앞에서 살펴본
innodb_force_recovery
파라미터를 0보다 큰 값으로 설정하고 재시작해야함
- 쿼리의 실행 도중 발생한 에러나 복제에서 문제가 될 만한 쿼리에 대한 경고 메시지가 해당 에러 로그에 기록
-
클라이언트 애플리케이션에서 정상적으로 접속 종료를 하지 못하고 프로그램이 종료된 경우
-
중간에 네트워크 문제가 있어서 의도하지 않게 접속이 끊어지는 경우
-
해당 메시지가 너무 많이 쌓일 경우 애플리케이션의 종료 로직을 검토해볼 필요가 있음
-
max_connect_errors
시스템 변수 값이 너무 낮게 설정된 경우 "Host 'host_name' is blocked"라는 에러가 발생할 수도 있음 -
클라이언트 호스트에서 발생한 에러(커넥션 실패, 강제 연결 종료등)의 횟수가
max_connect_errors
변수 값을 넘게 되면 발생 -> 해당 값을 좀 증가시켜주면 됨 -> 물론 왜 이 에러가 발생했는지 원인을 알아보는게 좋음
-
InnoDB의 테이블 모니터링이나 락 모니터링, 또는 InnoDB의 엔진 상태를 조회하는 명령은 상대적으로 큰 메시지를 에러 로그 파일에 기록
-
InnoDB의 모니터링을 활성화해놓고 그대로 유지해놓으면 에러 로그 파일이 너무 커져서 파일 시스템 공간을 다 사용해 버릴지도 모르기에 모니터링 사용 후 다시 비활성화 해야함
- MySQL이 이유도 모르게 종료되거나 재시작될 경우 에러 로그 파일에서 MySQL이 마지막으로 종료되면서 출력한 메시지를 화긴하는 것이 왜 MySQL 서버가 종료됐는지 확인하는 유일한 방법
-
쿼리 로그 파일은 시간 단위로 실행됐던 쿼리의 내용이 모두 기록
-
제너럴 쿼리 로그는 실행되기 전에 MySQL이 쿼리 요청을 받으면 바로 기록하기 때문에 쿼리 실행 중에 에러가 발생해도 일단 로그 파일에 기록
-
파일로 기록할 수도, 테이블로 기록할 수도 있음
-
서버의 쿼리 튜닝 시
- 서비스 적용되기 전에 전체적으로 튜닝하는 경우
- 서비스 운영 중에 MySQL 서버의 전체적인 성능 저하를 검사하거나 정기적인 점검을 위한 튜닝
-
1번은 검토해야할 대상 쿼리가 전부라 모두 튜닝
-
2번은 어떤 쿼리가 문제 쿼리인지 알기 힘듬
-
이때 어떤 쿼리가 문제인지 판별하는데 슬로우 쿼리 로그가 큰 도움
-
정상적으로 실행이 완료됐고 실행하는데 걸린 시간이
long_query_time
시스템 변수에 정의된 시간보다 많이 걸린 쿼리가 기록됨 -
파일로 기록할 수도, 테이블로 기록할 수도 있음
-
슬로우 쿼리 내용 -> Time, Query_time 등 책 참조
-
슬로우 쿼리 또는 제너럴 로그 파일의 내용이 상당히 많을 경우 쿼리를 하나씩 검토하기 매우 빡세다 -> Percona에서 개발한 Percona Toolkit의 pt-query-digest 스크립트를 이용해서 로그 파일 분석
-
로그 파일의 분석이 완료되면 아래의 3개의 그룹으로 나뉘어 저장
-
분석 결과의 최상단에 표시
-
모든 쿼리를 대상으로 슬로우 쿼리 로그의 실행 시간(Exec time), 그리고 잠금 대기 시간(Lock time)등에 대해 평균 및 최소/최대 값을 표시
-
각 쿼리별로 응답 시간과 실행 횟수를 보여줌
-
--order-by
옵션으로 정렬 순서를 변경 가능 (기본값: 단일 Query_time 이 가장 큰 쿼리가 먼저 나열) -
QueryID는 실행된 쿼리 문장을 정규화(쿼리에 사용된 리터럴을 제거)해서 만들어진 해시 값 -> 일반적으로 같은 모양의 쿼리라면 동일한 QueryID
- QueryID 별 쿼리를 쿼리 랭킹에 표시된 순서대로 자세한 내용을 보여줌