Q.느린 API를 받았을 때 DB 쪽 원인을 어떤 순서로 확인하나요?
| 순서 | 확인 | 알 수 있는 것 |
|---|
| 1 | 그 API 가 실행한 쿼리 목록과 각 시간 | DB 가 원인인지, 몇 번째 쿼리인지 |
| 2 | 쿼리 실행 횟수 | 반복 호출(N+1)인지 |
| 3 | 실행 계획 | 인덱스를 타는지, 스캔 행수가 얼마인지 |
| 4 | 잠금과 대기 | 쿼리 자체는 빠른데 기다리는지 |
| 5 | 커넥션 풀 | 쿼리 전에 풀에서 기다리는지 |
| 6 | DB 자원 | 다른 부하 때문에 전체가 느린지 |
1번과 2번을 먼저 하는 이유가 있습니다. 쿼리 하나가 느린 것과 빠른 쿼리를 100번 부르는 것은 총 시간이 같아도 대응이 완전히 다릅니다. 전자는 인덱스, 후자는 호출 구조입니다.
4번과 5번은 쿼리 자체를 봐서는 안 보입니다. 실행 계획이 완벽한데 느리면 대기를 봐야 합니다.
흔한 실수: 느린 쿼리 로그만 보고 판단하는 것. 임계값 미만의 쿼리가 수백 번 실행되는 경우는 그 로그에 안 남습니다. 요청 단위로 쿼리 수와 총 시간을 함께 봐야 합니다.
Q.EXPLAIN 결과에서 무엇을 먼저 보나요?
| 순서 | 보는 것 | 판단 |
|---|
| 1 | 스캔한 행수와 반환한 행수의 비 | 1,000행을 읽어 10행을 주면 걸러내는 비용이 크다 |
| 2 | 접근 방식 | 인덱스를 타는가, 전체를 훑는가 |
| 3 | 어떤 인덱스를 썼는가 | 의도한 인덱스인가 |
| 4 | 정렬과 임시 테이블 | 별도 정렬이나 임시 테이블이 생기는가 |
| 5 | 조인 순서 | 작은 쪽부터 시작하는가 |
| 6 | 예상과 실제의 차이 | 통계가 낡았는지 알 수 있다 |
1번이 가장 정보량이 많습니다. 읽은 행수가 결과 행수보다 훨씬 많으면 인덱스가 조건을 충분히 반영하지 못한 것입니다.
6번은 실제 실행 계획을 볼 때만 알 수 있습니다. 예상 1,000행인데 실제 100만 행이면 통계가 낡아 잘못된 계획을 고른 것이고, 통계를 갱신하면 해결되는 경우가 있습니다.
흔한 실수: 전체 스캔을 무조건 문제로 보는 것. 전체의 30% 이상을 읽는 쿼리라면 인덱스로 하나씩 찾아가는 것보다 순차 읽기가 빠릅니다. 행수와 함께 봐야 판단이 됩니다.
Q.페이지가 뒤로 갈수록 느려지는 문제를 어떻게 해결하나요?
건너뛰기 방식은 앞의 행을 실제로 읽고 버리기 때문입니다. 10만 번째 페이지를 보려면 10만 행을 읽고 버립니다.
| 방식 | 동작 | 뒤 페이지 |
|---|
| OFFSET | 앞의 행을 모두 읽고 버린다 | 선형으로 느려진다 |
| 커서 기반 | 마지막으로 본 값 이후를 인덱스로 찾는다 | 페이지 번호와 무관하게 일정하다 |
커서 방식은 이렇게 씁니다.
| 항목 | 내용 |
|---|
| 조건 | WHERE (created_at, id) < (마지막값, 마지막id) |
| 정렬 | 그 컬럼 조합으로 인덱스를 만든다 |
| 동점 처리 | 정렬 키가 같을 수 있으므로 고유 컬럼을 함께 넣는다 |
한계도 있습니다. 임의 페이지로 점프할 수 없고 이전 페이지로 가려면 별도 처리가 필요합니다. 그래서 무한 스크롤에는 잘 맞고, 페이지 번호를 보여주는 화면에는 그대로 쓰기 어렵습니다.
번호가 꼭 필요하면 앞쪽 몇 페이지만 번호로 주고 그 뒤는 검색 조건을 좁히도록 유도하는 방법을 씁니다.
흔한 실수: 인덱스만 추가하고 해결됐다고 보는 것. 인덱스가 있어도 건너뛴 행은 읽어야 하므로 뒤 페이지는 여전히 느립니다.
Q.인덱스를 추가하기 전에 무엇을 검토해야 하나요?
인덱스는 조회를 빠르게 하고 쓰기와 저장 공간을 대가로 받습니다. 그 거래가 맞는지 봅니다.
| 검토 | 내용 |
|---|
| 기존 인덱스로 되는가 | 복합 인덱스의 앞부분과 겹치면 새로 만들 필요가 없다 |
| 선택도 | 값의 종류가 적으면(성별 등) 효과가 거의 없다 |
| 쓰기 빈도 | 삽입과 수정이 잦은 테이블은 인덱스마다 비용이 든다 |
| 컬럼 순서 | 복합 인덱스는 순서가 결과를 가른다 |
| 실제 쿼리 패턴 | 자주 오는 조건인가. 한 번 쓰는 배치용은 아닌지 |
| 쿼리를 고칠 수 있는가 | 함수를 씌워 인덱스를 못 타는 것이면 그쪽이 먼저 |
첫 번째가 가장 자주 놓칩니다. (a, b) 인덱스가 있으면 a 단독 조건도 그것을 씁니다. a 만 있는 인덱스를 또 만들면 쓰기 비용만 늡니다.
마지막도 중요합니다. WHERE DATE(created_at) = '2026-08-14' 는 인덱스를 못 타는데, 범위 조건으로 바꾸면 기존 인덱스로 해결됩니다.
흔한 실수: 느린 쿼리마다 인덱스를 하나씩 추가하는 것. 테이블에 인덱스가 열 개 넘게 쌓이면 쓰기가 눈에 띄게 느려지고, 옵티마이저가 잘못된 것을 고르기도 합니다.
먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.
읽었으면 문제로 확인해보세요
성능 최적화 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.