Lined Notebook

검색과 쿼리 - Query DSL (2)

by HeshAlgo

Elasticsearch의 검색 기능에 대한 전반적인 내용을 해당 게시글을 통해 정리해보겠습니다.

품질이 높은 검색 기능을 구현하기 위해서는 여러 가지 고려해야 될 부분들이 많다고 생각합니다.

예를 들어 "나이키"라는 검색어로 검색을 했다고 가정하였을때, "나이키"란 검색어만 보여줄 것인지 "나이키 신발", "나이키 매장" 등과 같이 "나이키"란 검색어가 포함된 검색 결과를 보여줄 것인지 검색엔진의 설정에 따라 여러 가지의 검색 결과를 나타내 줄 것입니다.

 

"나이키"란 키워드를 검색 했을때 "나이키 신발", "나이키 매장", "나이키 옷" 등과 같이 관련된 검색을 원한다면 여러 가지 정보를 볼 수 있어서 좋긴 하겠지만 반대로, 검색 결과가 너무 많은 탓에 내가 정확히 얻어내고 싶은 상품을 찾기는 힘들 것이란 단점이 있을 수 있습니다.

그렇기 때문에 품질이 높은 검색 시스템을 구현하기 위해서는 이렇게 많은 부분들을 고민해야 합니다.

 

Elasticsearch는 사용자가 이런 여러가지 검색 조건들에 대해 목표로 하는 검색 기능을 구현할 수 있도록 다양한 기능들을 제공합니다.

특히 데이터를 실제로 검색에 사용되는 검색어인 텀(Term)으로 분석과정을 거쳐 저장하기 때문에 검색 시 대소문자, 단수, 복수 등의 여부와 상관없이 검색이 가능합니다. 이런 Elasticsearch의 특징을 풀 텍스트 검색(Full Text Search)라고 부릅니다.

그러면 이제 검색기능에 대해 하나씩 살펴보도록 하겠습니다.

 

예제 인덱스들은 참고한 elasticsearch 가이드북을 이용했습니다.

 

1. Full Text Query

1) match_all

해당 인덱스의 모든 도큐먼트를 검색하는 쿼리

GET my_index/_search

검색 시 쿼리를 넣지 않으면 자동으로 match_all을 적용한 것과 동일한 결괏값을 얻을 수 있다.

GET my_index/_search

{
  "query": {
    "match_all": {}
  }
}

 

2) match

해당 인덱스 필드의 내용을 검색할 수 있는 쿼리입니다.

예를 들어 my_index라는 인덱스 중에서 message 필드 값에 dog가 포함되어 있는 모든 문서를 검색합니다.

GET my_index/_search

{
  "query": {
    "match": {

      "message": "dog"

    }
  }
}

match 검색에 여러 개의 검색어를 집어 넣게 되면 default 값으로 OR 조건 검색이 가능하며 operator 옵션을 통해 AND 조건 검색도 가능합니다.

// OR 조건 검색

GET my_index/_search

{
  "query": {
    "match": {

      "message": "quick dog"

    }
  }
}

 

// AND 조건 검색

GET my_index/_search

{
  "query": {
    "match": {

      "message": {

        "query": "quick dog",

        "operator": "and"

      }
    }

  }
}

 

3) match_phrase

"lazy dog"라는 공백을 포함해 정확한 순서를 가진 하나의 단어 내용을 검색하려면 해당 쿼리로 검색이 가능합니다.

GET my_index/_search

{
  "query": {
    "match_phrase": {

      "message": "lazy dog"
    }

  }
}

slop이라는 옵션을 통해 지정된 값 만큼 단어 사이에 다른 검색어가 끼어드는 것을 허용할 수 있습니다.

"lazy dog"라는 검색어에 slop옵션 1 값을 주게 되면 "lazy {단어 1} dog"와 같이 검색 결과를 얻을 수 있습니다.

GET my_index/_search

{
  "query": {
    "match_phrase": {

      "message": {

        "query": "lazy dog",

        "slop": 1

      }
    }

  }
}

이렇게 slop 옵션을 이용한다면 원하는 검색 결과를 넓힐 수 있지만 너무 크게 하면 검색 범위가 넓어져 관련이 없는 결과가 나타날 확률도 높아지기 때문에 1 이상은 사용하지 않는 것을 권장합니다.

 

간단하게 정리하자면 검색어의 순서가 중요한 경우에는 match_phrase 순서에 구애받지 않는 경우에는 match를 사용하면 된다.

 

2. Bool Query

bool을 이용하면 여러 쿼리를 조합해서 사용이 가능하다.

bool 쿼리는 4개의 인자를 가지고 있으며 그 안에 다른 쿼리들을 배열로 넣는 방식으로 동작한다.

- must : 쿼리가 참인 도큐먼트들을 검색

- must_not : 쿼리가 거짓인 도큐먼트들을 검색

- should : 검색 결과 중 이 쿼리에 해당하는 도큐먼트의 점수를 높인다.

- filter : 쿼리가 참인 도큐먼트를 검색하지만 스코어를 검색하지 않는다. must 보다 검색 속도가 빠르고 캐싱이 가능하다.

사용방법은 아래와 같다.

GET my_index/_search

{
  "query": {
    "bool": {

      "must": [

        { <쿼리> }, ...

      ],

      "must_not": [

        { <쿼리> }, ...

      ],

      "should": [

        { <쿼리> }, ...

      ],

      "filter": [

        { <쿼리> }, ...

      ],

    }
  }
}

 

1) match, match_not

다음은 단어 "quick"과 구문 "lazy dog"가 포함된 모든 문서를 검색하는 쿼리입니다.

GET my_index/_search

{
  "query": {
    "bool": {

      "must": [

        {

          "match": {

            "message": "quick"

          } 

        },

        {

          "match_phrase": {

            "message": "lazy dog"

          } 

        }

      ]

    }
  }
}

다음은 단어 "quick"을 포함하지만 "lazy dog"가 하나도 포함되지 않는 문서를 검색합니다.

GET my_index/_search

{
  "query": {
    "bool": {

      "must": [

        {

          "match": {

            "message": "quick"

          } 

        }

      ],

      "must_not": [

        {

          "match_phrase": {

            "message": "lazy dog"

          } 

        }

      ]

    }
  }
}

 

2) should

위에서 언급한 것처럼 검색 점수를 조정하기 위해 사용됩니다.

예를 들어 "lazy"가 포함된 결과에 가중치를 줘서 검색 결과를 좀 더 상위로 올리고 싶다면 should를 사용하면 된다.

GET my_index/_search

{
  "query": {
    "bool": {

      "must": [

        {

          "match": {

            "message": "fox"

          } 

        }

      ],

      "should": [

        {

          "match": {

            "message": "lazy"

          } 

        }

      ]

    }
  }
}

 

Response)

위의 "lazy"가 포함되지 않은 문장은 0.6... 의 가중치로 비슷한 score를 가지고 있지만 "lazy"를 포함한 문장은 1.154001로 좀 더 높은 가중치를 부여해주어 가장 상위에 나타나는 모습을 볼 수 있습니다.

 

should는 match_phrase와 함께 유용하게 사용할 수 있습니다.

쇼핑몰 상품 검색 같은 사례에서는 보통 검색어로 입력된 단어가 하나라도 포함된 결과들을 모두 가져오도록 되어있습니다.

이때 검색 결과 중에서 입력한 검색어 전체 문장이 정확히 일치하는 결과를 맨 상위에 위치시킨다면 다른 결과들을 누락시키지 않으면서 사용자가 정확하게 원하는 수준 높은 품질의 결과를 제공할 수 있습니다.

 

3) filter

참 / 거짓 여부만 판별해서 결과를 가져오는 것이 가능합니다.

Full Text와 상반되는 이 특성을 정확 값(Excat Value)이라고 하는데 말 그대로 값이 정확히 일치하는지의 여부만을 따지는 검색입니다.

GET my_index/_search

{
  "query": {
    "bool": {

      "must": [

        {

          "match": {

            "message": "fox"

          } 

        }

      ],

      "filter": [

        {

          "match": {

            "message": "quick"

          } 

        }

      ]

    }
  }
}

filter는 검색에 조건은 추가하지만 스코어에는 영향을 주지 안도록 제어할 때 사용합니다.

위의 예시에서 "quick"은 점수에 전혀 반영되지 않습니다. 즉, 쿼리 결과에는 영향을 미치지만 점수에는 영향을 미치지 않습니다.

must 검색 결과를 토대로 스코어는 변경하지 않고 보기 싫은 검색 결과를 뺄 때 사용하면 됩니다.

보통 쇼핑몰에서 검색어로 정확도가 노은 상품명을 검색하면서 생산 업체를 다시 필터링하는 등의 용도로 사용합니다.

 

filter 내부에서 다른 bool 쿼리를 포함하려면 filter 내부에 다시 bool 쿼리를 선언하고 다시 must_not을 넣어야 합니다.

GET my_index/_search

{
  "query": {
    "bool": {

      "must": [

        {

          "match": {

            "message": "fox"

          } 

        }

      ],

      "filter": [

        {

          "bool": {

            "must_not": [

              {

                "match" : {

                  "message": "dog"

                }

              }

            ]

          } 

        }

      ]

    }
  }
}

 

문자열 데이터 keyword 형식으로 저장하여 정확한 검색이 가능합니다.

아래의 쿼리를 통해 message 필드 값이 "Brown fox brown dog" 문자열과 정확히 일치하는 데이터를 검색할 수 있습니다.

공백, 대소문자까지 정확히 일치하는 값을 가져옵니다.

GET my_index/_search

{
  "query": {
    "bool": {

      "filter": [

        {

          "match": {

            "message.keyword": "Brown fox brown dog"

          } 

        }

      ]

    }
  }
}

keword 타입으로 저장된 필드는 스코어를 계산하지 않고 정확 값의 일치 여부만을 따지기 때문에 스코어가 0.0으로 나오게 될 것입니다.

스코어를 계산하지 않기 때문에 keyword 값을 검색할 때는 filter 구문 안에 넣도록 합니다.

 

3. Range Query

숫자나 날짜 형식을 range 쿼리를 통해 검색이 가능합니다.

Range Query는 range : { <필드명>: { <파라미터>: <값> } }으로 입력됩니다.

- gte (Greater-than or equal to) : 이상

- gt (Greater-than) : 초과

- lte (Less-than or equal to) : 이하 

- lt (Less-than) : 미만

 

1) 숫자 검색

GET phones/_search

{
  "query": {
    "range": {

      "price": {

        {

          "gte": 700,

          "lte": 900

        } 

      }

    }
  }
}

위와 같은 검색 쿼리를 이용할 경우 price값이 700원 이상 900원 이하의 결괏값을 얻을 수 있습니다.

 

2) 날짜 검색

JSON에서 일반적으로 사용되는 ISO8601 형식을 사용합니다.

아래의 쿼리는 2022년 01월 01일 이후의 도큐먼트들을 검색하는 쿼리입니다.

GET phones/_search

{
  "query": {
    "range": {

      "date": {

        {

          "gt": "2022-01-01"

        } 

      }

    }
  }
}

또한, format 옵션을 통해 날짜 포맷을 변경하는 방법도 있습니다. || 을 사용해서 여러 값의 입력도 가능합니다.

아래의 쿼리는 2019년 01월 01일부터 2021년 이전 사이에 있는 결괏값을 검색하는 쿼리입니다.

GET phones/_search

{
  "query": {
    "range": {

      "date": {

        {

          "gt": "2019-01-01",

          "lt": "2021",

          "format": "dd/MM/yyyy||yyyy"

        } 

      }

    }
  }
}

Range query는 기본적으로 정확도를 계산하지 않기 때문에 score점수가 1.0으로 모두 동일합니다.

 

😶 참고 문헌 

https://esbook.kimjmin.net/05-search/5.6-range

 

5.6 범위 쿼리 - Range Query - Elastic 가이드북

날짜를 검색 할 때는 검색하는 현재 시간을 가져오는 예약어 now와 y(년), M(월), d(일), h(시), m(분), s(초), w(주) 등의 사용이 가능합니다. 다음은 date의 값이 2016년 1월 1일에서 6개월 후인 날 부터 오

esbook.kimjmin.net

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html

 

'Elasticsearch' 카테고리의 다른 글

[Elasticsearch] curl: (52) Empty reply from server  (0) 2022.10.02
Elasticsearch 데이터 처리  (0) 2022.02.24
데이터 모델링 (1) - Settings, Mappings  (0) 2022.01.21
Elasticsearch Node  (0) 2021.12.23
Elasticsearch에 대해서  (0) 2021.12.20

블로그의 정보

꾸준히 공부하는 개발 노트

HeshAlgo

활동하기