pagination / paging / page / offset
elasticsearch 에서 paginate (v6, v7)
from, size
1만개 이하의 hits 라면 from
, size
을 이용하면 된다.
GET /_search
{
"from": 5,
"size": 20,
"query": {
"match": {
"user.id": "kimchy"
}
}
}
그런데 너무 깊게, 또는 한번에 많은 결과를 가져올 것이라면, 이녀석을 사용하지 말라고 한다.
이유는 다음과 같다.
Search request 들은 일반적으로 여러 shard 들에 걸쳐있는데, 각 shard 는 “요청받은 hit들” 과 “이전 모든 page들의 hit들”(any previous pages) 메모리에 load해야만 한다. “deep page” 들과 “많은양의 결과” 에게 이 작업들은 memory 와 cpu 사용을 현저하게 높여서 성능저하 또는 node failure 를 일으키게 된다.[ref. 1]
그래서 기본 설정은 10,000 hit들 보다 많은 hits 를 paginate 할 것이라면, from
, size
를 사용할 수 없다. 이 값은 index.max_result_window index setting 에 의해 설정된다.[ref. 1]
그래서 1만개 이상의 hits 를 paging 하려면, search_after parameter 를 사용하라고 한다.
scroll 도 있지만, v7에선 search_after 를 사용하는 것을 권장하는 듯 하다.(참고)
version 6 에서는 아직 search_after에 PIT 가 없기 때문에, scroll 을 사용하는 것이 나은 듯 하다.
- Scroll | Elasticsearch Guide [6.7] | Elastic)
- Elasticsearch Pagination Techniques: SearchAfter, Scroll, Pagination & PIT
명확하진 않지만, “1만개의 hits” 를 확인할 때는 query 를 했을때 나오는 결과값에 보면, hits.total 값이 있는데, 이 값을 보고 1만 hits 가 넘는 경우에는 사용하지 않으면 될 것 같다.
search after
_shard_doc
PIT 는 version 7 부터 들어갔다.
- 모든 PIT search request 에는 sort tiebreaker field 인
_shard_doc
라는 field 가 들어가게 된다. 이 tiebreaker field 는 각 document 마다 unique 한 값을 가져야만 한다. 이 tiebreaker field 가 없으면 paged result 는 hits 를 빼먹거나, 중복된 hits 를 보여줄 수 있다. - sort 할 때 document 마다 있는 unique 값이 사용되어져야 한다. 그렇지 않으면, paged result 의 값이 중복되거나, 빠지거나 한다.[ref. 2, v6 의 document]
_id
가 unique 한 field 이지만, 이것을 사용하는 것은 추천하지 않는다. _id field 에 대해서 doc value 가 disabled 되어 있기 때문이다. 그래서 version 6 에서는 _id field 의 복사본을 사용하는 방법을 취한다. [ref. 2, v6 의 document]- ref. 3 에 따르면, version 6에서는 _id 를 대신할만한 unique value 를 직접 넣어줘야 하는 것 같다. 글의 느낌은 document 에 아예 기록해 놓으라는 느낌이다.
search_after
가 완전하게 또는 부분적으로 tiebreaker 값과 match 되는 첫번째 document 를 찾는다.- search after request 들은 sort order 가
_shard_doc
이면서 total hits 가 추적되지 않을때 더 빠르게 최적화 되어 있다. 순서에 상관없이 모든 documents 를 iterate 하길 원한다면, 이것이 가장 효과적이다.
- pit id 를 얻어온다.
- pit 를 넣고, query
POST /my-index-000001/_pit?keep_alive=1m
GET my-index-000001/_search
{
"size": 10000,
"query": {
"match" : {
"user.id" : "elkbee"
}
},
"pit": {
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
"keep_alive": "1m"
},
"sort": [
{"@timestamp": {"order": "asc", "format": "strict_date_optional_time_nanos", "numeric_type" : "date_nanos" }}
]
}
GET my-index-000001/_search
{
"size": 10,
"query": {
"match" : {
"title" : "elasticsearch"
}
},
"sort": [
{"date": "asc"},
{"tie_breaker_id": "asc"}
]
}
scroll
pagination 을 위한 함수는 아닌듯 하다. 그저 많은 page 를 불러오려할 때 유용해 보인다. 그래서 v6에서 거대한 page에 대한 pagination 을 하려면, sort, 와 page 의 구분은 다른 query 조건을 줘서 스스로 해야 할 듯 보인다. search_after 도 존재는 하지만, v7에 비하면 불완전해 보인다.
code example
- Scroll | Elasticsearch JavaScript Client [7.16] | Elastic
- Scroll | Elasticsearch Node.js client [6.x] | Elastic
- clear Scroll | API Reference | Elasticsearch JavaScript Client [7.16] | Elastic
const client = new Client({ node: 'http://3.112.194.162:9200' })
const result = await client.search({
index: 'my-index-001',
// keep the search results "scrollable" for 30 seconds
scroll: '30s',
size: 10,
// filter the source to only include the quote field
_source: ['s.d'],
body: {
"query": {
"bool": {
"must": [
{
"match": {
"myid": "3220"
}
},
{
"range": {
"s.date": {
"gte": 1627776000000, // 2021-08-01 GMT
"lt": 1630454400000, // 2021-09-01 GMT
"format": "epoch_millis"
}
}
}
],
"filter": [],
"should": [],
"must_not": []
}
}
}
})
if (result.statusCode != 200) {
console.error(result)
return
}
result.body.hits.hits.map((val, i)=>{
console.log(JSON.stringify(val))
})
// scroll next
const scrollId = result.body._scroll_id
let totalReqHitCount = 0
while(true){
const res2 = await client.scroll({
scroll_id: scrollId,
scroll: '30s'
})
if(res2.statusCode != 200){
console.error(JSON.stringify(res2))
break
}
this._doSomething(res2)
const hits = res2.body.hits
totalReqHitCount += hits.hits.length
if (hits.total === totalReqHitCount) {
break
}
}
client.clearScroll({
scroll_id: result.body._scroll_id,
})
결과:
'{"_index":"my-index-001","_type":"as","_id":"478274297bf453483e792499fd6ad4a0ad9c1b11::f2b8577cfc7c41bdb18ec2b57c881676","_score":2,"_source":{"s":{"date":1627782547273}}}'
'{"_index":"uh-as-202108","_type":"as","_id":"b496b241b720d98687de9df760ec497245fbc7b3::230f53df51aa4617a88b76f573e73a1a","_score":2,"_source":{"s":{"date":1627928148753}}}'
sort _doc
search after 의 _shard_doc
같은 느낌이다. 순서에 상관없이 빠르게 가져올 때 좋다고 한다.
GET /_search?scroll=1m
{
"sort": [
"_doc"
]
}
댓글 없음:
댓글 쓰기