ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 21. Search After for JAVA (10,000건 이상 조회)
    BackEnd/elasticsearch 2021. 12. 30. 20:43
    반응형

      엘라스틱서치의 페이징 처리는 from과 size를 사용하여 구현할 수 있습니다. 그러나, 검색 요청은 from + size에 비례하여 힙 메모리와 시간이 들기에 기본값을 10,000건으로 제한하고 있습니다(index.max_result_window). Scroll api 사용이 가능하지만 스크롤 컨텍스트 비용이 많이 들기에 실시간 사용자 요청에는 Search After를 사용합니다. Search After는 많은 쿼리를 병렬로 스크롤하는 솔루션으로, 이전 페이지의 결과를 사용하여 다음 페이지를 조회합니다.

     

      문서의 고유한 값이 있는 필드를 순위 결정자로 사용해야 합니다. 그렇지 않으면 정렬 순서가 정의되지 않아 결과가 누락되거나 중복될 수 있습니다. Search After는 순위 결정자가 제공한 값과 완전히 또는 부분적으로 일치하는 첫 번째 문서를 찾습니다.

    GET es_test/_search
    {
      "query": {
        "query_string": {
          "default_field": "Name",
          "query": "*엘라스틱서치*"
        }
      },
      "sort": [
        {
          "ID": {
            "order": "asc"
          }
        }
      ]
    }

      위의 코드는 es_test 인덱스에서 Name 필드에 "엘라스틱서치"가 포함된 문서(Like 검색)를 ID로 정렬합니다. 결과에는 정렬 값 배열이 포함되며 이러한 정렬 값을 search_after 매개변수와 함께 사용하여 결과 목록의 문서 "뒤에" 결과 반환을 시작할 수 있습니다. 예를 들어 마지막 문서의 정렬 값(10)을 search_after에 전달하여 다음 결과 페이지를 검색할 수 있습니다.

    GET es_test/_search
    {
      "query": {
        "query_string": {
          "default_field": "Name",
          "query": "*엘라스틱서치*"
        }
      },
      "search_after": ["10"],
      "sort": [
        {
          "ID": {
            "order": "asc"
          }
        }
      ]
    }

     

      클라이언트 코드는 아래와 같습니다.

            // 10,000건 이상 조회를 위해 searchAfter를 적용합니다.
            Object[] searchAfter = null;
            String SEARCH_FIELD_NAME = "Name";
            String RESULT_FIELD_NAME = "ID";
    
            // Like 검색을 위한 queryString
            String QUERY_KEYWORD = "*" + Name + "*";
    
            List<Long> result = new ArrayList<>();
    
            while(true) {
                SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
                    .size(1000)
                    .query(QueryBuilders.queryStringQuery(QUERY_KEYWORD).field(SEARCH_FIELD_NAME))
                    .sort(SortBuilders.fieldSort(RESULT_FIELD_NAME)) // default ASC
                    .fetchSource(RESULT_FIELD_NAME, null)
                    .timeout(new TimeValue(60, TimeUnit.SECONDS));
    
                // searchAfter: 다음 페이지의 검색에 이전 페이지의 결과(sort values)를 사용합니다.
                if(searchAfter != null) sourceBuilder.searchAfter(searchAfter);
    
                SearchRequest searchRequest = new SearchRequest("es_test").source(sourceBuilder);
                SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    
                // result
                SearchHit[] hits = searchResponse.getHits().getHits();
    
                for(SearchHit hit : hits) {
                    Map<String, Object> map = hit.getSourceAsMap();
    
                    Number id = (Number)map.get("ID");
                    if(id != null) result.add(id.longValue());
                }
    
                if(hits.length > 0) {
                    // searchAfter: 마지막 Document의 sort values를 설정합니다.
                    SearchHit lastHitDocument = hits[hits.length - 1];
                    searchAfter = lastHitDocument.getSortValues();
                } else {
                    // 검색이 없을 경우 EXIT
                    break;
                }
            }
    
            return result;

     

    [참고자료]

    https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-request-search-after.html

    반응형

    댓글

Designed by Tistory.