(4) DB 복합 Index 및 커서 페이지네이션을 통한 수행 시간 최적화

이원석·2025년 8월 17일
1

사이드 프로젝트

목록 보기
9/10

채팅 서비스나 SNS 피드 구성에서 무한 스크롤을 구현할 때, 쿼리의 성능은 사용자 경험에 직결됩니다. (커서 페이지네이션 관련 포스팅)

이번 포스팅은 실제 서비스에서 발생한 쿼리를 "Plan Cache" 기반으로 분석하면서, 후보군 중 어떤 인덱스가 선택되었고 왜 더 효율적인지 정리해보았습니다.



1. 복합 인덱스의 활용

특정 item_id의 채팅 로그를 커서 기반 페이지네이션(cursor pagination) 방식으로 불러오는 쿼리입니다.

db.chat.find({
  item_id: '0',
  _id: { $lt: ObjectId('687ab67e5d0bb28eef7b1975') }
}).sort({ _id: -1 }).limit(21)



2. 실행 계획

2-1. MongoDB가 선택한 실행 계획

Plan Cache를 확인해보니 예상대로 MongoDB는 item_id + _id 복합 인덱스를 실제로 사용했습니다.

IXSCAN 
  keyPattern: { item_id: 1, _id: -1 }
  indexName: "item_id_1__id_-1"
  indexBounds: { item_id: "0", _id: (커서 이후 ~ -∞) }

...

{
  nReturned: 21, // 반환된 문서 수
  executionTimeMillisEstimate: 0, // 실행 시간(밀리초 단위 추정치)
  totalKeysExamined: 21, // 인덱스 키 스캔 개수
  totalDocsExamined: 21 // 실제 문서 조회 개수
}

2-2. 탈락한 다른 후보: _id 단일 인덱스

MongoDB는 _id 인덱스만 사용하는 계획도 고려했지만, 해당 경우 item_id는 인덱스가 아니라 FETCH 단계에서 필터링해야 했습니다.

  • 실행 시간은 4ms로 더 느림
  • Plan Score도 더 낮음
candidatePlanScores: [3.0005, 3.0003]



3. 100만건 채팅 데이터 기준 실제 수행 속도 측정

3-1. 인덱스 확인


3-2. 인덱스 타기 전 수행 속도

  • 첫 번째 최초 로드: 6824ms -> 6.8s
  • 커서 페이지네이션 로드: 482ms -> 0.4s

3-3. 인덱스 탄 이후 수행 속도

  • 첫 번째 최초 로드: 151ms -> 0.1s
  • 커서 페이지네이션 로드: 28ms -> 0.02s



구분최초 로드 시간커서 페이지네이션 로드 시간
개선 전6,824ms (약 6.8초)482ms (약 0.4초)
개선 후151ms (약 0.1초)28ms (약 0.02초)
개선율97.8% 단축94.2% 단축

전반적으로 100ms 수준의 응답성을 확보했고, 체감 성능이 “즉각적”으로 변한 수준의 개선이 이루어졌습니다.



4. 왜 복합 인덱스가 더 빨랐을까?

이를 이해하기 위해서는 FETCH 단계를 이해해야 합니다.

FETCH 단계란?
IXSCAN이 가져온 문서의 ObjectId를 통해 실제 Collection에서 문서를 읽어옴. 이때 인덱스에 없는 조건(=인덱스로 커버 안 되는 필드)은 여기서 검사해야 함.

단일 인덱스의 경우는, _id는 뽑아온 데이터(무한스크롤을 위한 x값 이전의 데이터)의 item_id를 하나하나 검사를 하게 됩니다. 이를 FETCH 단계에서의 필터링이라고 합니다.

복합 인덱스의 경우에는, "item_id가 0인 책 중에서 _id < ...인 애들만 주세요"로 데이터를 뽑아오기 떄문에 (item_id + _id) "모두 인덱스에서 처리" 하며 → FETCH는 “결과 읽기”만 담당하게 됩니다.

즉 "단일 인덱스는 하나하나 검열이라 오래걸림, 복합 인덱스는 처음부터 정답만 뽑아준다" 라고 볼 수 있습니다.



5. 마무리

이전 사이드 프로젝트에서도 복합 인덱스를 통한 성능 최적화 관련 내용을 포스팅 했었는데, 이번에는 실행 계획을 기반으로 왜 DBMS가 왜? 복합 인덱스를 선택했는지에 대해 상세하게 알아보았습니다.

0개의 댓글