DB/MySQL

[Index] 플랫폼 내 페이지네이션 방식에 대한 개선사항 제안

Joonfluence 2025. 1. 18.

게시글 목록 개편

2024년 4월, 월부 커뮤니티에서는 ‘잘 보여주기’를 위해, 더 많은 컨텐츠를 한 화면 안에서 보여주기 위해 썸네일을 제외하여 보여주는 방식으로 변경되었습니다. 기존에는 무한스크롤 방식으로 조회되던게, 페이지네이션 UI를 통한 검색으로 변경되었습니다. 이와 더불어, 마지막 페이지 조회가 가능하도록 변경되었습니다. 변경된 조회 방식으로 인해, 발생된 문제점과 단/장기 개선사항에 대해서 정리하여 보였습니다.

현재 월부의 페이지네이션 방식 : LIMIT OFFSET

현재 플랫폼의 페이지네이션 방식은 LIMIT OFFSET 방식을 사용하고 있습니다. 이 방식은 간단하고 구현이 쉬운 장점이 있지만, 대규모 데이터셋에서 성능 저하와 일관성 문제를 일으킬 수 있습니다.

LIMIT OFFSET의 성능 저하 문제

특히 문제가 되는 상황은 LIMIT OFFSET에서 OFFSET이 지나치게 클 때 입니다.

SELECT * FROM contents LIMIT 4000000, 20;

1. 전체 스캔

  • 테이블 전체 스캔: OFFSET 값이 클수록 데이터베이스는 더 많은 행을 스캔해야 합니다. 예를 들어 offset 10000, limit 20 이라 하면 최종적으로 10,020개의 행을 읽어야 합니다. (10,000부터 20개를 읽어야하니) 이로 인해 쿼리의 비용이 크게 증가합니다. 문제가 됐던 투자공부인증 게시판 마지막 페이지 조회 시, 마지막 페이지까지 전부 조회해와야 하기 때문에, 조회까지 최소 6초에 달하는 시간이 소요됨.

2. 인덱스 비효율

  • 인덱스의 비효율적인 사용: 많은 경우, LIMIT OFFSET 쿼리는 전체 테이블 스캔 또는 인덱스 스캔을 필요로 하므로, 인덱스의 효율성이 떨어질 수 있습니다. 특히 큰 OFFSET 값을 사용할 때 인덱스의 효율이 급격히 저하될 수 있습니다. 보통의 경우, 인덱싱과 정렬을 통해 빠르게 조회되지만 해소되지만, OFFSET이 클 때, 이미 정렬된 데이터를 건너 띄어 조회하며, 인덱스가 무시되곤 한다.

단기 해결책

여러 방법이 있겠지만, 제가 생각한 방안은 아래와 같습니다.

1. 마지막 페이지 조회 기능 제거

  • 사용자는 마지막 페이지를 굳이 조회할 필요가 없습니다. 만약 데이터를 찾기 위함이라면 검색을 하면 됩니다. 오히려 >> 버튼 클릭 시, 10페이지 씩 이동하는 것을 사용자는 기대할 수도 있으리라 생각됩니다. 그러나 예상치 못하게 마지막 페이지로 이동되고, 이로인해 서버에 불필요한 부하를 주고 있는 상황입니다.
  • 대용량 데이터를 취급하는 서비스이면서 마지막 페이지를 조회할 수 있게 하는 경우는 거의 없습니다.
네이버카페 게시글 목록 페이지네이션
 

쿠팡의 상품상세 페이지네이션

2. 필터 추가

  • 마지막 페이지 조회가 꼭 필요하다면 아래와 같이 정렬 필터를 제공하고, 오래된 순으로 조회하도록 UI를 추가해주면, 백엔드에서도 쉽고 빠르게 조회해올 수 있습니다.
네이버 뉴스 검색

근본적인 해결책

1. 쿼리 리팩토링을 통한 복합 인덱스 적용

커버링 인덱스(Covering Index)는 데이터베이스 인덱스 중 하나로, 특정 쿼리를 실행할 때 인덱스 자체만으로 쿼리를 처리할 수 있게 하는 인덱스를 말합니다. 이를 통해 테이블의 데이터 페이지를 읽을 필요 없이 인덱스만으로 원하는 데이터를 얻을 수 있어 성능 향상이 가능합니다.

예시)

-- 인덱스 생성 (커버링 인덱스로 활용)
CREATE INDEX age_index ON SOME_TABLE(`AGE`, `NAME`);

-- 조회 쿼리 (커버링 인덱스 활용)
SELECT `NAME` FROM SOME_TABLE WHERE `AGE` = 41;

조회 쿼리에서 사용된 모든 칼럼(NAME , AGE)으로 구성된 새로운 Non-Clustered 인덱스를 생성했습니다. 이 경우, 아래와 같이 동작하게 됩니다.

그런데 만약 조회할 Row의 모든 칼럼을 확인하고 싶다면, 해당 테이블의 모든 칼럼을 가지고 있는 인덱스를 만들어야 하는 것일까요? 만약 모든 칼럼의 값이 궁금하다면 아래와 같이, 필요한 데이터의 ID 값만 미리 뽑아옴으로써 최적화할 수 있습니다.

SELECT * FROM [테이블명] AS t1
  JOIN (
	[커버링 인덱스를 사용해 페이징하는 서브쿼리]
  ) AS t2
ON t1.ID = t2.ID;

복합 인덱스의 작동 원리

  1. 인덱스 구성 요소: 커버링 인덱스는 쿼리에서 필요한 모든 열을 포함합니다. 예를 들어, SELECT column1, column2 FROM table WHERE column3 = value 쿼리가 있다고 가정하면, 커버링 인덱스는 `column1`, `column2`, `column3`를 포함합니다.
  2. 쿼리 실행 최적화: 쿼리를 실행할 때 데이터베이스 엔진은 인덱스만을 스캔하여 필요한 데이터를 가져올 수 있습니다. 따라서 테이블의 데이터 페이지에 접근하는 오버헤드가 줄어듭니다.
  3. 성능 향상: 테이블 페이지를 읽는 I/O 작업을 줄임으로써 쿼리의 실행 속도가 빨라집니다. 특히 대용량 데이터셋에서 유용합니다.

다만 이 방법을 적용하려면, 커버링 인덱스를 설정하려면, 쿼리에서 사용되는 모든 컬럼들이 인덱스에 포함되어야 합니다. 이렇게 하면 인덱스만으로 쿼리를 처리할 수 있어 성능이 향상될 수 있습니다. 그렇지만, 과도하게 많은 칼럼을 인덱스에 포함시킴으로써 쓰기 성능이 저하될 수 있다는 단점이 있습니다.

복합 인덱스 걸 때 중요한 점

  1. 가장 중요한 컬럼을 먼저 배치하기 
  2. 쿼리의 사용 패턴 분석
  3. 데이터의 카디널리티에 따른 순서 배치
  4. 읽기와 쓰기 비중 확인
  5. 인덱스 크기와 저장소 비용 확인
  6. 범위 조건 사용 여부 확인 
  7. 커버링 인덱스 고려 

커버링 인덱스란 인덱스에 필요한 컬럼을 모두 포함하는 것을 말합니다. 쿼리가 인덱스만 읽고 데이터를 테이블에서 추가로 조회할 필요가 없어져, 성능이 더 좋아집니다. 물론 쓰기 성능이 저하될 가능성이 있으므로 주의해야 합니다. 

예시)

board 테이블 인덱스 생성

CREATE INDEX idx_tb_board_covering ON tb_board ( BOARD_SEQ, BOARD_NAME, BOARD_CATEGORY_CD, BOARD_TYPE_CD, SECRET_YN, ANONYMOUS_YN, FAVORITE_YN, READ_AUTH_CD, WRITE_AUTH_CD, COMMENT_AUTH_CD, GUIDE_TEXT ) INCLUDE ( BOARD_SEQ, BOARD_NAME, BOARD_CATEGORY_CD, BOARD_TYPE_CD, SECRET_YN, ANONYMOUS_YN, FAVORITE_YN, READ_AUTH_CD, WRITE_AUTH_CD, COMMENT_AUTH_CD, GUIDE_TEXT );

이렇게 모든 관련 테이블에 대해 인덱스를 설정하면 쿼리가 인덱스를 사용해 빠르게 데이터를 조회할 수 있습니다.

그러나 이와 같이 과도한 인덱스를 작성하는 것은 매번 여러 인덱스를 복합적으로 걸어줘야 하기 때문에 근본적인 해결이 될 순 없습니다. 각각의 테이블을 분리하여, PKey 기반으로 조회하도록 변경해줄 수 있습니다.

보드 컨텐츠 조회

  • 조회 대상이 되는 게시글을 먼저 조회한다.
  • 해당 로직에서 조회된 요소를 기반으로 나머지 값들을 타 테이블들과 조인하여 조회해온다.

2. No Offset 구조로 페이지네이션 방식 변경

  • 해당 방식은 마지막으로 조회한 레코드의 고유 ID를 기준으로 다음 페이지를 가져옵니다. 인덱스를 효과적으로 활용하여 쿼리 속도를 크게 향상시킵니다. 또한 OFFSET을 사용하지 않으므로, 필요 없는 행을 건너뛰기 위해 전체 테이블을 스캔할 필요가 없습니다. 디스크 I/O가 줄어듭니다.
(예시) 마지막으로 조회한 레코드의 ID가 100인 경우 다음 10개의 레코드를 가져오기
SELECT * FROM orders WHERE id > 100 ORDER BY id ASC LIMIT 10;
  • 다만, 현재 페이지네이션 방식에서 조회 방식이 변경되어야 하기 때문에, 적용하기에 시간이 오래 걸릴 것 같습니다. 그렇지만 대용량 데이터를 다루는 곳이라면 No Offset 방식으로 조회해오는 경우가 많기 때문에, 향후 도메인에 따라, 대용량 데이터를 조회해와야 하는 상황이라면 No Offset 방식을 적극 고려해보면 좋을 것 같습니다.

3. 검색엔진(ElasticSearch) 활용

  • ElasticSearch와 같은 분산 검색 엔진은 대규모 데이터셋에서 빠르고 효율적인 검색을 위해 특화된 기술을 사용합니다.그리고 이미 월부의 커뮤니티 게시글은 검색엔진에 모두 저장되어 있는 상황이며, OpenSearch란 별도의 서버를 통해 검색이 이뤄지고 있습니다. 참고로 ElasticSearch에서도 페이지네이션 방식으로 No Offset 방식을 활용하고 있습니다.
  • 그 외에도 고성능 역색인, 분산 아키텍처, 고급 검색 알고리즘, 캐싱, 최적화된 데이터 구조, 비동기 처리 등 다양한 기술을 활용하여 데이터를 빠르고 효율적으로 조회할 수 있습니다. 이러한 특성은 특히 대규모 데이터셋에서 LIMIT OFFSET 방식보다 검색 성능을 크게 향상시킵니다.

레퍼런스

https://taegyunwoo.github.io/tech/Tech_DBPagination#db%EA%B0%80-%EC%9D%B8%EB%8D%B1%EC%8A%A4%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%A1%B0%ED%9A%8C%ED%95%98%EA%B3%A0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EA%B0%80%EC%A0%B8%EC%98%A4%EB%8A%94-%EA%B3%BC%EC%A0%95

반응형

댓글