Skip to content
isdnetworks
Go back

세지 않아도 되는 것을 세고 있었다

목록 화면이 느렸다. 쿼리를 보니 두 개가 나간다.

SELECT * FROM orders WHERE ... ORDER BY id DESC LIMIT 20;
SELECT COUNT(*) FROM orders WHERE ...;

각각의 시간을 재 보니 목록은 빠르고 카운트가 느렸다.

Table of contents

Open Table of contents

카운트가 왜 느린가

목록 쿼리는 LIMIT 20 이 있어서 조건에 맞는 행을 20개 찾으면 멈춘다. 인덱스를 타면 금방 끝난다.

COUNT(*) 는 멈출 수 없고 몇 개인지 알려면 전부 세야 하므로 20만 건이면 다 본다.

그래서 목록보다 COUNT(*) 가 훨씬 오래 걸렸고 자료가 많아질수록 차이가 벌어진다.

목록이 느려지는 원인이 목록이 아니라 옆에 붙은 숫자였다. 같은 WHERE 인데 시간이 크게 달랐다.

왜 세고 있었나

화면을 봤더니 페이지네이션에 이렇게 나오고 있었다.

1 2 3 4 5 ... 8,241   총 164,820건

마지막 페이지 번호와 총 건수를 보여 주려고 COUNT(*) 를 돌리는데 그것이 실제로 필요한지를 물었다.

뒤쪽 페이지로 가는 사람이 있는지를 access_log 로 봤더니 거의 없었다. 총 건수가 정확해야 하는지도 물었는데 대략만 알면 되는 화면이었다.

필요 없는 것을 위해 매번 20만 건을 세고 있었다. 빠르게 만들기 전에 필요한지를 물었어야 했다.

선택지 — 세 가지 방법

첫째는 다음 페이지가 있는지만 확인하는 것이다.

SELECT * FROM orders WHERE ... ORDER BY id DESC LIMIT 21;

LIMIT 21 로 한 개 더 가져와 21개가 오면 다음 페이지가 있고 20개 이하면 마지막이다. 화면은 이전다음 만 남는다.

총 건수와 마지막 페이지가 사라지는 대신 추가 쿼리가 아예 없다. 둘째는 대략적인 값을 쓰는 것이다.

SELECT table_rows FROM information_schema.tables
WHERE table_name = 'orders';

table_rows 는 빠르지만 부정확하고 WHERE 가 걸린 카운트에는 못 쓴다. 셋째는 조건별 카운트를 짧게 캐시하는 것이다.

셋째가 실제로 효과가 컸다. 사람들이 같은 WHERE 로 페이지를 넘기므로 한 번 세고 재사용한다.

셋을 섞었다

화면 성격에 따라 다르게 갔다.

관리자 목록   총 건수를 보여줘야 했다 → 조건별 카운트를 60초 캐시
사용자 목록   다음 페이지 유무만 확인하는 방식으로 변경
보고서       정확한 수가 필요하고 자주 안 본다 → 그대로 둠

사용자 목록은 총 건수를 안 보여 줘도 불만이 없었고 보고서는 하루 몇 번 안 도는 화면이라 안 건드렸다.

한 방식으로 통일하지 않은 것이 요점이다. 화면마다 그 숫자가 하는 일이 달랐다.

기술을 먼저 고르고 화면에 적용하면 이 구분이 안 나온다. 화면이 무엇을 필요로 하는지를 먼저 보면 세 방법이 각각 자기 자리를 찾는다.

정렬도 같이 봤다

카운트를 손보면서 정렬 쿼리도 봤다.

ORDER BY created_at DESC LIMIT 20

created_at 에 인덱스가 없어서 조건에 맞는 것을 다 찾아 정렬한 다음 앞의 20개를 취하고 있었다.

인덱스를 넣으니 그 순서대로 20개만 읽고 끝났고 같은 쿼리인데 비용 차이가 컸다.

다만 조건에 맞는 행이 20보다 적으면 어차피 전부 읽는다. 그때는 인덱스가 있어도 스캔과 비슷하므로 LIMIT 이 있다고 늘 싸지는 않다.

전체 흐름 — 확인 순서

목록이 느리면 이 순서로 본다.

1. 쿼리를 나눠서 각각 잰다 — 목록과 카운트 중 어느 쪽이 느린지
2. 카운트가 느리면 그게 정말 필요한지 묻는다 — 화면과 사용 로그를 본다
3. 필요하면 캐시하고 아니면 없앤다
4. 정렬 컬럼에 인덱스가 있는지 본다

둘째를 빼먹기 쉬운데 빠르게 만들 방법부터 찾게 되지만 안 하는 것이 가장 빠르다.

이번에 가장 크게 줄인 것도 COUNT(*) 를 안 나가게 만든 부분이다. 캐시나 인덱스는 그다음이었다.

정리


Share this post on:

Previous Post
들어오는 자리와 실제로 일하는 자리가 달랐다
Next Post
지운 캐시가 그 캐시가 아니었다