본문 바로가기
프로그래밍/SQL

SQL COUNT(*) 때문에 느려진 로그 목록 — 이전·다음만 남긴 이유

by iwbap 2026. 10. 17.
728x90
반응형

로그 목록에 전체 페이지 수를 보여주려고 COUNT(*)를 함께 실행했다. 그런데 로그가 쌓이자 전체 건수를 세는 작업 때문에 목록까지 느려졌다. 결국 페이지 번호를 없애고 이전·다음 버튼만 남기면서 목록 조회의 체감 속도를 개선했다.

COUNT(*) 대신 이전·다음 버튼으로 바꾼 로그 목록을 상징하는 대표 이미지

처음에는 COUNT(*)를 함께 실행했다

2026년 9월 6일, 로그 목록의 페이지네이션을 손보고 있었다. 처음에는 목록 쿼리 옆에 COUNT(*)를 같이 실행하면 전체 페이지 수를 쉽게 표시할 수 있다고 생각했다.

데이터가 적을 때는 체감상 괜찮았다. 목록도 나오고 전체 개수도 알 수 있으니 굳이 구조를 바꿀 이유가 없다고 생각했다.

로그가 쌓이자 카운트가 발목을 잡았다

그런데 로그가 쌓이니까 목록 조회보다 전체 건수를 세는 작업이 더 오래 걸리는 것처럼 느껴졌다. 사용자는 목록만 스크롤하고 싶은데 전체 개수 때문에 기다려야 했다.

목록 쿼리와 COUNT(*) 쿼리가 함께 끝나야 화면이 표시되던 기존 구조 도식

날짜 필터와 인덱스 조건이 맞물리는 부분도 의심했다. 다만 당시 실행 계획을 확인하거나 쿼리별 시간을 측정하지는 않았다. 따라서 인덱스가 직접적인 원인이었다고 단정할 수는 없었다.

생각해 보니 목록 화면에 정확한 전체 개수가 꼭 필요한지도 의문이었다. 사용자가 원하는 건 로그를 계속 살펴보는 것이지, 매번 전체 건수를 확인하는 일이 아니었다.

LIMIT n+1로 다음 페이지 존재 여부만 확인했다

그래서 정확한 전체 개수 대신 다음 페이지가 있는지만 확인하도록 바꿨다. LIMIT n+1 방식으로 한 페이지에 표시할 개수보다 한 건을 더 가져오도록 했다.

아래 SQL은 동작 원리를 보여주는 예시다. 테이블명과 컬럼명은 실제 구현과 무관한 가상 이름이다.

SELECT *
FROM example_logs
WHERE event_time >= :from_time AND level = :level
ORDER BY event_time DESC, id DESC
LIMIT :page_size_plus_one;

요청한 개수보다 한 건이 더 조회되면 다음 페이지가 있다고 판단했다. 화면에는 요청한 개수만 표시하고, 추가로 가져온 한 건은 다음 버튼 활성화 여부를 결정하는 데 사용했다.

페이지 크기보다 한 건 더 조회해 다음 페이지 존재 여부만 판단하는 LIMIT n+1 방식 도식

물론 이 방식으로는 전체 건수나 정확한 마지막 페이지 번호를 알 수 없다. 하지만 이번 목록 화면에서는 페이지 번호 대신 이전·다음 버튼만 둬도 충분했다.

무거운 카운트와 집계는 목록 조회에서 분리해 통계 화면에서만 실행하도록 했다. 이렇게 바꾸고 나니 목록을 조회하는 속도가 체감상 확 좋아졌다. 다만 변경 전후의 응답 시간은 따로 측정하지 않았다.

목록 화면은 가벼운 조회만, 카운트와 집계는 통계 화면에서만 실행하도록 나눈 구조 도식

인덱스와 정확한 페이지 수는 다음 과제로 남겼다

인덱스는 날짜+레벨 조합을 우선 검토했다. 카운트를 줄인 뒤에도 목록이 느리다면 그때 실행 계획을 확인해 실제 병목을 찾을 생각이었다.

다음에는 EXPLAIN으로 인덱스 사용 여부와 조회 계획을 확인할 생각이다. 변경 전후의 쿼리 실행 시간도 측정해 체감이 아닌 수치로 비교할 필요가 있었다.

나중에 정확한 전체 페이지 수가 다시 필요해진다면 비동기 카운트나 근삿값 캐시를 검토할 생각이다. 다만 비동기 카운트는 화면에서 기다리는 시간을 숨길 수 있어도 카운트 쿼리 자체의 비용을 줄이지는 못한다.

근삿값 캐시도 마찬가지로 고려할 점이 있다. 조회 부담을 줄일 수 있지만 데이터가 바뀌면 표시된 개수와 실제 개수 사이에 차이가 생길 수 있다.

이번 작업에서 배운 점

  • 목록 화면에 정확한 전체 건수가 항상 필요한 것은 아니었다.
  • LIMIT n+1로 다음 페이지 존재 여부를 확인할 수 있지만 전체 페이지 수까지 알 수는 없었다.
  • 체감 성능 개선과 실제 쿼리 성능 개선은 구분하고, 다음에는 실행 계획과 응답 시간을 확인해야 했다.

이날은 쿼리 튜닝 자체보다 화면에 필요한 정확도를 다시 정한 날에 가까웠다. SQL 결과의 정확성은 지키되, 화면에 필요하지 않은 계산까지 매번 수행할 이유는 없다는 걸 배웠다.

728x90
반응형