본 포스팅은 2024년 04월 23일을 기준으로 작성되었습니다.
Pagination은 콘텐츠를 여러 페이지 별로 나누어 이전 또는 다음 페이지로 이동하거나 특정 페이지로 이동할 수 있는 기능입니다.
스크린에 출력된 검색 결과에서 스크롤하여 다음 내용을 불러오기 보다는 다음 페이지로 넘어갑니다.
Elasticsearch는 pagination 기능을 여러가지 방법으로 제공합니다.
from ~ size 방법 : from으로 몇 번째 document부터 가져올 것인지, size로 한 페이지 당 얼마 만큼의 document를 가져올 것인지 명확하게 정의할 수 있는 pagination 기능입니다.
Scroll API 방법: 특정 시간동안 데이터를 실시간으로 스크롤하고, 스크롤한 만큼의 데이터 검색 결과를 반환해주는 pagination 기능입니다.
Search_after + PIT 방법 : 특정 시점을 페이징의 기준으로 설정하고, 해당 시점 다음 페이지를 검색 결과로 반환하는 pagingation 기능입니다.
종합적으로, 최신 버전의 Elasticsearch의 경우 Search_after + PIT를 활용한 pagination 기능을 사용하는 것을 권장합니다.
Elasticsearch에서 search API를 통한 검색은 따로 설정이 없으면 상위 10개의 결과를 반환해줍니다.
더 많은 검색 결과를 얻고 싶으면 search API의 from, size 파라미터를 설정하면 됩니다.
from: 출력을 시작할 document의 시점(default: 0)
size: 한번에 출력할 document의 수
2번째(from: 1) document부터 3개까지(size:3)의 document가 반환되었습니다.
이렇게 size로 page의 간격을 설정해주고, from으로 시작점을 설정해줌으로 원하는 페이지의 결과를 반환 받을 수 있습니다.
<from ~ size의 한계>
index.max_result_window 옵션으로 한번에 반환 받을 결과의 수를 지정할 수 있으며, 최대 10,000건의 결과를 반환 가능합니다. 10,000건 이상의 검색은 search_after 파라미터를 사용해야 합니다.
또한, from~size를 통해 너무 많은 검색결과를 요청하는 것은 샤드의 리소스 과부하를 야기하며 성능 저하나 오류 발생 가능성이 있습니다.
scroll API는 보다 대량의 데이터를 검색하기 위한 방법입니다.
특정 시간동안 데이터를 실시간으로 스크롤하고, 스크롤한 만큼의 데이터 검색 결과를 반환해줍니다.
scroll 유지 시간을 설정합니다.
여기서는 1분으로 설정하였습니다.
에서 반환받은 scroll_id를 search request에 포함시킵니다.
해당 스크롤 시간동안의 검색 결과를 출력하였습니다.
최신 버전의 Elasticsearch의 경우 scroll을 활용한 페이지네이션은 권장하지 않습니다.
10,000개 이상의 hits를 페이징하는 동안 인덱스 상태를 유지해야 하는 경우 Search_after와 PIT를 통한 방법을 권장드립니다.
search_after 파라미터를 통해 search API로 검색 완료된 부분의 다음 페이지 검색을 이어서 진행 가능합니다.
검색 결과 가장 마지막에 반환된 document의 sort 반환값을 search_after 안에 넣으면, 그 다음 document부터 이어서 검색이 됩니다.
기존 search API에서 sort로 정렬 기준을 설정합니다.
검색 결과의 가장 마지막 document의 sort 반환값을 복사합니다.
search API에 복사한 sort 반환값을 search_after 파라미터 안에 입력합니다.
이렇게 하면 해당 sort 반환값을 가진 document의 다음 document부터 검색 결과를 반환합니다.
<search_after의 한계>
새 페이지의 결과를 검색할 때마다 search_after 배열을 업데이트하여 위 과정을 반복합니다.
그러나, 이 search request를 요청한 사이에 인덱스에 refresh(업데이트)가 발생하면 결과의 순서가 변경되어 search_after로 나누어진 페이지들이 일관된 순서로 이어지지 않을 수 있습니다.
위 문제를 PIT 파라미터와 병행하여 해결 가능합니다.
PIT 사용시 search request는 인덱스의 최신 데이터에 대해서 실행되며, 이때 검색되는 순간의 시점을 페이징의 기준으로 설정합니다. PIT는 해당 특정 시점에서의 해당 인덱스의 데이터를 보여주는 view입니다.
_shard_doc : 타이브레이킹(동점자 순위 결정) 필드
PIT는 기본적으로 정렬 시 스코어가 동점인 값들에 대해 우선순위를 처리해주는 _shard_doc 필드를 자동으로 포함하고 있습니다. 이러한 순위 결정 필드가 없는 경우 페이징된 결과가 누락되거나 중복될 수 있으므로, PIT를 사용하지 않는 경우에도 동점 스코어의 순위 결정(tiebreaker)을 해주는 필드를 포함하는 것을 권장합니다.
keep_alive: 특정 시점을 얼마나 오랫동안 유지할지 설정 (1m로 설정하면, 1분이 지난 상태에서 해당 id는 만료됩니다.)
keep_alive를 1분으로 설정하고 실행합니다.
실행 시점에서의 PIT_id가 반환되었습니다.
위에서 얻은 PIT id를 search API의 pit 파라미터 안에 포함시킵니다.
또한, sort에 _shard_doc 으로 타이브레이킹 기준을 추가해줍니다. (기본 설정이므로 꼭 안 넣어도 됩니다.)
해당 pit_id 시점에 대한 검색 결과가 반환 되었습니다.
sort값에 timestamp에 대한 정렬 기준이 반환 되었고, 그 밑에 _shard_doc으로 생성된 동일 timestamp에 대한 추가적인 우선 순위 기준값이 반환되었습니다.
<주의점>
pit로 검색할 때는 인덱스를 지정하면 에러가 발생합니다. 위 예시처럼 인덱스 없이 GET /_search로 설정해야 합니다.
Search_after의 단점을 보완하기 위해 PIT와 함께 사용합니다.
PIT로 검색 시점을 명확하게 설정하여, 중간에 인덱스에 업데이트(refresh)가 발생하더라도 현재 인덱스의 상황을 유지하면서 Search_after로 검색이 완료된 부분의 다음 검색을 이어서 진행할 수 있습니다.
앞에서 설정했던 PIT, sort, search_after 설정을 모두 추가합니다. “track_total_hits”: false 설정을 통해 pagination 속도 성능을 향상시킵니다.
앞에서는 Search_after + PIT을 활용한 pagination 방법에 대해 다루었습니다.
이제는 많은 양의 데이터를 가진 인덱스에 대해 pagination 검색을 수행할 때에는 검색 속도의 향상을 위해 여러 조각으로 슬라이싱하여 검색에 도움이 되는 방법에 대해 추가적으로 설명하겠습니다.
id: 반환할 검색 결과의 슬라이스 id입니다. id: 0은 첫 번째 슬라이스의 검색 결과를, id: 1은 두 번째 슬라이스의 검색 결과를 반환합니다.
max: 2 이므로 id:0, id:1 두개의 슬라이스의 요청 합은 전체에 대한 pit 검색 결과와 동일합니다.
@이동훈(Donghoon Lee), @이상엽(Sangyup Lee)