테이블 사이즈와 부하 대책
- 시간이 지나면서 데이터 양이 증가하면 적절한 인덱스가 사용되지 않아 테이블 스캔이 다발하고 있는 경우 CPU 사용량이 급증하는 형태로 문제가 발생함
- 이는 데이터베이스 성능 문제 중에서도 기본 중의 기본
- 전체 테이블 스캔과 같은 나쁜 쿼리를 고친다 해도 데이터 양의 증가에 따라서 성능은 떨어짐
- 이는 데이터 양이 적은 상황에서는 메모리 안에서의 액세스로 완결하고 있던 것이 메모리 크기를 크게 초과하면서 디스크 액세스가 다발하게 되기 때문
- 데이터 증가에 따라 성능이 악화되는 것은 참조 처리뿐 아니라 갱신 처리의 성능도 마찬가지임
UPDATE
및DELETE
는 갱신 대상의 레코드를 특정하기 위해읽기 작업이 필요
INSERT
는 추가하려는 레코드가 속한 인덱스 블록을 읽어 오기 위해서로드 처리가 필요
- 참조 트래픽의 부하 대책은 그다지 어렵지 않음(슬레이브 추가 or 캐시 서버 추가)
- 한편, 갱신 처리는 데이터를 가지고 있는 모든 서버에 갱신할 수 밖에 없기 때문에 부하 대책이 어려움
INSERT 튜닝
INSERT 주체의 테이블
- 액세스 로그, 일기, 대전 이력 등의 이력계 테이블에서는 INSERT가 자주 발생함
- SELECT(및 UPDATE)가 필요가 전혀 없는 경우는 인덱스 만들 필요가 없고, 이런 상황에서는 데이터 양이 늘어도 쓰기 성능 저하가 발생하지 않음
- 그러나 SELECT 빈도가 나름 높다면 인덱스가 필요.
- 레코드 수가 늘어나면 인덱스 크기도 커짐 → 거대한 인덱스를 메모리 안에서 다 처리할 수 없음 → 갱신 대상의 인덱스를 로드하기 위해 디스크 로부터의 로드가 발생 (대폭적 성능 다운) → 서비스를 운용하는 데 커다란 리스크
INSERT의 성능 지표
- 메모리 내에서 INSERT가 완결되고 있던 것이 디스크 I/O 가 발생하게 됨으로써 어느 정도의 성능 저하가 발생하는지를 지표로서 파악해 두는 것이 중요함
- 메모리 내에서의 INSERT라면 초간 10,000건 ~ 15,000건 정도는 충분히 도달 가능함
- 인덱스 사이즈가 메모리 사이즈(버퍼 풀 사이즈)를 초과하여 디스크 읽기가 다발하게 되면 초당 INSERT 성능은 크게 떨어지기 시작함
- InnoDB는
Insert Buffering
이라는 자체 최적화 메커니즘이 있어서 랜덤 읽기의 발생을 최소화 해 주기 때문에 더 빨라진다. 하지만 그렇다고 해도 초당 2,000 ~ 4,000 INSERT 정도까지 떨어짐
- INSERT를 고속화하기 위해서는 뭐니뭐니 해도
INSERT를 메모리 안에서 완결하는 것
이 중요. 이를 실현하기 위해서는인덱스 크기를 작게 하는 것
이 필요함
시계열 처리의 고속화 접근
- 날짜로 레인지 파티션을 구성하기 (테이블을 물리적으로 분할하면서도 애플리케이션에서는 논리적으로 하나의 테이블로서 보여주는 것임)
- INT 형이나 TIMESTAMP 형이라면 초 단위까지 설정이 가능하고 과거 데이터를
ALTER TABLE… DROP PARTITION
으로 빠르게 제거가 가능함
- 다만 MySQL에서는 TIMESTAMP 형을 파티션의 키로 하면 파티션 풀링(특정 파티션에만 액세스하는 것)을 할 수 없기 때문에 이 경우는 INT 형이 좋음
레인지 파티션 사용할 수 없는 경우
- 테이블을 날짜와 시간별로 분할
- 과거 데이터를 삭제하거나 다른 서버로 옮기기
- 인덱스를 만들지 않기
- 불필요한 열을 만들지 않기
- 공간 효율적인 데이터형 사용하기
- 대용량 메모리 탑재하기
DELETE의 튜닝
데이터의 보존기간 확인하기
- 테이블 크기를 작게 유지하기 위한 손쉽고 빠른 방법은 테이블에서 데이터를 지우는 것
- 로그계의 데이터라면 1~3개월 정도로 유지해 두면 그 후에는 제거해도 괜찮은 상황이 많음
- 레인지 파티션을 구성해 두면 특정 파티션의 삭제는 고속으로 할 수 있음
- 삭제할 수 없는 데이터는? 필요할 때 다소 시간이 걸려도 좋으니 잃어버리지 않도록 해두면 되는 것도 많아서 아카이브용의 데이터베이스 또는 파일로 저장해 두어도 좋음
데이터 제거 방식에 따라 성과는 변화함
- 특정 기간보다 오래된 데이터를 일률적으로 제거할 수 없어도
특정 사용자를 제외한 삭제 가능
과 같이 조건부로 삭제할 수도 있음
- 이것은 얼핏 보기에 효과적인 것 처럼 보일 수 있지만 용량 면이나 성능 면에서의 관점에서는 즉시 큰 효과를 얻을 수 없는 경우가 많음
- 데이터베이스 I/O 단위는 16KB 등 결정된 단위의 블록으로 되어 있는데, 한 개의 레코드 크기는 16KB보다 훨씬 작기에 블록에는 많은 레코드가 들어있음
- 조건부로 레코드를 삭제하는 경우는 블록 내의 모든 레코드를 삭제하게 하는 일은 적고, 그 중 70%가 제거되는 정도의 어중간한 상황이 되기 쉬움 → 레코드 수가 줄어도 블록 개수가 줄어들지 않아 총 데이터 크기가 줄어들지 않을 것임
- 이 상태에서 데이터 크기 줄이기 위해서는 MySQL OPTIMIZE TABLE 과 같이 테이블 재구성 작업이 필요(복제 구성에서는 중단 없이 수행 가능)
- 실제 운영상에서는 OPTIMIZE TABLE을 사용할 수 없어도, 전체 블록을 지울 수 없어도 이러한 조건부 제거 프로세스 적극 추천함 (← 동일 테이블에 데이터 INSERT시 기존의 삭제된 레코드를 위한 공간 활용이 가능하기에)
슬레이브 지연을 방지하려면
- DELETE는 상상 이상으로 무거운 처리( 실제 레코드를 삭제할 뿐 아니라 그 테이블의 모든 인덱스에서 해당 레코드 항목을 삭제할 필요가 있으므로)
- 또한 DELETE 대상의 레코드는 대부분 몇 년 동안 액세스하지 않은 비활성의 데이터이므로 거의 확실하게 디스크 로부터의 로드가 발생
- 복제 지연을 일으키지 않게 DELETE를 하는 유효한 수단으로서 해당 슬레이브에서 DELETE 대상 레코드를 미리 SELECT 하기를 예로 들 수 있음
- 사전에 SELECT를 해두면 그 결과는 메모리에 있으므로 다음에 DELETE 할 때 디스크 읽기 발생 x
- 실제 레코드 뿐 아니라 인덱스도 읽어두면 디스크 읽기 빈도를 더 많이 낮출 수 있음(
FORCE INDEX
사용하여 특정 인덱스에 대한 액세스 강제)
- MySQL 5.5이상부터는
Change Buffering
이라는 메커니즘이 있어, DELETE/UPDATE 시에 인덱스 갱신을 비동기적으로 수행해줌 ( 그 이전에는 인덱스로부터 논리 삭제할 때까지 해당 쿼리가 끝나지 않던 것을 더 빨리 끝내줌 )
- 1회의 DELETE 문에서 삭제할 레코드 수를 수백 개 정도로 작게 억제하고, 단일 SQL 문이 고속으로 끝나게 하는(복제 지연이 발생하기 어려워지게 하는) 일도 중요함
UPDATE 튜닝
- UPDATE 문을 실행하기 위해서는 갱신 대상의 레코드(블록)을 메모리에 읽어 올 필요가 있음
- 인덱스 뿐 아니라 테이블이 거대하면 할수록 메모리 안에서 전부 처리하는 것이 어려워져 디스크 I/O가 발생하기 쉬움
- INSERT에 비교하면 UPDATE 만으로 데이터의 양이 증가하는 것은 아니므로 갑자기 성능 문제가 발생하지는 않음. 그러나 소지 아이템 데이터 등은 시간이 지남에 따라 서서히 INSERT 되어 증가하기 때문에 단계적으로 디스크 I/O가 보다 많이 발생. 따라서 데이터 양의 증가 상태나 부하의 증가 상태를 관찰 하면서 너무 늦지 않게 대책을 강구해야 함
- INSERT 문이 적절한 튜닝에 의해 메모리 안에서의 처리를 완결시키기 쉽고 DELETE 문이 사용자 측면에서 비동기적으로 실행시키기 쉬운 반면(심야 등 트래픽이 적은 시간에 시행하는 것도 가능), UPDATE 문은 사용자 측면에서 동기적으로 실행해야 함. 또한 필요한 레코드가 메모리에 있어야 디스크에서의 읽기를 동기적으로 할 수 있다는 점에서 결국 튜닝이 가장 어려운 유형
- UPDATE 문의 성능은 하드웨어의 스펙에 크게 의존. 메모리 내에서는 초당 12,000회 정도 일반적으로 실행 가능. 한편 HDD에 액세스가 집중하기 시작하면 초당 300회 정도밖에 실행할 수 없음
- 디스크 I/O가 빈발하고 있는 경우의 해결 방법으로 가장 효과적이라고 생각되는것이 SSD로의 교체. 혹은 메모리의 증설
부하 경향 모니터링하기
- MySQL의 효과적인 지표로서
갱신 빈도
와병렬도
를 들 수 있음
갱신 빈도
- 갱신 빈도는 복제 지연을 막을 수 있는지의 여부를 파악하는 데 유용한 지표
- 슬레이브 서버에 있어 처리 능력 이상의 갱신이 마스터 서버에 오는 경우, 슬레이브 서버는 점점 지연되어 감 → 현 시점에서 슬레이브 서버가 어느 정도의 갱신을 처리할 수 있는지 파악하는 것이 중요
- 현재 시점에서 슬레이브가 어느 정도 갱신을 처리할 수 있는지를 추정하려면 슬레이브에서의 참조를 중지하고 SQL 스레드를 일정 시간 멈춰 두어 다음 SQL 스레드를 다시 시작할 때 어느 정도의 갱신이 초당 이루어지고 있는지를 보는 방법이 유효함. 슬레이브가 적용해야 할 릴레이 로그가 많은 경우는 그것을 최대한 실행하려 하기 때문에 이것이 사실상의 한계 성능이 됨
- 또는 실제 운용 서비스보다 낮은 사양의 서버(적은 메모리, SSD 대신 HDD)를 비참조 슬레이브로 준비해, 여기가 지연되면 위험 신호로서 액션을 개시하는 등의 수단도 유효하다고 말할 수 있음)
병렬도
- 현재 MySQL 내부에서 몇 개의 스레드가 클라이언트로부터의 작업을 처리하고 있는지를 나타내는 것 (Thread_running 이라는 상태 변수에서 도출 가능)
- 병렬도와 처리량에는 강한 상관 관계가 있음 (병렬도의 상승에 의해 처리량은 상승하지만 병렬도가 너무 높으면 반대로 처리량이 내려가는)
- 실제로 병렬도는 CPU 코어 8~12 정도에 도달하면 충분한 정도이므로 지금 1~2 미만인지(여유가 너무 있는 정도), 5~8 정도인지(매우 부하가 높은) 지표를 보고 병렬도가 높은 서버부터 튜닝해 나가는 것이 효과적임
Threads_running
은 실제로 그대로 지표로 사용할 수 없고 약간의 가공이 필요함- SHOW GLOBAL STATUS로 취할 수 있지만, 이 쿼리를 실행하고 있는 스레드도 계산되기 때문에 스레드 한개 감산
- 마스터의 경우 슬레이브에 바이너리 로그를 송출하기 위한 BINLOG DUMP 스레드도 카운트되므로 이 스레드 몇 개를 감산
- Event Scheduler를 사용하는 경우 내부적으로는 쿼리 실행하지 않아도 +1 되므로 이것 감산