본문 바로가기

Monitoring/Elasticsearch

08. Query

Search Method
 
아래는 일반적으로 Elasticsearch 에서 사용되는 search API 의 형태로 DSL 쿼리를 많이 사용한다.
 
예를 들어 아래 DSL 쿼리는 description value 가
 
read wine 과 match 되는 documents 를 찾도록 시도하게 된다. 
 
 
좀 더 간단하게 value 를 따로 넣지 않고 direct 로 넣어도 된다.
 
 
다음과 같은 쿼리를 URI 쿼리라고 하는데 
 
 
이를 DSL 쿼리로 바꾸면 다음과 완전히 동일하다.
 
 
 
 
Query DSL Types
 
Elasticsearch 는 match, term, range 등의 Leaf query 와
 
bool, dis_max 같은 복합 Query ( Leaf query 를 조건식으로 결합하는 query ) 를 제공한다.
 
아직 아래 그림은 이해되지 않을 것이나 나중에 자세한 내용을 더 다룬다.
 
 
 
 
Meta Fields
 
검색 부분을 하면 나중에 확인하게 되겠지만 document 입력시 
 
Elasticsearch 가 document 를 관리하기 위해 자동으로 입력되는
 
몇몇 Field 가 있으며 이를 Meta Field 라고 한다.
 
이번 장에서 이런 Meta Field 중 중요한 몇몇 Meta Field 를 알아보도록 하자.
 
 
_index
 
document 가 속한 index 정보를 포함하는 field 이다.
 
 
_id
 
document 의 ID 정보를 포함하는 field 이다.
 
 
_source
 
사용자가 입력한 원본 Document 데이터 (json object) 를 포함하고 있는 filed 이다.
 
 
_field_names
 
non_null value 를 가지는 모든 field 이름을 저장하고 있는 field 로
 
exist 쿼리를 수행할 때 사용된다고 한다.
 
 
_version
 
document 의 internal version 정보를 저장하고 있다.
 
 
 
Test request URI
 
URI 방식으로 search 를 실제 해보면 아래와 같다.
 
 
결과로 나오는 것을 보면 hits 쪽이 검색 결과를 의미하며 앞서 살펴본 Meta Field 도 포함되어 있다.
 
 
 
Test  Query DSL
 
DSL 쿼리를 사용할 때는 어떤 타입의 쿼리인지 넣어줘야 한다.
 
여기에서는 match 를 넣었고 결과은 앞서 URI 쿼리와 동일하다.
 
 
 
 
Relevance Scores
 
위에서는 하나의 결과만 나오게 하였지만 실제로 결과는 수백 수만개가 될 수 있을 것이다.
 
구글 검색의 경우만 보아도 비슷하거나 입력 값 Token 중 일부가 포함된 것들도 결과로 나오는 것을 볼 수 있다. 
 
검색이란 이렇게 사용자가 입력한 검색 쿼리에 대해 관련있는 문서를 매칭한 후 관련성에 따라 정렬하는 과정이다.
 
또한 쿼리는 "최순실" 과 같이 한단어 이거나 "최순실 게이트" 같이 여러 단어일 수 있다.
 
이 경우 "최순실" , "게이트" 각 단어의 검색 점수를 합해야 하며 이러한 각 단어를 term 이라고 한다.
 
 
Relavance Scores 계산 알고리즘은 아래와 같다.
 
 
 
TF/IDF
 
루신의 TF/IDF 라는 알고리즘이 있다.
 
 
이 수식에 보면 TF 와 IDF 그리고 NORM Factor 를 사용하고 있는 것을 확인할 수 있다.
 
아래는 이에 대한 각 항목의 설명이다.
 
 

TF (Term Frequency - 단어빈도 )
 
Term 이 문서에 등장하는 횟수로 많이 등장할수록 문서의 검색 점수가 높아진다.
 
 
 
IDF(Inverse Document Frequency - 문서 역빈도)
 
 문서 빈도 (Document Frequency) 는 Term 이 등장하는 Document 의 개수로
 
여러 Document 에 등장할 수록 문서의 Search Score 가 낮아진다.
 
예를 들어 'the' 'a' 'is' 등 모든 문장에서 항상 등장하는 Term 은 검색 결과에 영향을
 
거의 미치지 않게 된다.
 
 
 
Norm ( Document 길이 가중치 )
 
Term 이 등장하는 Document 의 길이에 대한 가중치며 
 
"축구" 라는 키워드로 검색하는 경우
 
"A 의 취미는 축구다" 와 "A 와 B 의 취미는 축구와 야구다" 라는 Document 중
 
"A 의 취미는 축구다" 의 Search Score 가 더 높다.
 
즉 Norm 가중치는 길이와 반비례하며 조절 가능한 값이다.
 
 
Example
 
serch API 사용하면 기본적으로 _score 필드를 포함한 결과를 확인할 수 있다.
 
 
 
 
Debugging unexpected search results
 
_explanation API 를 사용하면 왜 검색이 안되었는지 디버깅할 수 있다.
 
 
 
 
Full-text query vs Term level query
 
위에서 serch API 사용시 옵션 match 와 term 은  각각 전체 텍스트 검색 과 용어 레벨 검색의 차이점이 있다.
 
match 라고 넣는 경우는 일치 문자열이 있거나 대/소문자 차이가 있더라도 Analyer 를 어떤것을 적용해 넣었느냐에 
 
따라 검색이 될 수가 있지만 term 이라고 넣는 경우 Analyzer 를 사용하지 않아 반드시 일치하는 문자열만 검색된다.
 
그리고 term 옵션의 경우는 Analyzer 사용하지 않기 때문에 속도가 빠르며 캐싱이 된다.
 
 
 
 
Term Level Query
 
Term 쿼리를 사용하는 여러 방식에 대해 알아보자.
 
먼저 검색을 위한 데이터를 아래와 같이 입력하고 확인해 보자. 
 
DELETE /product
 
PUT /product
{
  "mappings" : {
    "default" : {
      "dynamic":false,
      "properties":{
        "in_stock":{
          "type":"integer"
        },
        "is_active":{
          "type":"boolean"
        },
        "price":{
          "type":"integer"
        },
        "sold":{
          "type":"long"
        }
      }
    }
  }
}
 
POST /product/default/_bulk
{"index":{"_id":100}}
{"in_stock":100,"is_active":true,"price":10000,"sold":100000}
{"index":{"_id":101}}
{"in_stock":200,"is_active":true,"price":20000,"sold":200000}
{"index":{"_id":102}}
{"in_stock":300,"is_active":true,"price":30000,"sold":300000}
{"index":{"_id":103}}
{"in_stock":400,"is_active":true,"price":40000,"sold":400000}
{"index":{"_id":104}}
{"in_stock":500,"is_active":false,"price":50000,"sold":500000}
{"index":{"_id":105}}
{"in_stock":600,"is_active":false,"price":60000,"sold":600000}
{"index":{"_id":106}}
{"in_stock":700,"is_active":false,"price":70000,"sold":700000}
 
 
Query by a term ( term )
 
하나의 term 을  포함하는 document 를 검색 시에는 term query 를 사용한다.
 
GET /product/default/_search
{
  "query": {
    "term": {
      "is_active":true
    }
  }
}
{
  "took" : 10,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 4,
    "max_score" : 0.35667494,
    "hits" : [
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "100",
        "_score" : 0.35667494,
        "_source" : {
          "in_stock" : 100,
          "is_active" : true,
          "price" : 10000,
          "sold" : 100000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "101",
        "_score" : 0.35667494,
        "_source" : {
          "in_stock" : 200,
          "is_active" : true,
          "price" : 20000,
          "sold" : 200000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "103",
        "_score" : 0.35667494,
        "_source" : {
          "in_stock" : 400,
          "is_active" : true,
          "price" : 40000,
          "sold" : 400000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "102",
        "_score" : 0.2876821,
        "_source" : {
          "in_stock" : 300,
          "is_active" : true,
          "price" : 30000,
          "sold" : 300000
        }
      }
    ]
  }
}
 
 
Query by multiple terms ( terms )
 
여러개의 term 을 포함하는 document 검색 시에는 terms query 를 사용한다.
 
term 대신 terms 를 사용하면 배열로 여러개의 값을 지정해주면 된다.
 
GET /product/default/_search
{
  "query": {
    "terms": {
      "price":[20000,30000]
    }
  }
}
{
  "took" : 10,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 4,
    "max_score" : 0.35667494,
    "hits" : [
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "100",
        "_score" : 0.35667494,
        "_source" : {
          "in_stock" : 100,
          "is_active" : true,
          "price" : 10000,
          "sold" : 100000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "101",
        "_score" : 0.35667494,
        "_source" : {
          "in_stock" : 200,
          "is_active" : true,
          "price" : 20000,
          "sold" : 200000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "103",
        "_score" : 0.35667494,
        "_source" : {
          "in_stock" : 400,
          "is_active" : true,
          "price" : 40000,
          "sold" : 400000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "102",
        "_score" : 0.2876821,
        "_source" : {
          "in_stock" : 300,
          "is_active" : true,
          "price" : 30000,
          "sold" : 300000
        }
      }
    ]
  }
}
 
 
Query by range values ( range )
 
사잇값을 포함하는 document 검색 시는 range query 를 사용한다.
 
이는 보통 날짜 검색등에 많이 사용된다.
 
GET /product/default/_search
{
  "query": {
    "range": {
      "price":{
        "gte":20000,
        "lte":30000
      }
    }
  }
}
{
  "took" : 7,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "101",
        "_score" : 1.0,
        "_source" : {
          "in_stock" : 200,
          "is_active" : true,
          "price" : 20000,
          "sold" : 200000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "102",
        "_score" : 1.0,
        "_source" : {
          "in_stock" : 300,
          "is_active" : true,
          "price" : 30000,
          "sold" : 300000
        }
      }
    ]
  }
}
 
 
 
Query not null values ( exists )
 
특정 field 가 not null 인 모든 documents 를 검색할 때 exists query 를 날리면 된다.
 
GET /product/default/_search
{
  "query":{
    "exists": {
      "field":"price"
    }
  }
}
{
  "took" : 13,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 7,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "105",
        "_score" : 1.0,
        "_source" : {
          "in_stock" : 600,
          "is_active" : false,
          "price" : 60000,
          "sold" : 600000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "100",
        "_score" : 1.0,
        "_source" : {
          "in_stock" : 100,
          "is_active" : true,
          "price" : 10000,
          "sold" : 100000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "101",
        "_score" : 1.0,
        "_source" : {
          "in_stock" : 200,
          "is_active" : true,
          "price" : 20000,
          "sold" : 200000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "103",
        "_score" : 1.0,
        "_source" : {
          "in_stock" : 400,
          "is_active" : true,
          "price" : 40000,
          "sold" : 400000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "104",
        "_score" : 1.0,
        "_source" : {
          "in_stock" : 500,
          "is_active" : false,
          "price" : 50000,
          "sold" : 500000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "106",
        "_score" : 1.0,
        "_source" : {
          "in_stock" : 700,
          "is_active" : false,
          "price" : 70000,
          "sold" : 700000
        }
      },
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "102",
        "_score" : 1.0,
        "_source" : {
          "in_stock" : 300,
          "is_active" : true,
          "price" : 30000,
          "sold" : 300000
        }
      }
    ]
  }
}
 
 
Query by prefix ( prefix )
 
 
 
Query by wildcard ( wildcard )
 
 
 
Query with regular expression ( regexp )
 
 
 
 
 
Full Text Query
 
이번에는 Full text query 사용방법에 대해 알아보자.
 
사용할 데이터는 아래와 같이 입력한다.
 
POST /recipe/default/_bulk
<test-data.json 파일에 포함된 내용을 복붙>
 
GET /recipe/default/_mapping
{
  "recipe" : {
    "mappings" : {
      "default" : {
        "properties" : {
          "created" : {
            "type" : "date",
            "format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
          },
          "description" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "ingredients" : {
            "properties" : {
              "name" : {
                "type" : "text",
                "fields" : {
                  "keyword" : {
                    "type" : "keyword",
                    "ignore_above" : 256
                  }
                }
              },
              "quantity" : {
                "type" : "text",
                "fields" : {
                  "keyword" : {
                    "type" : "keyword",
                    "ignore_above" : 256
                  }
                }
              }
            }
          },
          "preparation_time_minutes" : {
            "type" : "long"
          },
          "ratings" : {
            "type" : "float"
          },
          "servings" : {
            "properties" : {
              "max" : {
                "type" : "long"
              },
              "min" : {
                "type" : "long"
              }
            }
          },
          "steps" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "title" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    }
  }
}
 
 
 
Match query ( match )
 
match query 에서 여러 terms 을 넣으면 자동으로 operator 가 or 로 설정된다.
 
따라서 그 결과로  Pesto Pasta Green Beans 중 하나라도 포함되어 있고
 
유사 문자열을 포함하는 문자가 title filed 값으로 있으면
 
Hit 되어 결과로 나오는 것을 확인할 수 있을 것이다. 
 
이때 and 연산자를 사용하면 타이틀 field 에
 
Pesto Pasta Green Beans 를 모두 포함하고 있는 documents 가 검색된다.
 
GET /recipe/default/_search
{
  "query":{
    "match":{
      "title":"Pesto Pasta Green Beans"
    }
  }
}
 
위 쿼리는 컴파운드 쿼리로 자동으로 or 조건이 되어 아래와 동일하게 구성된다.
 
GET /recipe/default/_search
{
  "query":{
    "match":{
      "title": {
        "query":"Pesto Pasta Green Beans",
        "operator": "or"
      }
    }
  }
}
{
  "took" : 7,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 9,
    "max_score" : 4.9510584,
    "hits" : [
      {
        "_index" : "recipe",
        "_type" : "default",
        "_id" : "13",
        "_score" : 4.9510584,
        "_source" : {
          "title" : "Pesto Pasta With Potatoes and Green Beans",
.....
GET /recipe/default/_search
{
  "query":{
    "match":{
      "title": {
        "query":"Pesto Pasta Green Beans",
        "operator": "and"
      }
    }
  }
}
{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 4.9510584,
    "hits" : [
      {
        "_index" : "recipe",
        "_type" : "default",
        "_id" : "13",
        "_score" : 4.9510584,
        "_source" : {
          "title" : "Pesto Pasta With Potatoes and Green Beans",
 
 
Match phrase query ( match_phrase )
 
구문일치 (해당 문자열이 keyword 처럼 포함되어 a, the 같은 문자열도 중요) 되는 documents 를 
 
찾을 때는 match_phrase query 를 실행하면 된다.
 
GET /recipe/default/_search
{
  "query":{
    "match_phrase":{
      "title": {
        "query": "Pesto Pasta With Potatoes"
      }
    }
  }
}
{
  "took" : 7,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 3.5817542,
    "hits" : [
      {
        "_index" : "recipe",
        "_type" : "default",
        "_id" : "13",
        "_score" : 3.5817542,
        "_source" : {
          "title" : "Pesto Pasta With Potatoes and Green Beans",
...
 
 
Multi match query ( multi_match )
 
여러 field 에서  match query 실시해야 할 경우는 multi_match query 를 사용한다.
 
GET /recipe/default/_search
{
  "query":{
    "multi_match":{
        "query":"Pasta",
        "fields":["title","description"]
    }
  }
}
{
  "took" : 71,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 16,
    "max_score" : 1.2756016,
    "hits" : [
      {
        "_index" : "recipe",
        "_type" : "default",
        "_id" : "2",
        "_score" : 1.2756016,
        "_source" : {
          "title" : "Pasta With Butternut Squash and Sage Brown Butter",
...
 
 
 
Query type context vs Filter type context
 
context 는 한글로 표현하자면 "절" 이다.
 
바꿔말하면 query 절과 filter 절이 있는데 이런 절 안에는
 
Elasticsearch 에서 제공하는 query 를 채울 수 있다.
 
query ( queries ) 
 
두 절의 다른 점은 query 절에는 relevant score 를 계산하는 full-text query 로만 구성해야 하며
 
"How well does this document match this query clause"
 
filter 절에 넣은 query 는 작동시 Releavant Score 를 사용하지 않고 단순히 일치 / 불일치만 계산하므로
 
Does this document match this query clause? 
 
filter 절에는 이런 특성을 가지는 term level query 로 구성해야 한다.
 
예를 들어 아래 compound query 는 must 라는 query 절과 filter 라는 filter 절을 포함하며
 
must query 절에는 match 라는 full-text query  ( relevant score 고려한 결과를 내는 쿼리 ) 를 사용하였고
 
filter 절에는 term , range 처럼 term level query 를 사용하였다.
 
GET /my_index/default/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title":   "Search"        }},
        { "match": { "content": "Elasticsearch" }}  
      ],
      "filter": [
        { "term":  { "status": "published" }},
        { "range": { "publish_date": { "gte": "2015-01-01" }}}
      ]
    }
  }
}
 
 
 
Filter type context
 
filter type 의 query 들은 document 를 조건에 따라 true/false 로 평가하여 true 인 것만 골라낸다.
 
_score 는 평가하지 않으며 boost 옵션을 지정하지 않으면 _score 는 모두 1점이 된다.
 
_더불어 cache 를 지원한다.
 
cache 는 일종의 index 로 아래와 같이 저장되는데
 
"title:test1" = [1, 0, 0]
"title:test2" = [0, 1, 0]
"title:test3" = [1, 1, 1]
"genre:test4" = [0, 0, 1]
 
이것의 의미는 아래와 같다.
 
field
term
document 1
document 2
document 3
document N
title
test1
1
0
0
title
test2
0
1
0
title
test3
1
1
1
genre
test4
0
0
1
 
 

'Monitoring > Elasticsearch' 카테고리의 다른 글

10. Joining Query  (0) 2020.01.17
09. Compound Query  (0) 2020.01.17
07. Analyzer  (1) 2020.01.17
06. Mapping  (0) 2020.01.17
05. Document  (0) 2020.01.17