테이블 사이즈와 부하 대책
- 시간이 지나면서 데이터 양이 증가하면 적절한 인덱스가 사용되지 않아 테이블 스캔이 다발하고 있는 경우 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 되므로 이것 감산