Elasticsearch에서 search_after 기능 사용하여 조회하기

elasticsearch에서 search_after를 이용하여 데이터를 조회하는 방법을 정리해보자.

우선 사용할 인덱스를 생성하자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PUT wedul
{
  "mappings": {
    "cjung": {
      "properties": {
        "id": {
          "type": "keyword"
        },
        "name": {
          "type": "text",
          "analyzer": "nori",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        }
      }
    }
  }
}
cs


생성된 인덱스에 데이터 몇개만 삽입하여보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST wedul/cjung?pretty { "id": "wemakeprice", "name": "원더쇼핑" } POST wedul/cjung { "id": "dauns", "name": "다운" }
cs


그리고 일반적으로 사용하는 방식으로 데이터를 조회해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
GET wedul/cjung/_search
{
  "from": 0, 
  "size": 2, 
  "query": {
    "match_all": {}
  }
}
 
 
{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "wedul",
        "_type" : "cjung",
        "_id" : "_update",
        "_score" : 1.0,
        "_source" : {
          "doc" : {
            "id" : "wedul",
            "name" : "정철"
          }
        }
      },
      {
        "_index" : "wedul",
        "_type" : "cjung",
        "_id" : "tSNYH2cBvWxWFgHQJ6J4",
        "_score" : 1.0,
        "_source" : {
          "doc" : {
            "id" : "dauns",
            "name" : "다운"
          }
        }
      }
    ]
  }
}
 
cs

정상적으로 조회가 된다. 하지만 여기서 만약 size가 10000이 넘은 곳을 검색하고 싶다면 어떻게 될까? 저번에 공부해서 정리한 글 처럼 10000개 이상에 데이터에 접근하려고 하면 오류가 발생한다.

참고 : https://wedul.tistory.com/518?category=680504


그럼 어떻게 조회해야 할까? 그래서 제공되는 방법이 search_after를 이용하여 검색하는 방법이다.

search_after는 라이브 커서를 제공하여 다음 페이지를 계속 조회하는 방식으로 검색기능을 제공한다. 


기본적인 검색 방법은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET wedul/cjung/_search
{
  "sort": [
    {
      "id": {
        "order": "asc"
      },"name.keyword": {
        "order": "desc"
      }
    }
  ], 
  "size": 1, 
  "query": {
    "match_all": {}
  }
}
cs


이렇게 검색을하게 되면 다음과 같이 결과가 나오는데 여기서 나온 sort  필드를 이용하여 다음 필드를 조회해 나가는 것이 search_after 기능이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : null,
    "hits" : [
      {
        "_index" : "wedul",
        "_type" : "cjung",
        "_id" : "uyNoH2cBvWxWFgHQ86L9",
        "_score" : null,
        "_source" : {
          "id" : "wemakeprice",
          "name" : "원더쇼핑"
        },
        "sort" : [
          "wemakeprice",
          "원더쇼핑"
        ]
      }
    ]
  }
}
 
cs


위에 나온 검색결과 sort에 출력된 wemakeprice와 원더쇼핑을 사용하여 다음 데이터를 조회한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET wedul/cjung/_search
{
  "search_after": ["wemakeprice",
          "원더쇼핑"],
  "sort": [
    {
      "id": {
        "order": "asc"
      },"name.keyword": {
        "order": "desc"
      }
    }
  ], 
  "query": {
    "match_all": {}
  }
}
cs


그렇다면 저 위에 sort 필드는 과연 무엇인가 하고 생각이 들 수 있다. sort 필드는 바로 검색 dsl에서 사용했던 sort필드의 값 들이다. 이 값 다음에 나오는 데이터를 조회 하라는 뜻이다. 그렇기 때문에 무조건 search_after를 사용하기 위해서는 데이터를 정렬하는것이 필수이다. 그리고 정말 중요한 것은 그 sort필드에 들어가는 데이터중 하나는 무조건 unique한 값 이어야 한다는 것이다. 그렇지 않으면 어디서 부터 검색을 시작해야할지 알지 못하기 때문이다. 


참고

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

댓글()

Elasticsearch에서 Paging시 max_result_window 초과시 조회가 안되는 이슈

엘라스틱 서치에서 데이터를 paging 하여 조회할때 from과 size를 사용한다. 

from은 시작 지점을 이야기하고 size는 그 시작 지점으로 부터 몇 개의 데이터를 보여주어야 하는 건지 설정할 때 사용 되는 값이다. 그래서 계산 방법은 다음과 같다.

from : (page - 1) * size 

size : size


그럼  만약 3개씩 보여주는 페이지에서 2번째 페이지를 보여주기 위해서는 from은 3, size는 3으로 설정하면 된다.

1
2
3
4
5
6
7
8
9
GET wedul/_search
{
  "from": 3, 
  "size": 3, 
  "query": {
    "match_all": {}
  }
}
 
cs


그럼 만약 wedul 페이지를 접근하다가 다음과 같이 Document의 숫자가 10000을 넘어가게 되면 어떻게 될까? 쿼리를 사용해서 조회를 해보자.

1
2
3
4
5
6
7
8
9
GET wedul/_search
{
  "from": 9999, 
  "size": 3, 
  "query": {
    "match_all": {}
  }
}
 
cs


정상적인 결과가 나오지 않고 query_phase_execution_exception에러가 발생하고 다음과 같은 에러문구를 출력한다.

1
Result window is too large, from + size must be less than or equal to: [10000] but was [10002]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting.
cs


이유가 몰까? 이유를 잘 몰라서 엘라스틱 서치 공식홈페이지 문서를 뒤져봤다. 

엘라스틱서치에서 인덱스를 생성할 때 기본적으로 하나의 노드에 5 섯개의 shard가 생성되고 그 shard는 데이터를 나누어서 저장한다. 물론 그 데이터를 복제하고 있는 primary shard도 1로 설정되는데 이는 primary shard가 한개라는 뜻이 아니라 각 shard 마다 복제 shard가 한 개씩 존재한다는 의미이다, 

다시 본점으로 돌아가서 노드에 분리되어있는 파티션 shard들에 데이터가 분산되어 들어간다. 그래서 만약 5섯개의 shard가 있는 노드에 위치한 index에서 1 ~ 10 개의 데이터를 찾는다면 각 shard에서 10개의 데이터를 찾고 모아서 정렬작업을 한 후 50개의 데이터에서 1 ~ 10까지의 데이터를  반환한다.

그럼 10000개의 데이터라면? 각 shard에서 10000개의 데이터를 가져와서 모으고 그것을 정렬할 것이다. 그래서 총 50000개 이상의 데이터를 모아야 하고 그것을 정렬해야하기 때문에 성능적인 문제를 야기할 수 있다. 그래서 엘라스틱서치의 기본 검색 제한 document의 값은 10000이고 이 설정값 이름은 max_result_window이다.

이 값은 아래의 쿼리를 사용해서 원하는 대로 50000까지 설정할 수 있다. 하지만 근본적으로 10000을 넘게 조회하게되면 많은 리소스 사용으로 성능문제를 야기할 수 있기 때문에 함부로 설정값을 바꿀것이아니라 검색을 10000개가 한번에 되지 않도록 검색조건을 잘 분할해서 지정해야한다.

1
2
3
4
PUT your_index_name/_settings
  "max_result_window" : 500000 
}
cs


참조 

https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html

https://www.elastic.co/guide/en/elasticsearch/guide/current/_fetch_phase.html

댓글()