[Article] 게시판 페이징

Kooks·2025년 11월 10일

SpringBoot

목록 보기
3/7
post-thumbnail

게시판 설계

articleIdPrimary Key
title제목
content내용
board_id게시판 ID(Shard Key)
writer_id작성자 ID
created_id생성 시간
modified_at수정 시간

분산 데이터베이스를 사용을 고려해 게시글이 N개의 Shard로 분산되는 상황을 고려한다.

Shrad Key = board_id

❓Shard Key는 왜 게시판 ID일까?
❗️게시글 서비스는 게시판 단위로 서비스를 이용한다. 게시판 단위로 게시글 목록이 조회되기 때문

만약 Shard Key가 article_id 일 경우 동일한 게시판에 속한 게시글이 별도의 샤드에 저장될 수 있다.
각 게시판의 게시글 목록을 조회하는 경우 게시글이 속한 모든 샤드에 조회를 요청해야 한다.
N개의 샤드라면, 게시글의 N개의 샤드로 분산되어 있으므로 게시글 목록 조회를 위해 N개의 쿼리를 사용해 목록을 조회하는 복잡한 처리가 필요하다.

Primary Key

  • 오름차순 유니크 숫자를 애플리케이션에 직접 생성
    AUTO_INCREMENT 또는 UUID를 사용하지 않는다.
  • 오름차순 유니크 숫자를 만들기 위한 알고리즘
    Snowflake, TSID 등

게시글 API 및 Test Data

  • 1200만 건 데이터 삽입
    1번 게시판에 1200만 건 게시글

❓대규모 데이터에서 게시글 목록 조회는 왜 복잡할까?
모든 데이터를 한 번에 보여줄 수 없다. 메모리, 네트워크, 성능 등의 제약...페이징이 필요하다.

서버 애플리케이션 내의 메모리로 디스크에 저장된 모든 데이터를 가져오고, 특정 페이지만 추출하는 것은 비효율적이다. 데이터베이스에서 특정 페이지의 데이터만 바로 추출하는 방법이 페이징 쿼리다.

페이징

  • 페이지 번호
    이동할 페이지 번호가 명시적으로 지정, 원하는 페이지로 즉시 이동 가능

  • 무한 스크롤
    스크롤을 내리면 다음 데이터가 조회, 주로 모바일 환경에서 사용

페이지 번호

페이지 번호 방식에는 두 가지 정보가 필요하다.
페이지 당 30개의 게시글을 보여주고, 총 94개의 게시글이 있다면, 사용자는 클라이언트 화면에서 4번 페이지까지 이동할 수 있다. 페이지 번호 활성화에 필요

N번 페이지에서 M개의 게시글을 조회하려면?
SQL offset, limit을 활용하여 페이지 쿼리를 할 수 있다. offset지점 부터 limit개의 데이터 조회

select * from article
where board_id = {board_id}
order by create_at desc 
limit 30 offset 90;

그런데 1,200만 건의 데이터를 조회하는데 4초가 소요되었다. 정상적으로 서비스하기 어려운 상황이다. Query Plan으로 확인해보고 인덱스를 사용한다.

create index idx_board_id_article_id on article(board_id asc, article_id);

여기서 created_at 이 아닌 article_id로 사용했나?
현재 게시판은 분산 환경을 가정하여 만들고 있는데 여러 서버에서 동시에 처리될 수 있기 때문이다. 게시글이 동시에 생성될 수 있다. 또한, 테스트 데이터를 멀티스레드에서 동시에 생성했기 때문에 동일 시간을 갖는 데이터가 많다.

article_id는 분산 시스템에서 고유한 오름찬순을 위해 고안된 알고리즘을 사용해 데이터를 저장한다.

select * from article
where board_id = 1
order by aricle_id desc 
limit 30 offset 90;

이전에는 약 4초가 소요되었다면 0초대에 수행되는 것을 확인해 볼 수 있다.
또한, Query Plan를 살펴보면 key = idx_board_id_article_id 를 사용하는 것도 확인해 볼 수 있을 것이다.

그렇다면 모든 문제는 해결되었는가??
만약 50,000페이지를 조회한다면 다시 4초의 시간이 걸릴 것이다. 인덱스를 생성하고 사용하게 했지만 느려진다.

이런 경우에는 Secondary Index(보조 인덱스 or Non-clustered Index)가 Clustered Index에 접근하게 하여 데이터를 가져오면 된다.
Secondary Index에더 데이터에 접근하기 위한 포인터를 찾은 뒤, Clustered Indexd에서 데이터를 찾는 방식이다.

select * from (
    select article_id from article
    where board_id = 1
    order by article_id desc
    limit 30 offset 1499970
) t left join article on t.article_id = article.article_id;

여기서 서브쿼리는 Covering Index로 동작한다. 데이터를 읽지 않고 인덱스에 포함된 정보만으로 쿼리가 가능하기 때문에 1499970 건이 있어도 빠르게 조회할 수 있다.

만약 여기서 더 높은 페이지를 조회할려 한다면??? 여기서 생각해 볼 수 있는건 그 조회가 정상적인 조회인지를 생각해야 한다. 데이터 수집을 목적으로 하는 어뷰저일 수도 있기 때문에 정책으로 풀어내 특정 페이지까지만 제한해야 한다.

profile
I'm kooks

0개의 댓글