데이터베이스

커버링 인덱스(Covering Index)로 조회 성능 개선하기

ummdev03 2025. 12. 18. 15:18

커버링 인덱스(Covering Index)는 쿼리에서 필요한 모든 정보를 담아 놓은 인덱스를 말합니다.

커버링 인덱스는 WHERE절 뿐만 아니라 SELECT로 조회하는 컬럼과 ORDER BY 조건까지 포함합니다.

 

그래서 쿼리를 실행할 때 테이블의 실제 데이터를 읽지 않아도, 인덱스만으로 바로 결과를 가져올 수 있습니다.
이 덕분에 조회 속도가 훨씬 빨라지고, 대량 데이터에서도 효율적으로 동작하죠.

PK를 통해 실제 데이터를 읽을 필요가 없습니다.

 

예를 들어 우리가 만든 서비스가 성장하여 누적 사용자가 10만 명을 넘고, 게시글이 500만 건 이상 쌓였다고 가정해보겠습니다.

카테고리, 날짜 범위로 검색하는 쿼리가 5초가 걸리고, 커넥션 풀이 20일 때, 

동시에 100명이 요청하면 단순 계산으로 맨 마지막 사용자는 최소 25초를 기다려야 합니다.

 

이 서비스가 100명의 동시 요청을 지연 없이 소화하려면,

적어도 초당 100개 이상의 트랜잭션을 처리할 수 있는 100 TPS를 확보해야 합니다.

 

목표 응답 시간: 20(커넥션 풀) / 100(목표 TPS) = 0.2초

 

즉, 현재 5초 걸리는 쿼리를 최소 0.2초(200ms) 이내로, 단축해야만 안정적인 서비스 운영이 가능하다는 결론이 나오게 되죠.

이제 위와 같은 가설을 바탕으로 인덱스를 설계 해보고 쿼리 성능을 개선해보겠습니다.

 

복합 인덱스와 커버링 인덱스 성능 비교

먼저 WHERE 절에 대한 복합 인덱스, SELECT로 조회하는 컬럼과 ORDER BY 조건까지 포함하는 인덱스를 생성하겠습니다.

CREATE INDEX idx_user_category 
ON posts (
	user_id, 
    category
);

CREATE INDEX idx_covering_all_created_desc
ON posts (
    user_id,
    category,
    created_at DESC,
    title,
    content(255)
);

 

그리고 아래와 같은 조회 쿼리를 각 인덱스로 수행했을 때 결과는 다음과 같습니다.

EXPLAIN 키워드를 붙여 실행 계획을 확인했을 때 key에 인덱스 이름이 있으면 해당 인덱스를 사용한 것입니다.

SELECT title, content, created_at
FROM posts
WHERE user_id BETWEEN 1 AND 500
  AND category = 'tech'
ORDER BY created_at DESC

 

동일 쿼리 10번 시행 결과 (단위 ms)

위 시행 결과에서 동일한 조건의 조회 쿼리에서 복합 인덱스만 사용한 경우에는 약 2초 내외의 응답 시간이 소요된 반면,
조회 컬럼을 모두 포함한 커버링 인덱스를 적용했을 때는 약 500ms 수준으로, 약 75%에 가까운 성능 개선을 확인할 수 있습니다

 

물론 이번 실험에서는 성능 차이를 극명하게 확인하기 위해

  • userId 조건의 범위를 넓게 잡았고
  • 페이징을 위한 LIMIT을 사용하지 않았기 때문에

목표 응답 시간인 200ms에는 다소 미치지 못한 결과가 나왔습니다.

 

실제 운영 환경에서처럼 명확한 사용자 범위 + 페이징 기반 조회가 적용된다면 응답 시간은 충분히 200ms 이하로 안정화될 것입니다.

 

정리해보면, 인덱스를 타는 것과 인덱스만 읽는 것은 명확히 다르다는 걸 알 수 있었습니다.

특히 Inno DB 환경에서는 커버링 인덱스를 사용한 것처럼

  • 테이블 랜덤 I/O를 줄이고
  • 엔티티 로딩 비용을 제거하며
  • 불필요한 디스크 접근을 최소화

해야겠습니다.