-
Notifications
You must be signed in to change notification settings - Fork 3
[Problem & Solving] 게시판 페이징 & 캐싱
게시판은 다양한 서비스에서 제공하는 기능입니다. 그만큼 사용자에게 익숙하고, 구현 자체도 복잡하지 않습니다. 하지만 완성도를 높이는 순간 복잡하고 다양한 선택지가 생깁니다. 이 글에서 게시판 기능 구현에서 생긴 고민들과 선택에 대해 다루도록 하겠습니다.
완성된 게시판. 이 구조를 바탕으로 글을 작성하였습니다.
db에서 pagination을 하는 방법 두가지, offset과 cursor가 있습니다. 두 방법 모두 순서가 있는 목록의 일부를 읽는 방법입니다. 둘다 다음 페이지의 데이터를 찾기 위해서 사용하며, 그 방식에 차이가 있습니다.
offset 방식은 페이지 번호를 사용해 몇번째 데이터인지 찾는 방식입니다. (페이지 번호) * (한 페이지 데이터 개수) 의 다음 데이터를 찾습니다. 첫번째 데이터부터 개수를 count 하기 때문에 데이터가 뒤에 있을수록 속도가 느려지는 단점이 있습니다.
cursor 방식은 현재 데이터의 다음 데이터를 찾는 방식입니다. 정렬 가능한 기준(id, timestamp)으로 현재 데이터보다 크거나 작은 데이터를 찾습니다. db의 인덱싱으로 많은 데이터 속에서도 찾는 속도가 빠릅니다.
설명에서 알수있듯이 일반적으로 cursor 기반이 빠르고, 그래서 추천합니다. 하지만 목표로 하는 게시판처럼 다음, 이전 페이지가 아닌, n페이지에 접근하는 상황에서는 부적합합니다. 다음 표처럼 현재 위치에 대한 정보, 즉 지금까지 몇개의 데이터를 넘어왔는지를 알 수 없기 때문입니다.
offset - page (data) | cursor - cursor-position |
---|---|
1 (1, 2, 3) | cursor-1, cursor-2, cursor-3 |
2 (4, 5, 6) | cursor+0, cursor+1, cursor+2 |
3 (7, 8, 9) | cursor+3, cursor+4, cursor+5 |
… | … |
그리고 일반적으로 사용자는 주로 게시판의 최신 데이터에 접근하기 때문에 offset의 단점도 일정부분 감소하게 됩니다. 참고로 실제 검색 서비스에서 query string을 보면 구글은 start=20, 네이버는 page=2 으로 offset 을 사용합니다.
이렇게 분석한 결과 offset을 적용하기로 했습니다.
게시판 구현시 고려사항으로 페이지 존재여부가 있습니다. 페이지의 존재 여부에 따라서 브라우저 화면에 표시할 페이지 버튼이 달라지기 때문입니다. (먼저 페이지당 최대 버튼 수를 채워놓고 버튼 클릭 시 해당하는 페이지가 없을 경우 따로 처리하는 방법도 있습니다.)
다음 페이지의 존재 여부를 확인하려면 역시 db에서 count를 해야합니다. (한번에 보여주는 페이지 버튼 수) * (한 페이지의 데이터 수) 만큼 count 하여 표시할 버튼의 수를 계산할 수 있습니다.
이렇게 다음 페이지 버튼을 위한 처리를 적용했습니다.
게시판 기능의 주요 관점, pagination과 다음 페이지 확인을 알아보았습니다. 그런데 아직 의문이 남아있습니다. offset의 단점은 여전히 부담스럽고, 페이지 체크에 매번 count 하는것도 아깝습니다. 이것을 해결하기 위해 redis cache 를 적용하기로 하였습니다.
cache는 데이터를 미리 저장해놓고 사용하는 것입니다. 상대적으로 속도가 빠른 하드웨어(cache > ram > disk)에 데이터를 저장합니다. 하드웨어는 속도가 빠를수록 비싸기 때문에 자주 사용되는 데이터만 caching 합니다. 여기서는 disk(db)의 데이터를 ram(redis)에 caching하고 있습니다.
그렇다면 어떤 데이터를 caching해야 할까요? cache hit할 확률이 높은 데이터, 즉 자주 사용할 데이터를 caching 하도록 해야합니다.
사용한 데이터와 가까운 데이터가 순서대로 상용하는 경우입니다. 사용할 데이터의 주변 데이터까지 미리 caching하여 속도를 향상시킬 수 있습니다.
게시판에서는 현재 페이지와 가까운 페이지들이 이에 해당한다고 판단하였습니다. 보통 사용자는 게시판에서 가까운 페이지를 조회합니다. 그렇다면 1 페이지에 접근 시 2~10 페이지까지 함께 caching 하면 됩니다.
하지만 몇가지 문제로 최종적으로 적용은 하지 않은채, 사용한 페이지만 caching 하고 있습니다. 우선 몇 페이지 까지가 함께 caching 하기 적절한가? 라는 문제입니다.
이는 좀 더 실험적으로 얻어야 의미있는 답을 얻을 수 있어보인다 판단했습니다. 또 한가지 문제는 실제로 빨라지는가? 입니다. 게시판의 한 페이지에 5개의 데이터를 가지고 있고, 하나의 데이터는 객체입니다. 따라서 하드웨어상에서 물리적으로 가까운 특성은 없습니다.
그렇다면 성능의 향상은 n개의 페이지 caching 하기 위해서 db에 접근한 횟수의 차이만 남습니다. db에 n번 접근하는가, 아니면 한번 1번 대량으로 읽고, n개로 나누어 caching 하는가 입니다. 하지만 사실 caching한 데이터가 n개 모두 반드시 사용되지는 않기 때문에 (n - m(접근 안하는 페이지 수))번 vs 1번에 n페이지 전부 읽기가 됩니다.
사용한 데이터가 가까운 시간 안에 다시 사용되는 경우입니다.
게시판에서는 우선 제목 검색을 했을 경우 caching을 하지 않도록 했습니다. 사람마다 검색 키워드는 겹치는 경우가 적기 때문에 다시 사용될 확률이 낮다 판단했습니다. 그래서 카테고리, 정렬 등의 기준에 따라서만 caching을 하고 있습니다. 특히 기본 페이지는 모든 사용자가 처음 접근하는 페이지이기 때문에 hit율이 높습니다.
그리고 다음 페이지 존재 여부를 caching 하고 있습니다. 일반적으로 사용자는 게시판에서 한번 검색을 하면, 바로 다음 검색으로 넘어가지 않고 (1, 2, 5, …, n)처럼 임의로 페이지를 이동합니다.
그때마다 (한번에 보여주는 페이지 버튼 수) * (한 페이지의 데이터 수)를 검색하는 것은 비효율적입니다. 그래서 공통으로 사용할 count 정보를 caching하여 사용했습니다.
여기까지의 caching 방식을 들어보면 의문이 생기게 됩니다. 페이지 데이터를 caching 하면, 새로운 데이터가 추가되었을때 반영하지 못하는 점입니다. 실제로 즉각적으로 반영하지는 못합니다. 하지만 이건 인지하고, 허용범위 내로 판단하여 구현한 것입니다.
왜 괜찮은가? 를 설명하려면 우선 게시판과 데이터의 특성에 대해 이야기 해야합니다. 우선 여기서 데이터는 설문조사 응시 요청 설문지 목록입니다. 이 데이터들은 실시간성을 필요로 하지 않습니다.
실시간 데이터, 처리 속도 두가지 중에서 caching으로 속도를 높이는 것이 지금 상황에 적합하다 판단하였습니다.
그렇다면 새로운 데이터는 어떻게 반영할까요? 만료기간을 설정하여 해결합니다. caching한 데이터에 만료기간을 설정하고 기간이 지나면 caching 데이터가 없어집니다. 이후 다시 같은 key로 접근하면 업데이트된 데이터가 caching 됩니다.
여기서 한가지, cache hit 시 만료기간을 재설정하는 cache 사용방법이 있습니다. 자주 사용하는 데이터를 cache에 유지시키는 좋은 방법이지만, 여기서는 지속적으로 구버전 데이터만 사용하는 문제가 생기므로 적용하지 않았습니다.