Modu Square

Covering Index 기반 게시글 목록 조회 개선

문제

SELECT article_id, title, content, board_id, writer_id, created_at, modified_at
FROM article
WHERE board_id = :boardId
ORDER BY article_id DESC
LIMIT :pageSize OFFSET :offset;
  • 페이지 번호가 커질수록 앞쪽 게시글을 순서대로 건너뛰는 OFFSET 비용 증가
  • 제목과 본문을 함께 조회하므로 반환하지 않을 게시글도 Clustered Index에서 읽는 문제

해결 전략

  • 목록 조회를 ID 선조회와 원본 조인 두 단계로 분리
  • 앞 단계는 board_idarticle_id만 읽어 건너뛸 게시글의 원본 접근 제거
  • 화면에 표시할 30건만 원본과 조인해 제목과 본문 조회

기술 선택 이유

  • Keyset Pagination - 제외

    • OFFSET 없이 일정한 조회 성능을 얻지만 특정 페이지 번호로 바로 이동할 수 없음
    • 현재 페이지 번호 이동 UI를 유지해야 해 우선 적용 대상에서 제외
  • Covering Index - 채택

    • 페이지 번호 이동 UI를 그대로 유지한 채 적용 가능
    • 클라이언트가 보내는 파라미터와 응답 형태를 바꾸지 않고 쿼리만 교체
기존 - OFFSET 조회Secondary Indexboard_id, article_idClustered Indextitle, content 포함offset 0article 0offset 1article 1offset …article …offset 2,999,969article 2,999,969offset 2,999,970article 2,999,970offset …article …offset 2,999,999article 2,999,999건너뛸 행까지 Clustered Index 조회개선 - Covering IndexSecondary Indexboard_id, article_idClustered Indextitle, content 포함offset 0article 0offset 1article 1offset …article …offset 2,999,969article 2,999,969offset 2,999,970article 2,999,970offset …article …offset 2,999,999article 2,999,999필요한 30건만 Clustered Index 조회

구현

SELECT article.*
FROM (
    SELECT article_id
    FROM article
    WHERE board_id = :boardId
    ORDER BY article_id DESC
    LIMIT :limit OFFSET :offset
) page
JOIN article ON page.article_id = article.article_id;
  • 건너뛸 게시글은 제목과 본문을 읽지 않고 ID만 조회

검증

페이지 100,000 - Covering Index 적용 전후

방식실행 시간조회 범위
기존 목록 조회약 3.8s건너뛸 게시글도 원본에서 조회
Covering Index 적용약 0.3s최종 30건만 원본에서 조회
  • 페이지 크기 30, OFFSET 2,999,970 동일 위치에서 실행 시간 약 3.8s → 0.3s로 단축

한계

  • Covering Index 적용 후에도 OFFSET 탐색 비용은 남음. 페이지 500,000에서 실행 시간 9.42초
  • 번호 페이지 UI를 유지하느라 Keyset은 보류. 무한 스크롤이나 연속 탐색이 중요해지면 전환 필요