Elasticsearch 검색 API


search API로 지정할 수 있는 4가지 검색 조건

search API에서 검색 기준을 지정할 때에 쿼리 DSL이라는 Elasticsearch 언어를 사용햔다. search API 로 이용하는 쿼리 DSL 에서는 이하의 4가지의 검색 조건을 지정할 수 있다.

sort 쿼리를 활용하여 검색 결과를 정렬하는 것도 가능하다.

사전 준비

search API의 데모를 하기 위해서, bulk API 로 검색 데모용의 도큐먼트 3개를 작성한다.

bulk API으로 도큐먼트를 일괄 등록

POST /_bulk
{ "index" : { "_index" : "demo_search" , "_id" : "1" }}
{ "text" : "This is Elasticsearch test." }
{ "index" : { "_index" : "demo_search" , "_id" : "2" }}
{ "text" : "Elasticsearch is God." }
{ "index" : { "_index" : "demo_search" , "_id" : "3" }}
{ "text" : "This is a pen." }

search API를 검색 조건 없이 사용

search API를 사용하여 생성한 demo_search 인덱스의 도큐먼트를 검색한다.

seach API를 이용한 검색

GET /demo_search/_search

demo_search 인덱스에 search API를 이용한 결과

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "text" : "This is Elasticsearch test."
        }
      },
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "text" : "Elasticsearch is God."
        }
      },
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "text" : "This is a pen."
        }
      }
    ]
  }
}

생성한 3개의 도큐먼트를 모두 검색할 수 있는 것을 볼 수 있다.

검색 조건 1. match 쿼리

match 쿼리는 역색인을 이용해 전체 텍스트 검색을 하는 쿼리이다. text 타입 필드의 문자열은 단어로 나뉘어 역색인이 생성되므로 match 쿼리에 사용할 수 있다.

match 쿼리

match 쿼리는 전체 텍스트 검색에 사용되는 쿼리이다.

match 쿼리를 이용해서 “Elasticsearch"라는 단어가 포함된 도큐먼트를 검색해 보자.

match 쿼리

GET /demo_search/_search
{
  "query" :{
     "match" : {
       "text" : "Elasticsearch"
    } 
  }
}

match 쿼리 결과

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 0.5077718,
    "hits" : [
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.5077718,
        "_source" : {
          "text" : "Elasticsearch is God."
        }
      },
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.45315093,
        "_source" : {
          "text" : "This is Elasticsearch test."
        }
      }
    ]
  }
}

“Elasticsearch"가 포함된 2개의 도큐먼트가 검색에 조회(hit)된 것을 볼 수 있다.

다음으로 지정한 단어 2개가 모두 일치하는 도큐먼트를 검색한다. 지정한 단어 2개의 AND를 하려면 operator에 AND를 지정한다. (기본적으로 OR이다.)

match 쿼리로 AND 검색을 한다.

GET /demo_search/_search
{
  "query" :{
     "match" : {
       "text" :{
         "query" : "Elasticsearch test" ,
         "operator" : "AND"
      }
    } 
  }
}

match 쿼리로 AND 검색한 결과

{
  "took" : 16,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.3988109,
    "hits" : [
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.3988109,
        "_source" : {
          "text" : "This is Elasticsearch test."
        }
      }
    ]
  }
}

“Elasticsearch"와 “test” 모두 포함된 도큐먼트만 출력되었다.

match_phrase 쿼리

match_phrase 쿼리는 단어 순서가 일치하는 도큐먼트만 출력한다.

예를 들어, “Elasticsearch test"의 순서이어야 검색에 조회(hit)된다.

match_phrase 쿼리: 어순이 맞는 경우

GET /demo_search/_search
{
  "query" :{
     "match_phrase" : {
       "text" :{
         "query" : "Elasticsearch test"
      }
    } 
  }
}

match_phrase 쿼리 실행 결과: 어순이 맞는 경우

{
  "took" : 27,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.398811,
    "hits" : [
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.398811,
        "_source" : {
          "text" : "This is Elasticsearch test."
        }
      }
    ]
  }
}

이번에는 “test Elasticsearch"를 match_phrase 쿼리로 조회해보자.

match_phrase 쿼리: 어순이 다른 경우

GET /demo_search/_search
{
  "query" :{
     "match_phrase" : {
       "text" :{
         "query" : "test Elasticsearch"
      }
    } 
  }
}

match_phrase 쿼리 실행 결과: 어순이 다른 경우

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  }
}

검색을 결과를 보면 조회되지 않않을 것을 볼 수 있다. “test” 다음에 “Elasticsearch"가 오는 도큐먼트가 없기 때문이다.

검색 조건 2. Term 검색 쿼리

필드에 대해 정확히 일치되는 검색을 수행한다.

term 쿼리

위에서 설명한 대로 필드에 대해 정확히 일치되는 검색을 수행한다.

Term 쿼리

GET /demo_search/_search
{
  "query" :{
     "term" : {
       "text.keyword" : "This is Elasticsearch test."
    } 
  }
}

Term 쿼리 실행 결과

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.9808291,
    "hits" : [
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.9808291,
        "_source" : {
          "text" : "This is Elasticsearch test."
        }
      }
    ]
  }
}

아래와 같이 “Elasticsearch"로 단어를 검색할 수 없다. 문장 “This is Elasticsearch test.“와 부분 일치는 하지만, 정확히 일치하지는 않는다.

Term 쿼리: 정확히 일치하지 않는 경우

GET /demo_search/_search
{
  "query":{
    "term": {
      "text.keyword": "Elasticsearch"
    } 
  }
}

Term 쿼리 실행 결과: 정확히 일치하지 않는 경우

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  }
}

text 타입의 필드를 Term 쿼리로 검색하면 예기치 않은 결과가 발생할 수 있다.

Term 쿼리: test 타입으로 검색한 경우

GET /demo_search/_search
{
  "query" :{
     "term" : {
       "text" : "This is Elasticsearch test."
    } 
  }
}

Term 쿼리 실행 결과: test 타입으로 검색한 경우

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  }
}

이것은 text 타입에서는 “This is Elasticsearch test.” 문장은 형태소 해석이 되어 단어로 분할되기 때문에, 분해된 단어와 “This is Elasticsearch test.” 문장이 일치하지 않기 때문이다.

Terms

여러 문자열이 정확히 일치되는 검색을 수행하려면, Term에 복수의 의미로 s가 붙은 Terms 쿼리를 사용한다.

Terms 쿼리

GET /demo_search/_search
{
  "query" :{
     "terms" : {
       "text.keyword" :[ "This is Elasticsearch test." , "This is a pen." ]
    } 
  }
}

Terms 쿼리 실행 결과

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "text" : "This is Elasticsearch test."
        }
      },
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "text" : "This is a pen."
        }
      }
    ]
  }
}

검색 조건 3. Range 쿼리

지정된 값의 범위를 검색한다.

먼저 Range 쿼리 데모용 test_range 인덱스를 만든다. (덧붙여 템플릿으로 date 필드의 매핑을 작성하고 있는 것이 전제로 하고 있기 때문에, 아직 작성하지 않았다면 “템플릿으로 매핑 만들기”를 보고 먼저 템플릿을 생성해 주길 바란다.)

test_range 인덱스 생성

POST /_bulk
{ "index" : { "_index" : "test_range" , "_id" : "1" }}
{ "date" : "2000/01/01 09:00:00+0900" }
{ "index" : { "_index" : "test_range" , "_id" : "2" }}
{ "date" : "2010/08/01 09:00:00+0900" }
{ "index" : { "_index" : "test_range" , "_id" : "3" }}
{ "date" : "2020/11/01 09:00:00+0900" }

그럼 이제 준비가 다 되었다면, Range 쿼리를 사용하여 “2010/01/01 09:00:00+0900” 보다 최신이고, “2030/01/01 09:00:00+0900” 보다 오래된 도큐먼트를 검색해 보자.

Range 쿼리

GET /test_range/_search
{
  "query" :{
     "range" : {
       "date" :{
         "gte" : "2010/01/01 09:00:00+0900" ,
         "lte" : "2030/01/01 09:00:00+0900"
      }
    } 
  }
}

Range 쿼리 실행 결과

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "test_range",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "date" : "2010/08/01 09:00:00+0900"
        }
      },
      {
        "_index" : "test_range",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "date" : "2020/11/01 09:00:00+0900"
        }
      }
    ]
  }

“2010/01/01 09:00:00+0900” 보다 최신이고, “2030/01/01 09:00:00+0900” 보다 오래된 도큐먼트가 조회된 것을 확인할 수 있다.

검색 조건 4. bool 쿼리

match 쿼리, term 쿼리, range 쿼리를 조합하여 AND, OR, NOT를 사용할 수 있다.

먼저, bool 쿼리를 실행해 보기 위해 demo_bool 인덱스를 만든다.

demo_bool 인덱스 생성

POST /_bulk
{ "index" : { "_index" : "demo_bool", "_id" : "1" } }
{ "text" : "This is Elasticsearch test.","id" : 1 }
{ "index" : { "_index" : "demo_bool", "_id" : "2" } }
{ "text" : "Elasticsearch is God.", "id" : 2 }
{ "index" : { "_index" : "demo_bool", "_id" : "3" } }
{ "text" : "This is a pen.", "id" : 3 }

이것으로 준비가 되었으므로 여기에서 bool 쿼리를 소개한다.

must 쿼리

AND 조건이다. text 필드에 “Elasticsearch"와 match 되고, id가 1이하인 도큐먼트를 검색한다.

must 쿼리

GET /demo_bool/_search
{
  "query" :{
     "bool" : {
       "must" :[
        { "match" :{ "text" : "Elasticsearch" }},
        { "range" :{ "id" :{ "lte" : 1 }}}
      ]
    } 
  }
}

must 쿼리 실행 결과

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.4061103,
    "hits" : [
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.4061103,
        "_source" : {
          "text" : "This is Elasticsearch test.",
          "id" : 1
        }
      }
    ]
  }
}

should 쿼리

should 쿼리는 OR 조건이다. 이전과 동일한 조건으로 bool 쿼리를 shoud 쿼리로 변경하여 검색한다.

should 쿼리

GET /demo_bool/_search
{
  "query" :{
     "bool" : {
       "should" :[
        { "match" :{ "text" : "Elasticsearch" }},
        { "range" :{ "id" :{ "lte" : 1 }}}
      ]
    } 
  }
}
{
  "took" : 15,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.4096484,
    "hits" : [
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.4096484,
        "_source" : {
          "text" : "This is Elasticsearch test.",
          "id" : 1
        }
      },
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.4590256,
        "_source" : {
          "text" : "Elasticsearch is God.",
          "id" : 2
        }
      }
    ]
  }
}

이전에 조회되지 않았던 id = 2의 도큐먼트에도 “Elasticsearch” 문자열이 포함되어 있으므로 검색 결과에 나타난다. 또한 id, text 두 조건을 만족하는 쪽이 "_score"의 값이 높아진다.

must_not 쿼리

must_not 쿼리는 NOT을 나타낸다. 이전과 동일한 조건으로 bool 쿼리를 must_not 쿼리로 변경하여 검색한다.

must_not 쿼리

GET /demo_bool/_search
{
  "query" :{
     "bool" : {
       "must_not" :[
        { "match" :{ "text" : "Elasticsearch" }},
        { "range" :{ "id" :{ "lte" : 1 }}}
      ]
    } 
  }
}

must_not 쿼리 실행 결과

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.0,
    "hits" : [
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.0,
        "_source" : {
          "text" : "This is a pen.",
          "id" : 3
        }
      }
    ]
  }
}

두 조건을 모두 충족하지 않는 도큐먼트가 표시되었음을 알 수 있다. 조건을 전혀 충족시키지 않기 때문에, “_score"는 당연히 0.0 이다.

filter 쿼리

filter 쿼리는 필터로 지정된 문서 이외는 검색 대상에서 제외한다. 제외된 도큐먼트는 검색 결과의 “_score"에 영향을 주지 않는다.

filter 쿼리

GET /demo_bool/_search
{
  "query" :{
     "bool" : {
       "must" : [
        { "match" :{ "text" : "Elasticsearch" }}
      ], 
      "filter" :[
        { "range" :{ "id" :{ "lte" : 1 }}}
      ]
    } 
  }
}

filter 쿼리 실행 결과

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.40964836,
    "hits" : [
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.40964836,
        "_source" : {
          "text" : "This is Elasticsearch test.",
          "id" : 1
        }
      }
    ]
  }
}

우선, filter 조건으로 id 필드의 값이 1 이하인 도큐먼트만을 반환한다. 그런 다음 반환된 문서에서 match 쿼리의 내용과 일치하는 문서를 찾는다.

보충: (검색 결과 정렬) sort 쿼리

sort 쿼리는 쿼리 결과를 지정된 필드로 정렬한다.

id 필드로 정렬하는 쿼리는 다음과 같다.

sort 쿼리

GET /demo_bool/_search
{
  "sort" : [
    {
      "id" : {
         "order" : "desc"
      }
    }
  ]
}

sort 쿼리 실행 결과

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : null,
        "_source" : {
          "text" : "This is a pen.",
          "id" : 3
        },
        "sort" : [
          3
        ]
      },
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : null,
        "_source" : {
          "text" : "Elasticsearch is God.",
          "id" : 2
        },
        "sort" : [
          2
        ]
      },
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "text" : "This is Elasticsearch test.",
          "id" : 1
        },
        "sort" : [
          1
        ]
      }
    ]
  }
}

id 필드의 값으로 내림차순(desc)이 출력된 것을 볼 수 있다.

요약 정리

검색 API 5가지

  • URI 검색 - query String 방식의 key/value로 검색
  • Request Body 검색 - json 형식
  • QueryDSL 방식 - 여러개의 질의를 조합하거나 강력한 검색이 가능
  • Match All Query - 색인에 모든 문서를 검색하는 쿼리 (일반적인 색인에 저장된 문서를 확인시)
  • Match Query - 텍스트, 숫자, 날짜 등이 포함된 문장을 텀을 분리해 검색 질의
  • Multi Match Query - Match Query와 동일하나 여러 필드를 검색시 사용
  • Term Query - 분석되지 않은 Keyword 타입을 검색할 시 사용
  • Bool Query - must(and), must_not(!=), should(or), filter(in) 등을 필드로 검색할 때 사용
  • WildCard Query
  • 등등

검색 쿼리

  • query
    • match_all : 지정된 색인의 모든 도큐먼트를 검색한다.
    • match : 기본 필드 검색 쿼리. 특정 필드 또는 필드 집합에 대해 검색이 수행된다.
      • FIELD : TEXT
    • match_phrase : 지정한 문구 있는 모든 계정을 반환한다.
      • FIELD : TEXT
    • bool : 부울 로직을 사용하여 작은 쿼리로 더 큰 쿼리를 만들 수 있다.
      • must : 모든 쿼리가 true가 되어야 문서가 일치하는 항목으로 간주된다.
        • match
          • FIELD : TEXT
      • should : 지정된 쿼리 중 하나라도 true가 되면 문서는 일치 항목이 된다.
        • match
          • FIELD : TEXT
      • must_not : 지정된 쿼리 중 어느 것도 true가 아닐 때만 문서가 일치 항목이 된다.
        • match
          • FIELD : TEXT
      • filter : 점수 계산 방식을 바꾸지 않고도 쿼리를 사용하여 다른 절과 일치할 문서를 제한할 수 있다.
        • range : 값이 범위로 문서를 필터링할 수 있다.
          • FILED : 범위를 지정할 필드명
            • gte : 입력값 이상
            • lte : 입력값 이하
    • term : 점수와 관련 없이 일치하는 도큐먼트를 조회한다.
      • FIELD : 조회할 필드명
        • value : 지정값 필드명의 값을 조회한다.
  • size : 조회 건수를 지정한다. (지정하지 않으면 기본 10이다.)
  • from : 어떤 문서 색인에서 시작할지 지정한다.
  • sort : 조회하는 도큐먼트를 정렬한다.
    • FIELD : 정렬할 필드명
      • order : desc(오름차순), asc(내림차순)
  • _source : 표시할 필드(들)을 지정한다.
  • aggs : 집계 데이터를 그룹화하고 통계치를 얻는 기능이다.