Technote

성능 최적화 입문

연재 워드프레스 성능 프로파일링 8부 중 2부

쿼리 프로파일 읽는 법 — 느린 쿼리와 중복 쿼리

같은 화면에 나란히 나오지만 완전히 다른 문제입니다. 한쪽은 쿼리를 고쳐야 하고, 다른 한쪽은 캐시로 사라집니다. 구분이 먼저입니다.

서버 처리 시간이 범인으로 좁혀졌다면 다음은 쿼리입니다. Query Monitor 같은 프로파일러를 켜면 한 요청이 만든 쿼리가 전부 나열되는데, 그 목록을 총 개수만 보고 판단하면 대개 틀립니다. 200개짜리 목록이 300개짜리 목록보다 느릴 수 있습니다.

봐야 할 것은 두 종류이고, 둘은 원인도 처방도 다릅니다.

같은 패널에 나란히 나오지만 고치는 곳이 다르다

패널을 읽는 순서

프로파일러 화면은 위에서 아래로 읽습니다. 개요에서 총 쿼리 수 · 총 쿼리 시간 · 최대 메모리 세 값을 먼저 봅니다. 여기서 중요한 것은 총 쿼리 시간이 응답 시간의 일부일 뿐이라는 사실입니다. 총 쿼리 시간이 작은데 응답이 느리다면 병목은 DB 가 아니라 PHP 실행 — 외부 API 호출 · 이미지 처리 · 과한 훅 — 쪽입니다.

그다음 쿼리 목록을 소요 시간으로 정렬해 상위 몇 건을 봅니다. 여기에 잡히는 것이 느린 쿼리입니다. 이어서 중복 패널로 옮겨 같은 SQL 이 몇 번 실행됐는지를 봅니다. 이 둘을 각각 보지 않고 목록을 통째로 훑으면, 시간의 대부분을 차지하는 한 건과 개수의 대부분을 차지하는 반복이 서로를 가립니다.

모든 프로파일러에서 가장 값진 열은 호출자(caller) 입니다. 어떤 SQL 인지보다 누가 그것을 불렀는지가 고칠 위치를 알려 줍니다. 같은 SQL 이 30번 나오는데 호출자가 전부 같은 함수라면 그 함수 안에 루프가 있다는 뜻이고, 호출자가 제각각이라면 캐시가 아예 비어 있다는 뜻입니다.

프로파일러를 설치할 수 없을 때

Query Monitor 는 개발 · 스테이징 전용입니다. 쿼리와 훅 정보를 화면에 노출하는 데다 db.php 드롭인을 설치해 모든 쿼리에 오버헤드를 얹으므로, 프로덕션에 켜 두면 진단하려던 대상을 진단 도구가 느리게 만듭니다.

플러그인을 넣기 어려운 환경이라면 코어 상수 하나로 같은 데이터를 얻을 수 있습니다.

// wp-config.php — 스테이징에서만
define( 'SAVEQUERIES', true );

이 상수가 켜지면 $wpdb->queries 에 [SQL, 소요시간, 호출 스택] 이 쌓입니다. 요청 끝에서 정렬 한 번, 집계 한 번이면 위의 두 목록이 그대로 나옵니다.

add_action( 'shutdown', function () {
    global $wpdb;

    if ( ! defined( 'SAVEQUERIES' ) || ! SAVEQUERIES ) {
        return;
    }

    $rows = $wpdb->queries;

    // ① 느린 쿼리 — 소요시간 내림차순
    usort( $rows, fn( $a, $b ) => $b[1] <=> $a[1] );
    error_log( 'slowest: ' . round( $rows[0][1] * 1000 ) . 'ms ' . $rows[0][0] );

    // ② 중복 쿼리 — 같은 SQL 이 몇 번인가
    $dupes = array_count_values( array_column( $rows, 0 ) );
    arsort( $dupes );
    error_log( 'top repeat: ' . reset( $dupes ) . 'x ' . key( $dupes ) );
} );

스무 줄이 안 되는 코드지만, 이 두 줄의 로그가 “무엇을 고칠 것인가” 를 정합니다. 느린 쿼리가 하나 크게 튄다면 그 쿼리를 고쳐야 하고 — 캐시를 얹어도 첫 요청은 여전히 그 시간을 냅니다 — 같은 SQL 이 수십 번이라면 캐시 프라이밍으로 대부분 사라집니다.

숫자를 해석할 때의 함정

쿼리 수가 줄었다고 반드시 빨라지지는 않습니다. 반대로 쿼리 수가 그대로여도 느린 한 건을 고치면 크게 빨라집니다. 개수는 목표가 아니라 단서입니다. 개선을 보고할 때도 응답 시간과 쿼리 수를 함께 적고, 둘 중 하나만 좋아졌다면 그 사실을 그대로 적습니다.

또 하나, 관리자 화면의 프로파일과 방문자 화면의 프로파일을 섞지 않습니다. 관리 화면은 목록 테이블 · 알림 · 업데이트 확인 때문에 원래 쿼리가 많고, 그 숫자를 방문자 페이지의 기준으로 삼으면 엉뚱한 곳을 파게 됩니다.

쿼리 구조와 인덱스를 다루는 글은 성능 최적화 아카이브에 있고, 진단부터 개선까지 한 번에 맡기는 경로는 최적화 지원 사업에 정리돼 있습니다.

다음 회차

중복 쿼리 쪽이 압도적이라면 원인은 대개 하나입니다. 다음 회차는 N+1 — 루프 안의 get_post_meta() 가 만드는 비용과, 코어가 이미 갖고 있는 프라이밍 함수들입니다.

이 주제의 다른 글

노하우 목록으로

성능 최적화 실무

태그 하나가 더하는 밀리초는 누가 냅니까

추적 스크립트는 내려받고 끝나지 않습니다. 파싱과 실행이 메인 스레드를 점유하는 동안 페이지는 손가락에 반응하지 않습니다. 그 비용은 직접 잴 수 있습니다.

마케터 3분 읽기

₩270,000 · 신청하기