이번 글에서는 쿼리를 통해 검색을 하는 Query DSL( Domain Specific Language )에 대해 알아보겠습니다.

실습을 위한 테스트 데이터 셋팅과 Search API 개념과 관련하여 이전 글을 먼저 읽으시길 권장합니다.




1. Query DSL

이제 본격적으로 json 포맷으로 query를 만들어서 검색을 해보는 Query DSL에 대해 알아보겠습니다.

검색할 때 대부분 Query DSL을 사용하므로 이 방법을 익혀두는 것이 좋습니다.


Query DSL에 대해 알아보기에 앞서 Query Context와 Filter Context에 대한 개념이 필요합니다. ( 참고 )

앞으로 소개 할 query 절은 Query Context 또는 Filter Context에서 사용되는지 여부에 따라 다르게 동작합니다.

  • Query Context
    • Query Context에서 사용되는 query 절은 " 해당 document가 query 절과 얼마나 잘 일치하는가? "라는 질문에 응답하는데, document가 얼마나 잘 일치하는지를 _score( 관련성 점수, relevance score )로 표현합니다.
  • Filter Context
    • Filter Context에서 사용되는 query 절은 " 해당 docuemnt가 query 절과 일치합니까? "라는 질문에 응답하는데, 그 대답은 true 또는 false이며 점수는 계산하지 않습니다.


아래의 예제는 다음의 종족이 모두 충족되는 document를 반한하는 예제입니다.

1) title 필드에 search가 있고

2) content 필드에 elasticsearch가 있고

3) status 필드의 값이 정확히 published이고

4) publish_date 필드의 값이 정확히 2015-01-01 이후인 경우

{ "query": { "bool": { "must": [ { "match": { "title": "Search" }}, { "match": { "content": "Elasticsearch" }} ], "filter": [ { "term": { "status": "published" }}, { "range": { "publish_date": { "gte": "2015-01-01" }}} ] } } }

여기서 query 파라미터는 Query Context를 명시한 것이며, bool과 그 아래 2개의 match 절은 Query Context가 됩니다.

filter 파라미터는 Filter Context를 명시한 것이며, term과 range 절은 Filter Context가 됩니다.


  • bool 쿼리 아래에 있는 filter 또는 must_not 파라미터
  • constant_score 쿼리
  • filter aggregation

등과 같이 자주 사용되는 필터는 성능을 높이기 위해 ES가 캐싱합니다.

따라서 두 환경의 차이를 다음과 같이 정리할 수 있습니다.

참고: https://m.blog.naver.com/tmondev/220292262363




2. Query DSL 예제

이제 예제를 통해 Query DSL에는 어떤 것들이 있는지 살펴보도록 하겠습니다.

ElasticSearch의 모든 API를 소개하는 것은 무리가 있으며, 아래와 같이 몇 가지의 사용법을 익힌다면 문서를 보면서 자유자재로 사용할 수 있지 않을까 싶습니다.


아래의 예제들을 실행하기 위해서는 query.json 파일을 만들어서 각 예제마다 수정을 하고, 명령어는 모두 다음과 같이 실행하면 됩니다.

# curl -XGET 'localhost:9200/bank/account/_search?pretty' -H 'Content-Type: application/json' -d @query.json


1) match_all / match_none 참고 )

match_all 쿼리는 지정된 index의 모든 document를 검색하는 방법입니다.

즉, 특별한 검색어 없이 모든 document를 가져오고 싶을 때 사용합니다.

SQL로 치면 WHERE 절이 없는 SELECT문과 같겠네요.


이와 유사하게 match_none이 있는데, 모든 document를 가져오고 싶지 않을 때 사용합니다. 

# vi query.json

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



Full text Queries

2) match ( 참고 )

match 쿼리는 기본 필드 검색 쿼리로써, 텍스트/숫자/날짜를 허용합니다.

아래는 address에 mill이라는 용어가 있는 모든 document를 조회하는 예제입니다.

{

"query":{ "match":{ "address":"mill" } } }



Filter Context

3) bool 참고 )

bool 쿼리는 bool( true / false ) 로직을 사용하는 쿼리이며, 그 종류는 다음과 같습니다.

  • must : bool must 절에 지정된 모든 쿼리가 일치하는 document를 조회
  • should : bool should 절에 지정된 모든 쿼리 중 하나라도 일치하는 document를 조회
  • must_not : bool must_not 절에 지정된 모든 쿼리가 모두 일치하지 않는 document를 조회
  • filter : must와 같이 filter 절에 지정된 모든 쿼리가 일치하는 document를 조회하지만, Filter context에서 실행되기 때문에 score를 무시합니다. 

bool 쿼리 내에 위의 각 절들을 조합해서 사용할 수도 있고,

또한 bool 절 내에 bool 쿼리를 작성할 수도 있습니다.


아래는 나이가 40세이지만, ID 지역에 살고 있지 않은 document를 조회하는 예제입니다.

{

"query": { "bool": { "must": [ { "match": { "age": "40" } } ], "must_not": [ { "match": { "state": "ID" } } ] } } }




4) filter ( 참고 )

filter 쿼리는 document가 검색 쿼리와 일치하는지 나타내는 _score 값을 계산하지 않도록 쿼리 실행을 최적화합니다.

자세한 내용은 위에서 Filter Context에서 다뤘으니 예제도 생략하겠습니다.




5) range ( 참고 )

range 쿼리는 범위를 지정하여 범위에 해당하는 값을 갖는 document를 조회합니다.

앞에서 살펴본 바와 같이 Filter Context이며, 정수, 날짜를 비교할 수 있습니다.


range 쿼리에서 범위를 지정하는 파라미터는 다음과 같습니다.

  • gte : 크거나 같다.
  • gt : 크다.
  • lte : 작거나 같다.
  • lt : 작다.
  • boost : 쿼리의 boost 값을 셋팅합니다. ( 기본 값 1.0 )
    • boost란 검색에 가중치를 부여하는 것으로, 여기를 참고하시길 바랍니다.


아래는 잔액이 20000~30000인 범위에 속하는 document를 조회하는 예제입니다.

{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}



Term Level Queries

6) term ( 참고 )

term 쿼리는 역색인에 명시된 토큰 중 정확한 키워드가 포함된 document를 조회합니다.


term 쿼리를 사용할 때 document가 조회되지 않는 이슈가 있을 수 있습니다.

string 필드는 text 타입( 이메일 본문과 같은 전문(full text) ) 또는 keyword 타입( 전화번호, 우편번호 등.. )을 갖습니다.

text 타입은 ES 분석기를 통해 역색인이 되는 반면, keyword 타입은 역색인이 되지 않는 특징이 있는데요.

예를 들어, "Quick Foxes" 라는 문자열이 있을 때 text 타입은 [quick, foxes]으로 역색인이 됩니다.


이런 상황에서 term 쿼리로 "quick" 문자열을 검색했다면 해당 document를 찾을 수 있지만,

"quick foxes"라는 전문을 검색했을 경우에는 document를 찾을 수 없습니다.


따라서 term 쿼리는 정확한 키워드를 찾기 때문에 전문 검색( full text search )에는 어울리지 않습니다.

전문 검색을 하고자 할 경우에는 match 쿼리를 사용하는 것이 좋다고 합니다.

또는 "Quick Foxes"라는 문자열의 타입을 keyword 타입이 되도록 mapping을 하는 방법도 있습니다.


term 쿼리 사용방법은 다음과 같습니다.

{ "query": { "term" : { "lastname" : "bond" } } }

term 쿼리는 역색인에 명시된 문자열을 찾으므로 Bond 문자열에 대해선 찾을 수 없습니다.

ES에서 분석기를 거쳐 역색인이 될 때 lowercase 처리하기 때문이죠.




7) terms ( 참고 )

terms 쿼리는 배열에 나열된 키워드 중 하나와 일치하는 document를 조회합니다.


아래는 address 필드에서 street, place, avenue 키워드가 하나라도 존재하는 document를 조회하는 예제입니다.

{
  "query": {
    "terms": {
      "address": ["street", "place", "avenue"]
    }
  }
}

최소 몇 개의 검색어가 포함되기를 원한다면 여기를 참고하면 좋을 것 같습니다.




8) regexp ( 참고 )

regexp 쿼리는 정규 표현식 term 쿼리를 사용할 수 있습니다.

regexp 쿼리는 Term Level 쿼리인데, 이는 정확한 검색을 한다는 의미입니다.


아래는 address 필드에서 끝 문자열이 street인 document를 조회하는 예제입니다.

{
    "query": {
        "regexp":{
            "address": ".*street"
        }
    }
}

정규식 .* 을 분석해보면 다음과 같습니다.

  • . : 모든 문자열
  • * : 0개 이상의 자리수

즉, " ( 0개 이상의 자리수를 갖는 모든 문자열 ) + street " 인 문자열을 찾는 쿼리가 됩니다.

그 밖에 ES 정규식 문법에 대해서는 여기를 참고해주세요





이상으로 Query DSL의 몇 가지 쿼리들에 대해 알아보았습니다.

다음 글에서는 이렇게 조회한 쿼리들에 대한 검색 결과를 가공하는 방법에 대해 알아보겠습니다.


[ 참고 문서 ]

http://www.jopenbusiness.com/mediawiki/ElasticSearch_-_REST_API

https://bakyeono.net/post/2016-08-20-elasticsearch-querydsl-basic.html