개요
엘라스틱서치를 사용하는 중 특정 문서의 prev, next로 넘어가는 기능을 만들어야 했다. 내 경우 구분자가 +-1해서 생성한게 아니라서 단순히 다음 구분자를 예상하는 구현이 불가능하다.
더군다나 정렬 상태를 기준으로 prev, next가 바뀐다는 점이 허들을 높였다. 조회순으로 보고있다면 next 버튼을 눌렀을 때 조회순으로 정렬된 문서 기준으로 동작해야 한다는 것이다.
여기저기 찾으면서 알아보니 보통 search_after를 이용하는 것 같았고, 나도 이방법을 사용하기로 했다.
정렬 정보와 현재 문서를 서버가 받고, 그 정보를 기준으로 prev와 next를 찾는 방식이다.
구현
우선 기준이 되는 문서가 필요하다. 문서는 유저가 보고있는 문서이고, prev와 next의 기준이 된다.
생략할 부분 생략하고 핵심만 남기면 대략 아래와 같은 쿼리다
"query": {
"bool": {
"should": [
{검색조건1},
{검색조건2},
],
"must":[{
"match": {
"구분자 필드": {
"query": 구분자 값,
"boost": 0
}
}
}]
}
}
- 굳이 should와 must를 둘 다 사용한 이유는 서로간 목적이 달라서이다.
- should는 검색조건에 대응한다. 조건에 대응하는 'score'를 재현하는 목적이다. 여러 조건중 하나로 검색되었을 때와 처음부터 특정 문서를 찾기위해 쿼리를 날렸을 때는 score가 다르다.
- must는 문서를 정확히 찾기 위한 구분자이다. 여러 리스트 -> 상세보기를 누르면 구분자를 통해 문서를 찾으므로 score가 굉장히 높아진다. score 변동은 원하는 상황이 아니므로 boost: 0을 줘서 score에 간섭하지 못하게 했다.
- 정렬옵션은 클라이언트에서 서버로 전해진 상황이기에 이후 prev, next 찾을 때 사용한다.
next는 위 쿼리와 비슷하다. 다른 점은 "sort"와 "search_after"가 붙었다는 정도이다. "search_after"에는 정렬 기준의 값이 들어간다.
가령 정렬기준이 name 필드이면 현재문서의 name 필드의 값을 입력하면 된다.
"query": {
"bool": {
"should": [
{검색조건1},
{검색조건2},
],
"must":[{
"match": {
"구분자 필드": {
"query": 구분자 값,
"boost": 0
}
}
}]
}
},
"sort": 정렬 옵션들,
"search_after":[
정렬 옵션에 따른 현재 문서의 값
]
- 정렬옵션이 중요하다. 정렬옵션의 순서는 곧 우선 순위이다. 나와 같은 경우는 리스트를 우선 반환하고 상세정보 보기로 연결되기에 문서리스트를 뽑을 때 이미 "sort"옵션을 쓴 상태이다. 물론 정렬기준은 동일해야 한다.
- 일단 가장 첫번째 정렬옵션을 통해 정렬되고, 정렬 대상의 값이 같으면 두번째 정렬옵션을 통해 정렬된다.
- 1.생성일자, 2.이름 순이라면 -> 생성일자 별로 정렬되다가 생성일자가 같은 문서가 있으면 이름순으로 정렬된다는 뜻이다.
- "search_after"에 입력된 문서 이후 부터 반환된다. 즉, next 문서를 얻을 수 있다.
prev는 조금 번거롭다. "search_after"는 찾았는데 "search_before" 같은 것은 못찾았기 때문에 문서 정렬 기준을 아예 거꾸로 돌려야 했다.
"query": {
"bool": {
"should": [
{검색조건1},
{검색조건2},
],
"must":[{
"match": {
"구분자 필드": {
"query": 구분자 값,
"boost": 0
}
}
}]
}
},
"sort": 거꾸로 설정된 정렬 옵션들,
"search_after":[
정렬 옵션에 따른 현재 문서의 값
]
- "sort"의 정렬기준을 바꾸어야 한다. 정렬 옵션의 우선순위는 같은데 정렬의 "DESC"는 "ASC"로, "ASC"는 "DESC"로 바꿔준다.
- 역순은 정순으로, 정순은 역순으로 바꿔야 같은 검색조건 중 문서를 거꾸로 정렬한 것과 같은 효과를 본다.
마무리
이렇게 하면 현재문서, 이전문서, 다음문서를 얻을 수 있다. 어쨌건 구현은 했지만 최적화가 계속 신경쓰인다. 단순하게 생각해도 사용자는 prev, next를 사용할지 말지조차 불투명한데 문서를 찾는 쿼리를 3번 날려야한다.
더 좋은 방법은 뭐가 있을까?
아쉬움
글로 정리하다가 문뜩 떠오른 것이 있다. 굳이 미리 문서를 읽을 필요가 없다는 점이다. prev, next에 필요한건 현재 문서의 데이터인데 이 데이터는 클라이언트 이벤트가 발생했을 때 전달해줘도 충분히 대응할 수 있다.
현재문서의 데이터, 정렬 값, 원하는 문서를 전부 다 전해줄수 있는데 굳이 prev, next 문서의 구분자를 동시에 보내줄 필요가 없다. 한걸음 떨어져서 보니 이제야 보인다.