Study/MySQL을 더 빠르게, 성능최적화 선택과 집중
1장. 쿼리 응답 시간
주지민
2024. 8. 22. 01:47
반응형
본 포스트는 사내 스터디를 진행하며 정리한 포스트입니다
성능은 곧 쿼리 응답 시간입니다.
책의 목적
- 본 책의 목적은 MySQL 성능을 현저하게 개선하는 것이며, 다양한 측면에서 방안을 탐구
- MySQL을 관리하는 입장이 아니라 사용하는 입장에서 기술
쿼리 응답 시간이란?
- MySQL이 쿼리를 실행하는 데 소요되는 시간
- 쿼리를 받고나서 결과 세트를 클라이언트에 전송한 시점까지의 경과 시간을 의미
쿼리 응답 시간 분석을 위한 지표
- 핵심지표로써 쿼리 응답 시간을 집중해야한다
- 이를 분석하기 위해 쿼리 보고를 이해하는 것이 중요하다
- 소스
- 쿼리 메트릭은 2개의 소스에서 비롯되며 MySQL 배포 및 버전에 따라 다릅니다
- 쿼리 메트릭을 제공하는 것으로 슬로 쿼리 로그, 성능 스키마가 존재
- 슬로 쿼리 로그: 디스크에 있는 로그 파일
- 성능 스키마: performance_schema와 같은 이름의 데이터베이스
- 두개의 차이점은 얼마나 많은 메트릭을 제공하는지이다
- 특히나 성능 스키마는 MySQL을 심층 분석할 수 있는 풍부한 여타 데이터를 포함하여 유용성이 높다
- 슬로 쿼리 로그도 다양한 메트릭을 지원하지만, MySQL 80.14 이전 버전의 슬로 쿼리 로그는 분석의 질이 낮다
- 슬로 쿼리 로그는 기본으로 비활성화되어 있지만 MySQL을 다시 시작할 필요 없이 바로 활성화 가능하다
- 슬로 쿼리 로그 long_query_time 설정을 0이나 너무 낮게설정하여 많은 쿼리가 기록되게 한다면 디스크 입출력시간이 증가하므로 유의해야한다
- 성능 스키마는 활성화하려면 MySQL을 다시 시작해야한다
- 집계
- 쿼리 메트릭값은 정규화된 SQL 문법을 기준으로 그룹화되고 집계됩니다
- 정규화된 SQL문을 SHA-256 hash로 변환하여 그룹화하는 방식을 사용
- SHA-256 hash가 생성되는 과정
- SQL문을 정규화하여 다이제스트 텍스트를 생성
- 이를 SHA-256 hash 계산을 통해 다이제스트 해시를 생성
- 쿼리 정규화시에는, 즉 다이제스트 텍스트를 생성할 시에는 값이 제거된다 ( where절로 들어가는 값이 달라도 같은 다이제스트 텍스트로 생성된다 )
- 보고
- 쿼리 보고서는 고급 프로파일(쿼리 프로파일)과 쿼리에 특화된 보고서로 구성됩니다
- 쿼리 프로파일
- 느린 쿼리가 표시
- 느리다는 정의는 쿼리가 정렬되는 쿼리 메트릭의 집계치인 정렬 메트릭과 관련있다
- 일반적으로 쿼리 시간이 기본 정렬 메트릭
- 쿼리 시간 집계치 종류
- 쿼리 총시간
- 실행 시간의 총합
- 쿼리 A가 응답시간 1초, 10번 실행 = 총시간 10초
- 쿼리 B가 응답시간 0.1초, 1000번 실행 = 총시간 100초
- 이 경우 쿼리 B가 가장 느린 쿼리로 집계
- 쿼리 B를 개선해야 더 많은 효과를 볼 수 있다
- 실행 시간 비율
- 쿼리 총 시간(각 쿼리)을 실행 총시간(모든 쿼리)으로 나눈 값
- 얼마나 많은 비율로 MySQL을 실행시켯는지
- 실행 시간 비율이 높은것이 가장 느린 쿼리
- 쿼리 부하
- 쿼리 총시간을 클럭 타임으로 나눈 것
- 클럭 시간이 300초, 쿼리 총 시간 250초라면 250/300 = 0.83
- 해당 지표는 쿼리 동시성을 나타낸다
- 쿼리 부하가 3.5라는 것은 언제든지 조회하면 실행 중인 쿼리의 인스턴스가 3.5개정도 있다라는 뜻
- 쿼리 부하가 높을수록 쿼리가 동일하거나 가까운 행에 접근할 때 경합할 가능성이 커집니다
- 쿼리 총시간
- 쿼리 보고서
- 하나의 쿼리에 대해 알아야 할 모든 것을 보여준다
- 일반적으로 쿼리 프로파일에서 느린 쿼리를 선택하는 것으로 시작된다
- 쿼리 보고서는 쿼리 메트릭 도구에 따라 크게 달라진다
- 쿼리 분석에는 보고서의 쿼리 메트릭만 있으면 된다. 메타데이터는 수동으로 수집할 수 있다
쿼리 분석
- 쿼리 실행을 이해해보자
- 쿼리 메트릭은 소스와 MySQL 배포 및 버전에 따라 다르다
- 쿼리 시간
- 가장 중요한 메트릭이다
- 쿼리 시간에는 또 다른 메트릭인 잠금 시간이 포함된다
- 성능 스크마로 수집되는 이벤트는 다음과 같은 계층 구조로 구성된다
- 트랜잭션: 모든 쿼리가 트랜잭션 안에서 수행되므로 최상위 이벤트
- 명령문: 쿼리 메트릭이 적용되는 쿼리
- 단계: 명령문 실행과정 내의 단계로, 명령문 구문 분석, 테이블 열기, 파일 정렬 수행과 같은 과정이 포함
- 대기: 시간이 걸리는 이벤트
- 잠금 시간
- 잠금 시간이 쿼리 시간의 50% 이상이면 문제로 볼 수 있다
- 배경 지식 타임
- InnoDB 스토리지 엔진에서 잠금에는 테이블 락과 로우 락이 존재
- 서버(MySQL)은 테이블과 테이블 락을 관리, 스토리지 엔진에 구애받지 않는다
- 로우 레벨 락은 잠금이 지원되는 스토리지 엔진만 가능
- InnoDB는 로우 레벨 락을 지원, 즉 기본 스토리지 엔진인 InnoDB 락은 기본적으로 로우 레벨 락을 의미
- 서버에서 관리하는 메타데이터 락도 존재, 스키마, 테이블, 저장 프로그램등의 접근을 제어
- 테이블 락과 로우 락은 테이블 데이터에 대한 접근을 제어, 메타데이터 락은 테이블 구조(열, 인덱스 등)에 대한 접근을 제어하여 쿼리가 테이블에 접근하는 동안 변경되는 것을 방지
- 모든 쿼리는 접근하는 모든 테이블을 대상으로 메타데이터 락을 획득, 트랜잭션이 끝날 때 해제
- 성능 스키마의 잠금 시간에는 로우 락 대기가 포함되지 않고 테이블과 메타데이터 락 대기만 포함합니다
- 잠금은 데이터를 변경하거나 작성하기 전에 행을 잠가야하므로 주로 쓰기(INSERT, UPDATE, DELETE, REPLACE)에 사용
- 잠금 시간은 행에 대한 동시성에 따라 달라진다
- 읽기(SELECT)에는 비잠금 읽기와 잠금 읽기가 존재
- 잠금 읽기: SELECT FOR UPDATE, SELECT FOR SHARE
- 비잠금 읽기: 일반 SELECT
- 추가로 쓰기 쿼리에 서브쿼리로 들어가는 SELECT(임시 테이블)는 공유 로우 락을 획득한다
- 비잠금 읽기는 로우 락을 획득하지 못하더라도 메타데이터 락과 테이블 락을 획득하므로 잠금시간이 0은 아니다
- SELECT 쿼리는 접근하는 모든 테이블에서 공유 메타데이터 락을 획득하고, 이는 다른 공유 메타데이터 락과 호환된다
- 단 ALTER TABLE같은 배타 메타데이터 락을 획득하는 쿼리는 다른 모든 배타 메타데이터 락을 차단합니다
- 숙지 해야하는 내용 5가지
- innodb_lock_wait_timeout 시스템 변수가 각각의 로우 락에 적용되므로 잠금 시간은 이보다 상당히 클 수 있습니다.
- 잠금과 트랜잭션 격리 수준은 서로 관련되어 있습니다.
- innoDB는 쓰지 않는 행을 포함하여 접근하는 모든 행을 잠급니다
- 잠금은 트랜잭션 커밋이나 롤백할 때 해제되며 때로는 쿼리 실행 중에도 해제됩니다
- innoDB에는 record, gap, next-key 등 다양한 유형의 잠금이 있습니다
- 스터디중 나온내용
- X락은 다른 X락만 차단하고, 단순 SELECT는 허용한다
- SELECT ... FOR UPDATE시 UPDATE되기전까지 SELECT는 해당 테이블에 접근 가능, UPDATE후 UNDO 로그가 하나 쌓이고 기존에 SELECT하던(같은 트랜잭션을 열었던) 쿼리는 해당 UNDO 로그를 보게됨
- 조회된 행
- 조회된 행은 MySQL이 쿼리 조건 절에 일치하는 행을 찾으려고 접근한 행의 수를 나타냅니다
- 쿼리와 인덱스의 선택도(데이터 세트에서 특정 값을 얼마나 잘 골라낼 수 있는지에 대한 지표)를 나타낸다
- 쿼리와 인덱스의 선택도가 높을수록 MySQL이 일치하지 않는 행을 조회하는데 낭비하는 시간이 줄어든다
- 보낸 행
- 클라이언트에 반환된 행의 수(결과 세트 크기)를 나타낸다
- 이상적인 경우 보낸 행 = 조회된 행
- 10,000개 행만 있는 테이블의 1,000개 행은 응답 시간이 허용되더라도 비율 10%의 문제 소지가 될 수 있다
- 비율과 관계없이 보낸 행과 조회된 행이 같고 값이 의심스러울 정도로 높으면 쿼리가 테이블 스캔을 유발한다는 소리, 이는 성능면에서 매우 안좋다
- 보낸 행 < 조회된 행
- 쿼리나 인덱스의 선택도가 좋지 않다는 신호
- 조회된 행중 보낼려고 의도한 값의 비율이 적다는 뜻(조회된 데이터중 일치하지 않는 데이터가 많다)
- 많은 시간을 낭비하고 있다는 뜻(개선해야한다)
- 보낸 행 > 조회된 행
- 드문 케이스...
- 보낸 행 자체가 문제가 되는 케이스는 적다
- 영향받는 행
- 삽입, 갱신, 삭제된 행의 수를 나타낸다
- 엔지니어는 해당하는 행에만 영향을 미치도록 주의해야한다
- 대량 INSERT, UPDATE, DELETE는 복제 지연, 변경 목록 길이, 잠금 시간, 전반적인 성능 저하와 같이 여러 문제를 야기
- 적당한 배치 크기의 정답은 없다, MySQL과 애플리케이션이 쿼리 응답 시간에 영향을 미치지 않고 유지할 수 있는 배치 크기와 비율로 결정해야한다
- 셀렉트 스캔
- 첫 번쨰로 접근한 테이블에서 수행한 전체 테이블 스캔 횟수를 나타낸다
- 쿼리가 2개 이상의 테이블에 접근할 때는 셀렉트 풀 조인 메트릭이 적용된다
- 이는 쿼리가 인덱스를 사용하지 않는다는 것을 의미하므로 일반적으로 성능에 좋지 않다
- 셀렉트 스캔이 0이 아니면 쿼리 최적화를 강력하게 권장
- 테이블 스캔은 MySQL이 결정하게 되는데, 인덱스의 고윳값 수인 카디널리티와 테이블의 전체 행 수, 기타 비용을 고려해 쿼리가 조회할 행 수를 추정한다
- 셀렉트 풀 조인
- 조인된 테이블을 대상으로 전체 테이블을 스캔한 수를 나타낸다
- 셀렉트 스캔과 유사하지만 더 나쁘다
- 항상 0을 유지해야한다
- EXPLAIN으로 쿼리 분석을 하면, MySQL은 테이블 조인 순서를 위쪽(첫 번째 테이블)에서 아래쪽(마지막 테이블)으로 출력
- 이때 셀렉트 스캔은 첫 번째 테이블에만 적용, 나머지는 셀렉트 풀 조인 테이블에만 적용된다
- EXPLAIN 했을때 type: ALL이 된다면 셀렉트 스캔 혹은 셀렉트 풀 조인이다
- 테이블 조인 순서는 MySQL이 결정
- 디스크에 생성된 임시 테이블
- 디스크에 생성된 임시 테이블의 수를 나타낸다
- 쿼리가 메모리에 임시 테이블을 만드는 것은 정상이나, 메모리에 임시 테이블이 너무 커지면 MySQL은 임시 테이블을 디스크에 쓴다, 이 경우 메모리보다 디스크 I/O가 훨씬 느리므로 응답 시간에 영향을 미친다
- 과도하게 사용되는 디스크의 임시 테이블은 쿼리 최적화가 필요함을 나타내거나, 시스템 변수 tmp_table_size가 너무 작음을 나타낸다(항상 쿼리 최적화를 시도하고, 시스템 변수는 그 후에 생각할 것)
- 쿼리 카운트
- 쿼리 실행 횟수
- 해당 값은 매우 낮고 쿼리가 느리지 않는 한 기준이 없고 임의적이지만, 낮고 느림인 경우 최적화 대상으로 조사해야한다
메타데이터와 애플리케이션
- 쿼리 메트릭보다 쿼리 분석에 더 많이 사용하는 것이 바로 메타데이터다
- EXPLAIN 계획, 각 테이블의 구조(SHOW CREATE TABLE)등이 주요 메타데이터
- 어플리케이션에서 쿼리가 어떤 용도로 사용되고 있는지도 주요한 지표이다
상대값
- 각 쿼리 메트릭에서 객관적으로 긍정적으로 보는 유일한 값은 0이다
- 예시) 수천 개의 행에 불필요하게 접근해 반환하고, 그것이 DB 크기가 커질수록 행 수도 늘어 시간이 지남에 따라 속도가 느려졌다. 원인은 SELECT COUNT (*) 였다
평균, 백분위수, 최대
- 평균은 지나치게 낙관적
- 평균에 속으면 안된다, 매우 크거나, 매우 작은 몇개의 값이 평균 응답시간을 왜곡할 수 있다
- 백분위수는 어디까지나 추정
- 평균이 갖는 문제를 보완(P95, P99)
- 무시되는 값의 작은 비율을 특잇값으로 간주
- 높은 백분위수가 원하는 값이 되도록 하는게 바람직하다(이상적이나, 현실은 힘들 수도 있다)
- 최대는 최상의 표현
- 최소, 평균, P99 쿼리 시간은 모두 밀리초지만, 최대 쿼리 시간은 초 단위일 수도 있다
- 최대 쿼리 시간은 백분위수가 갖는 문제를 보완하므로 어떤 값도 버리지 말라
쿼리 응답 시간 개선 맛보기
- 쿼리 최적화라고도 한다
- 직접 쿼리 최적화
- 쿼리와 인덱스를 변경하는 일
- 종류
- 인덱스 머지 최적화
- 해쉬 조인 최적화
- 인덱스 컨디션 푸시다운 최적화
- 다중 범위 읽기 최적화
- Constant-Folding 최적화
- IS NULL 최적화
- ORDER BY 최적화
- GROUPT BY 최적화
- DISTINCT 최적화
- LIMIT 쿼리 최적화
- 기타 등
- 직접 쿼리 최적화는 필요조건이지만 충분 조건은 아니다
- 선행 수행작업이지만, 이를 통해 최적화가 실패한다면 간접 쿼리 최적화로 이어져야한다
- 간접 쿼리 최적화
- 데이터와 접근 패턴을 변경하는 일
- 쿼리 변경 대신 쿼리가 접근하는 대상과 방법(각각의 데이터와 접근 패턴)을 변경한다
- 결국 쿼리, 데이터, 접근 패턴은 성능과 관련하여 뗄 수 없는 관계이므로 이러한 변경은 쿼리를 간접적으로 최적화해준다
- TRUNCATE TABLE등이 해당한다 ( 데이터를 줄여, 응답시간을 확보하기 )
언제 쿼리를 최적화해야 할까?
- 성능이 고객에게 영향을 미칠 때
- 사후 대응이라 좋다고 말할 순 없다
- 적정 대기 시간을 초과했을때, 최적화를 고려해야한다
- 코드 변경 전후
- 한달에 한번
- 쿼리 응답 시간은 데이터와 접근 패턴이 변경됨에 따라 달라지기에 주기적으로 관리해야한다
- 처음 설계때 데이터가 인입되고, 어떻게 사용될건지를 고려해야한다
요점 정리
- 성능은 쿼리 응답 시간, 즉 MySQL이 쿼리를 실행하는 데 걸리는 시간입니다
- 쿼리 응답 시간은 의미 있고 실행 가능하므로 MySQL 성능에서 핵시 지표입니다
- 쿼리 메트릭은 슬로 쿼리 로그나 성능 스키마에서 비롯
- 성능 스키마는 쿼리 메트릭 중 최상의 소스입니다
- 쿼리 메트릭은 다이제스트(SQL문 정규화)에 의해 그룹화되고 집계됩니다
- 쿼리 프로파일은 느린 쿼리를 보여 주며 느리다는 것은 정렬 메트릭과 관련
- 쿼리 보고서는 하나의 쿼리를 대상으로 사용할 수 있는 모든 정보를 보여 주며 쿼리 분석에 사용
- 쿼리 분석의 목표는 느린 응답 시간을 해결하는 것이 아니라 쿼리 실행을 이해하려는 것
- 쿼리 분석에는 쿼리 메트릭, 메타데이터(EXPLAIN 계획, 테이블 구조 등), 애플리케이션 지식을 사용
- 쿼리 응답시간을 향상시키려면 직접 및 간접 쿼리 최적화를 알아야한다
728x90
반응형