본문 바로가기

신입 개발자 면접 기초

웹 개발 페이지 처리(Paging) 방법 - 성능을 고려해보자

반응형

웹 개발 페이지 처리(Paging)

웹 개발을 하면서 당연하게(?) 고민하게 되는 것은 게시판의 페이징 처리다.

실제로 면접에서 질문을 받아봤다. 

"페이징처리할 때 어떻게 하셨어요?"

대답은 게시물의 총 개수와 한 페이지당 보여줄 게시물의 개수를 기준으로 이렇게 저렇게 해서 DB는 Mysql을 썼었으니까 LIMIT로 가져왔다고 대답했다.

그런데 그런건 당연한 얘기고 본인이 듣고 싶었던 키워드는 "커서"였다고 했다.

흠.. 여전히 잘 모르겠으나 페이지 처리를 정리해보고자 한다.


MySQL 에는 LIMIT, MS-SQL 2012에서는 OFFSET Fetch로 페이징 쿼리를 조금 더 쉽게 작성할 수 있으나 현업에서 해당 버전, 해당 DBMS를 사용한다는 보장이 없으니 기본적인 것을 알고 가도록 한다.

게시판 페이지 처리 기본 과정 (Oracle)

1. 전체 게시물의 개수를 가져온다. (totalCount)

1
select count(*) as totalCount from board
cs

2. 한 페이지에 몇 개의 게시글을 보여줄지를 정한다. (listCount) ex) 10개, 20개씩 보여준다!

3. 이제 게시판이 몇 개의 페이지를 가지는지 구할 수 있다. (총 페이지 수, totalCount)

totalPage = totalCount / listCount ;

나눠떨어지지 않는 경우에 추가로 페이지가 하나 더 있어야 하므로 +1을 해준다.

1
2
3
4
5
6
7
8
9
int totalCount = /*DB접속후 쿼리를 통해 얻은 값*/;
 
int listCount = 10;
 
int totalPage = totalCount / listCount;
 
if (totalCount % listCount > 0) {
    totalPage++;
}
cs

이것으로 1에서 총 페이지 수(totalPage)까지 출력 가능

또한 url에서 임의로 page수를 바꿀 수 있으므로 총 페이지 수 보다 높은 페이지를 접근 못하게 예방한다.

1
2
3
4
//page = 현재 보고있는 페이지
if (totalPage < page){
    page = totalPage;
}
cs

4. 하단에 페이지 번호들을 몇 개 보여줄지 정한다. (pageCount 10개=> 1~10페이지, 11~20페이지, ...)

5. 시작페이지끝페이지를 계산한다.

1
2
3
4
5
int startPage = ((page - 1/ pageCount) * pageCount + 1;
//현재 페이지가 pageCount와 같을 때를 유의하며 (page-1)을 하고
// +1은 첫페이지가 0이나 10이 아니라 1이나 11로 하기 위함임
int endPage = startPage + pageCount - 1;
// -1은 첫페이지가 1이나 11 등과 같을때 1~10, 11~20으로 지정하기 위함임
cs

6. 대신 5와 같이 끝페이지를 계산해버리면 totalPage(총 페이지 수)보다 크게 잡힐 위험이 있으니 그것을 처리함.

1
2
3
if (endPage > totalPage) {
    endPage = totalPage;
}
cs

7. 이제 현재 페이지(page)를 가지고 시작 페이지와 끝 페이지를 동적 계산하도록 변경되었으므로 현재페이지를 자유롭게 옮겨다닐 수 있도록 [처음][이전] /*1,2,3,4,...,10*/ [다음][끝] 링크를 만듦.

=> 단순히 처음, 끝은 page=1,page=totalPage로 링크시키면되고 이전, 다음은 page-1, page+1로 링크시키면 됨.

//이전, 다음으로 다음 startpage, endpage 구조로 넘어가게 할 수 있음 > page=startpage-1, endpage+1 하면됨.

끝.


이걸 실제로 spring, jsp에 적용하려면 구조는 조금 더 복잡하겠지만 메커니즘은 정리가 되었다.

그런데 좀더 나아가서 성능이 좋은 게시판, 페이지 처리를 고민해야한다.

Oracle에서 페이징 생각하기

1단계 - oracle에서 rownum이라는 가상 칼럼을 만들어서 번호를 붙여주기

1
2
3
4
5
select rownum as rnum, A.id, A.name, A.content, A.regdate
from (
    select id, name, content, regdate
    from board
    order by regdate desc ) A


* order by절과 rownum을 한번에 적용하려고 하면 order by 의 정렬보다 rownum이 먼저 적용되어 엉뚱한 순서로 번호가 붙게 된다.

따라서 인라인뷰로 먼저 정렬을 한 후 가상으로 번호를 붙인 칼럼을 만드는 rownum을 사용한다.

2단계 - between대신 rownum의 범위를 나눠서 정하기 [key point]

1
2
3
4
5
6
select rownum as rnum, A.id, A.name, A.content, A.regdate
from (
    select id, name, content, regdate
    from board
    order by regdate desc ) A
where rownum between 11 and 20
cs

이렇게하면 잘 가져올 수는 있으나 데이터가 수 백, 수 천만건이면 엄청나게 느려진다

왜냐하면 rownum을 수백만 데이터에 붙이고 그 중에서 11~20에 있는 값을 가져오기 때문이다.

이것을 나눠서 표현하면 옵티마이저의 힘으로 성능을 좋게 만들 수 있다.

(게시판의 페이지를 볼 때 대부분의 사람들이 5페이지 이상 잘 안보는 특성을 고려함)

1
2
3
4
5
6
7
8
9
select B.rum, B.id, B.name, B.content, B.regdate
from
    (select rownum as rnum, A.id, A.name, A.content, A.regdate
    from (
        select id, name, content, regdate
        from board
        order by regdate desc ) A
    where rownum <=20 ) B
where B.rnum >= 11
cs

이렇게 하면 DBMS 옵티마이저가 rownum을 20번까지 붙이다가 그 이상을 필요없다고 판단하고 수행하지 않는다.

그런 후에 rownum의 11번부터 20번을 가져오게 되니 엄청난 성능 향상이 된다. (데이터가 많을 수록 눈에 띔)

이정도만 해도 100만건인 데이터에서 제일 뒤인 999991~1000000인 부분의 수행시간은 약 0.8초가 된다.

3단계 - 인덱스를 이용하여 한 발 더 나아가기.

1
2
3
4
5
6
7
8
9
select B.rum, B.id, B.name, B.content, B.regdate
from
    (select /*+index_desc(A 인덱스명)*/ rownum as rnum, A.id, A.name, A.content, A.regdate
    from (
        select id, name, content, regdate
        from board
        order by regdate desc ) A
    where rownum <=20 ) B
where B.rnum >= 11
cs

select문에서 옵티마이저에게 인덱스 힌트를 주는 방법을 사용하면 조금 더 성능이 좋은 페이징 처리를 할 수 있다.

기본키는 자동으로 유니크인덱스가 설정되므로 적용하는 것이 좋겠다.

이것도 다른 블로그의 실험을 보면 999991~1000000인 부분의 수행시간은 약 0.6초가 된다.

* 인덱스를 잘 지정하는 것이 포인트같다.

+ MySQL의 LIMIT는 위의 오라클의 rownum+인덱스를 조합한 것보다도 성능이 월등히 좋다...


참고 사이트 : 

http://kdarkdev.tistory.com/272

http://fruitdev.tistory.com/45

http://egloos.zum.com/cubenuri/v/2273888

반응형
  • 그램 2019.03.08 15:13

    모든 row 개수를 가져올때 where조건 문 붙으면 인덱싱이 안되서 데이터 많으면 느려지지 않나요?

    • Favicon of https://jeong-pro.tistory.com BlogIcon JEONG_AMATEUR 2019.03.08 20:28 신고

      질문하신 내용을 잘 이해를 못했습니다;
      양해부탁드립니다.
      위의 예제에서는 모든 row의 개수(count)를 가져올 때 where절이 들어가 있지 않습니다.
      오라클에서 제공하는 rownum은 쿼리 결과에서 제한된 부분만 가져올 때 사용하는 기능입니다.
      여기서는 현재 페이지에서 보여줘야할 데이터만 제한해서 가져오는 것이지요.
      이 부분에서 사용한 where절은 모든 데이터를 다 가져오기 전에, 필요한 컬럼 수를 제한해야 하니까 DBMS의 옵티마이저에게 알려준 것입니다.
      인덱싱이라기보다 쿼리 결과의 컬럼 수를 컨트롤한다고 보면 될 것 같습니다.

  • chldnjstkd 2019.08.17 14:01

    rownum as rnum 으로 굳이 해주는 이유가 있나요?
    .A .B이걸 굳이 하는 이유가 있나요?(이런 문법을 뭐라하나요?)

    공부중인데 왜쓰는지 잘 몰라서 질문 남깁니다 !

    • Favicon of https://jeong-pro.tistory.com BlogIcon JEONG_AMATEUR 2019.08.19 12:26 신고

      rownum은 레코드에 번호를 붙이는 방법이고요.
      번호를 붙여놔야 원하는 번호만 가져올 수 있어서 수행시간이 짧아지기 때문에 사용합니다.
      A. B. 이런것은 테이블이나 뷰에 alias를 적용한다고 보시면 됩니다.
      테이블명이 엄청나게 길거나 중간결과로 나온 뷰에 간단하게 이름을 붙이는 것으로 구분하기도 좋아집니다.

  • 퀘스쳔 2020.05.21 08:25

    질문이있는데..! rownum <= 20을 인라인뷰 A 안으로 넣어주면 값이 달라지나요? 왜 한 번 더 인라인뷰로 감싸서 처리하죠??

    • Favicon of https://jeong-pro.tistory.com BlogIcon JEONG_AMATEUR 2020.05.22 17:01 신고

      A안에는 아직 rownum 컬럼이 생성되지 않았기 때문입니다!

    • 지나가는 ㅎㅌㅊ 2020.05.23 06:35

      A안에 rownum<=20을 넣어주면 결과가 달라져요. 딱 20개만 가져오고 그 안에서 order by를 하니깐요.

      인라인뷰로 감싸서 처리하는 이유는 일단 모든 데이터를 가져와서 regdate 기준으로 order by를 하기 위해서입니다.

      그러면 글에서 말한 100만건 이상인 수많은 데이터를 가져와서 정렬하는거랑 뭐가 다르냐 하실텐데... 오라클에서 저런 식으로 처리하면 실행계획에 count stop key라는게 나와서 rownum을 기준으로 정렬을 하는 그런 매커니즘이 있는데용... 자세한건 count stop key를 검색하셔서 찾아봐주세요 ㅎㅎ

    • Favicon of https://jeong-pro.tistory.com BlogIcon JEONG_AMATEUR 2020.05.23 19:34 신고

      지나가는 ㅅㅌㅊ로 정정부탁드립니다(?)
      대댓분께서 더 정확하게 지적해주셨습니다.
      order by보다 rownum으로 레코드에 번호 붙이는게 먼저되어서 원하는 결과를 얻을 수 없습니다.

  • 컴맹 2020.12.04 21:09

    글 잘 읽었습니다. 근데 위에 내용으로 페이징시 원글과 답글이 다음 페이지로 넘어가지 않나요 ? 전 원글과 답글이 같이 붙어 있어서 다음페이지로 넘어가도 원글에 붙어 있어 다음 글로 안넘어 갔으면 하거든요 ?

    • Favicon of https://jeong-pro.tistory.com BlogIcon JEONG_AMATEUR 2020.12.06 21:29 신고

      맞는 말씀입니다.
      요구 사항에 맞는 커스터마이징은 들어가야될 것 같습니다.
      답글이 수십개가 달렸을 때의 페이징 처리 등 다양한 경우를 요구에 맞게 생각해볼 필요가 있겠네욥