2.X/2. Search in Depth

2-1-1. Finding Exact Values

drscg 2017. 9. 30. 02:29

When working with exact values, you will be working with non-scoring, filtering queries. Filters are important because they are very fast. They do not calculate relevance (avoiding the entire scoring phase) and are easily cached. We’ll talk about the performance benefits of filters later in All About Caching, but for now, just keep in mind that you should use filtering queries as often as you can.

exact value로 연산시에는, non-scoring인 filter를 사용한다 filter는 매우 매우 빠르기 때문에 중요하다. filter는 relevance를 계산하지 않고(전체 score 계산절을 피하여), 확실히 cache된다. 나중에 All About Caching에서, filter의 성능상의 장점에 대해 이야기할 것이다. 하지만 지금은, 가능한 한 자주 filter를 사용해야 한다는 것만 기억하자.

term Query with Numbersedit

We are going to explore the term query first because you will use it often. This query is capable of handling numbers, booleans, dates, and text.

term query가 자주 사용되니 먼저 살펴보자. 이 query는 number, boolean, date 그리고 텍스트를 다룰 수 있다.

We’ll start by indexing some documents representing products, each having a price and productID:

상품을 나타내는 몇 개의 document를 색인하면서 시작하자. 각각은 price 와 productID 를 가지고 있다.

POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }

Our goal is to find all products with a certain price. You may be familiar with SQL if you are coming from a relational database background. If we expressed this query as an SQL query, it would look like this:

목표는 특정 가격의 모든 상품을 찾는 것이다. RDB를 사용해 보았다면, SQL에 익숙할 것이다. 이 query를 SQL로 표현하면, 아래와 같다.

SELECT document
FROM   products
WHERE  price = 20

In the Elasticsearch query DSL, we use a term query to accomplish the same thing. The term query will look for the exact value that we specify. By itself, a term query is simple. It accepts a field name and the value that we wish to find:

Elasticsearch query DSL에서는, 동일한 동작을 위해, term query를 사용한다. term query는 지정한 정확한 값을 찾는다. term query 자체는 매우 간단하다. field의 이름과, 찾으려는 값을 주면 된다.

{
    "term" : {
        "price" : 20
    }
}

Usually, when looking for an exact value, we don’t want to score the query. We just want to include/exclude documents, so we will use a constant_score query to execute the term query in a non-scoring mode and apply a uniform score of one.

일반적으로, exact value를 찾으려할 때, query의 score 계산을 원하지는 않을 것이다. 단지 document의 포함/배제를 원할 뿐이다. 따라서, non-scoring mode에서 term query를 실행하기 위해, constant_scorequery를 사용하여, 일정한 score를 제공해야 한다.

The final combination will be a constant_score query which contains a term query:

마지막 조합은 term query를 포함하는 constant_score query이다.

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : { 
            "filter" : {
                "term" : { 
                    "price" : 20
                }
            }
        }
    }
}

filter 내의 term query를 변경하기 위해 constant_score 를 사용한다.

이전에 보았던 term query

Once executed, the search results from this query are exactly what you would expect: only document 2 is returned as a hit (because only 2 had a price of 20):

실행해 보면, 이 query의 검색 결과는 기대했던 것과 일치한다. document 2 만 20 이라는 가격을 가지고 있기 때문에, document 2 만, hit로 반환된다.

"hits" : [
    {
        "_index" : "my_store",
        "_type" :  "products",
        "_id" :    "2",
        "_score" : 1.0, 
        "_source" : {
          "price" :     20,
          "productID" : "KDKE-B-9947-#kL5"
        }
    }
]

filter 내에 있는 query는 relevance나 score를 계산하지 않는다. 따라서, 모든 결과는 중립적인 score 1 을 받는다.

term Query with Textedit

As mentioned at the top of this section, the term query can match strings just as easily as numbers. Instead of price, let’s try to find products that have a certain UPC identification code. To do this with SQL, we might use a query like this:

위에서 언급했듯이, term query는 문자열도 number처럼 쉽게 일치할 수 있다. price 대신, 특정 UPC 식별 코드를 가진 상품을 찾아 보자. 이를 SQL로 표현해 보면, 아래와 같다.

SELECT product
FROM   products
WHERE  productID = "XHDK-A-1293-#fJ3"

Translated into the query DSL, we can try a similar query with the term filter, like so:

query DSL로 바꾸면, term filter를 가진, 간단한 query를 만들 수 있다.

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "term" : {
                    "productID" : "XHDK-A-1293-#fJ3"
                }
            }
        }
    }
}

Except there is a little hiccup: we don’t get any results back! Why is that? The problem isn’t with the term query; it is with the way the data has been indexed. If we use the analyze API (Testing Analyzers), we can see that our UPC has been tokenized into smaller tokens:

그런데, 약간 문제가 있다. 어떤 결과도 받을 수 없다. 왜 그럴까? term query에는 실제로 문제가 없다. 문제는 데이터가 색인된 방법이다. analyze API(Testing Analyzers)를 사용해 보면, UPC가 더 작은 token으로 나누어져 있음을 알 수 있다.

GET /my_store/_analyze
{
  "field": "productID",
  "text": "XHDK-A-1293-#fJ3"
}
{
  "tokens" : [ {
    "token" :        "xhdk",
    "start_offset" : 0,
    "end_offset" :   4,
    "type" :         "<ALPHANUM>",
    "position" :     1
  }, {
    "token" :        "a",
    "start_offset" : 5,
    "end_offset" :   6,
    "type" :         "<ALPHANUM>",
    "position" :     2
  }, {
    "token" :        "1293",
    "start_offset" : 7,
    "end_offset" :   11,
    "type" :         "<NUM>",
    "position" :     3
  }, {
    "token" :        "fj3",
    "start_offset" : 13,
    "end_offset" :   16,
    "type" :         "<ALPHANUM>",
    "position" :     4
  } ]
}

There are a few important points here:

여기에 몇 가지 중요한 것이 있다.

  • We have four distinct tokens instead of a single token representing the UPC.

    UPC를 나타내는 단일 token이 아닌 4 개의 유일한 token이 있다.

  • All letters have been lowercased.

    모든 문자가 소문자이다.

  • We lost the hyphen and the hash (#) sign.

    -(hyphen)과 #(hash) 문자가 없어졌다.

So when our term query looks for the exact value XHDK-A-1293-#fJ3, it doesn’t find anything, because that token does not exist in our inverted index. Instead, there are the four tokens listed previously.

term query가 정확한 값, XHDK-A-1293-#fJ3 을 찾는 경우, 해당 token이 inverted index에 존재하지 않기 때문에, 아무것도 발견할 수 없다. 대신에, 위의 4개의 token이 있다.

Obviously, this is not what we want to happen when dealing with identification codes, or any kind of precise enumeration.

물론, 이것은 식별 코드나, 정확한 배열의 모든 종류를 처리할 할 때, 여러분이 원하던 것이 아니다.

To prevent this from happening, we need to tell Elasticsearch that this field contains an exact value by setting it to be not_analyzed. We saw this originally in Customizing Field Mappings. To do this, we need to first delete our old index (because it has the incorrect mapping) and create a new one with the correct mappings:

이런 일을 방지하기 위해, 이 field에 not_analyzed를 설정하여, Elasticsearch에게 이 field가 exact value를 가지고 있다는 것을 알려야 한다. Customizing Field Mappings에서, 이것을 설명한 바 있다. 이를 위해, 먼저 기존 index를 지우고(올바르지 않은 mapping을 가지고 있기 때문에), 올바른 mapping을 가진 새로운 index를 생성한다.

DELETE /my_store 

PUT /my_store 
{
    "mappings" : {
        "products" : {
            "properties" : {
                "productID" : {
                    "type" : "string",
                    "index" : "not_analyzed" 
                }
            }
        }
    }

}

기존 index의 mapping을 변경할 수 없기 때문에, 먼저 index를 지워야 한다.

index가 지워지면, 사용자 정의 mapping을 가진 index를 다시 생성한다.

여기에서 productID 를 분석하지 말라고, 명확하게 지정한다.

Now we can go ahead and reindex our documents:

이제 document를 다시 색인하고, 진행할 수 있다.

POST /my_store/products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10, "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20, "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30, "productID" : "QQPX-R-3956-#aD8" }

Only now will our term query work as expected. Let’s try it again on the newly indexed data (notice, the query and filter have not changed at all, just how the data is mapped):

이제 term query가 기대했던 대로 동작할 것이다. 새로 색인한 데이터로, 다시 시도해 보자. query와 filter는 전혀 변경하지 않았고, 단지 데이터의 mapping만 변경했다는 점을 기억하자.

GET /my_store/products/_search
{
    "query" : {
        "constant_score" : {
            "filter" : {
                "term" : {
                    "productID" : "XHDK-A-1293-#fJ3"
                }
            }
        }
    }
}

Since the productID field is not analyzed, and the term query performs no analysis, the query finds the exact match and returns document 1 as a hit. Success!

productID field는 분석되지 않기 때문에, term query는 어떠한 분석도 하지 않는다. query는 정확히 일치하는 document 1을 hit 로 반환한다. 성공이다.

Internal Filter Operationedit

Internally, Elasticsearch is performing several operations when executing a non-scoring query:

내부적으로, Elasticsearch 는 non-scoring query를 실행할 때, 여러 가지 연산을 실행한다.

  1. Find matching docs.

    일치하는 document를 찾는다.

    The term query looks up the term XHDK-A-1293-#fJ3 in the inverted index and retrieves the list of documents that contain that term. In this case, only document 1 has the term we are looking for.

    term query는 inverted index에서 XHDK-A-1293-#fJ3 라는 단어를 찾고, 해당 단어를 포함하는 document의 목록을 가져온다. 이 경우에는, 찾는 단어는 document 1에만 있다.

  2. Build a bitset.

    bitset을 만든다.

    The filter then builds a bitset--an array of 1s and 0s—that describes which documents contain the term. Matching documents receive a 1 bit. In our example, the bitset would be [1,0,0,0]. Internally, this is represented as a "roaring bitmap", which can efficiently encode both sparse and dense sets.

    그 다음에, filter는 document가 그 단어를 포함하고 있다는 것을 나타내는 bitset(1과 0의 배열)을 만든다. 일치하는 document는 bit 1 을 가진다. 예제에서, bitset은 [1,0,0,0] 일 것이다. 내부적으로, 이것은 "roaring bitmap" 으로 나타내는데, 이것은 희박(sparse)하거나 조밀(dense)한 집합 양쪽 모두를 효과적으로 인코드할 수 있다.

  3. Iterate over the bitset(s)

    bitset을 반복한다

    Once the bitsets are generated for each query, Elasticsearch iterates over the bitsets to find the set of matching documents that satisfy all filtering criteria. The order of execution is decided heuristically, but generally the most sparse bitset is iterated on first (since it excludes the largest number of documents).

    일단, 각 query에 대해 bitset이 생성되면, Elasticsearch는 모든 filtering 조건을 만족하는 일치하는 document를 찾기 위해 bitset을 반복한다. 실행 순서는 경험적으로 결정된다. 그러나, 일반적으로 가장 희박한 bitset이 먼저 반복된다. (먾은 수의 문서를 배제할 수 있기 때문이다.)

  4. Increment the usage counter.

    사용 빈도수를 증가시킨다.

    Elasticsearch can cache non-scoring queries for faster access, but it’s silly to cache something that is used only rarely. Non-scoring queries are already quite fast due to the inverted index, so we only want to cache queries we know will be used again in the future to prevent resource wastage.

    Elasticsearch는 더 빠른 access를 위해 non-scoring query를 cache할 수 있다. 그러나, 드물게 사용되는 것을 cache하는 것은 비합리적이다. non-scoring query는 inverted index 때문에 이미 굉장히 빠르다. 따라서, 자원 낭비를 막기 위해, 미래에 다시 사용될 것을 _알고_ 있는 query만 cache하기를 원할 것이다.

    To do this, Elasticsearch tracks the history of query usage on a per-index basis. If a query is used more than a few times in the last 256 queries, it is cached in memory. And when the bitset is cached, caching is omitted on segments that have fewer than 10,000 documents (or less than 3% of the total index size). These small segments tend to disappear quickly anyway and it is a waste to associate a cache with them.

    이를 위해, Elasticsearch는 index별로 query의 기록을 추적한다. 어떤 query가 최근 256개의 query에서 몇 번 이상 사용했다면, 그것은 memory에 cache된다. 그리고, bitset이 cache될 때, 10,000개 이하의 document를 가지고 있는 (또는 전체 index 크기의 3% 미만인) segment에서는 caching은 생략된다. 이들 작은 segment는 어쨌든 빠르게 사라질 것이고, 그들dmf cache와 관련시키는 것은 낭비이다.

Although not quite true in reality (execution is a bit more complicated based on how the query planner re-arranges things, and some heuristics based on query cost), you can conceptually think of non-scoring queries as executing before the scoring queries. The job of non-scoring queries is to reduce the number of documents that the more costly scoring queries need to evaluate, resulting in a faster search request.

실세계에서는 사실이 아닐지도 모르지만(실행은 query 설계자의 재배치에 때라 약간 더 복잡해지고, query 비용에 따라 약간은 경험적이다.), 여러분들은 개념적으로 scoring query 이전에 non-scoring query의 실행을 생각할 수 있다. non-scoring query 작업은 더 많은 비용이 소모되는 scoring query가 평가해야할 document의 수를 줄여서, 더 빠른 search request가 되도록 하는 것이다.

By conceptually thinking of non-scoring queries as executing first, you’ll be equipped to write efficient and fast search requests.

개념적으로, non-scoring query를 먼저 실행하면, 효과적이고 빠른 search request를 가지게 될 것이다.


'2.X > 2. Search in Depth' 카테고리의 다른 글

2. Search in Depth  (0) 2017.09.30
2-1. Structured Search  (0) 2017.09.30
2-1-2. Combining Filters  (0) 2017.09.30
2-1-3. Finding Multiple Exact Values  (0) 2017.09.30
2-1-4. Ranges  (0) 2017.09.30