ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Elasticsearch] DSL 살펴보기
    DB/Elasticsearch 2019. 12. 1. 17:53

    DSL이란 Domain Specific Language 으로 JSON에 기반한 질의입니다. ES에서의 DSL은 데이터베이스의 SQL문과 동일하다고 이해하면 쉽습니다. 사용자가 원하는 데이터를 추출하기 위한 질의 언어입니다.


    아래는 Elastic search에 질의를 하여 원하는 데이터를 추출하기 위한 DSL 예시입니다. 기본적으로 질의를 위해선 아래와 같이 "query" 속성을 사용합니다.

    GET /조회할 인덱스명/_search/ 
    {
      "query": { 
        ...
      }
    }

    Query VS Filter

    DSL 기능이 어떤게 있는지 설명에 앞서 query와 filter간 차이점을 먼저 설명합니다.

    기본적으로 Elastic search는 일치하는 검색 결과의 관련성을 점수 별로 정렬하여 각 문서가 쿼리와 얼마나 잘 일치하는지 측정합니다. 

    관련성 점수는 소수점 숫자이며 검색 API의 _score 메타 필드에 반환됩니다. _score가 높을수록 문서의 관련성이 높아집니다. 각 쿼리 유형은 관련성 점수를 다르게 계산할 수 있지만 _score 계산은 query나 filter 문에서 실행되는 것에 따라 달라집니다.

    Query

    query문은 "이 문서가 이 쿼리 절과 얼마나 잘 일치합니까?"라는 질문에 대답합니다. 문서가 일치하는지 여부를 결정하는 것 외에도 query문은 _score 메타 필드에서 관련성 점수를 계산합니다.

    Filter

    filter문은 query문과 같이 "이 문서 가 이 쿼리 절과 일치합니까?"라는 질문에 대답합니다. 차이점은 score를 계산하지 않고 간단하게 예 또는 아니요 로 계산합니다. filter문은 주로 구조화 된 데이터를 필터링하는 데 사용됩니다. 예를 들어, "등록 시간이 2018년에서 2019년 사이에 있는 데이터 나 데이터의 상태 값이 삭제가 안 된 데이터" 와 같이 문서의 score가 필요없는 데이터 형태를 필터링할 때 사용됩니다. 또한 Elasticsearch는 filter 문을 사용하게 되면 자동으로 캐싱하여 성능을 향상 시킵니다.

    score를 계산하지 않기 때문에 query보다 검색이 빠릅니다.

    아래는 query와 filter를 비교한 표입니다.



    QueryFilter
    검색 결과관련성Yes or No
    검색 범위전문 검색주어진 질의에 해당하는 정확한 값
    캐시여부NoYes
    관련성 점수 계산YesNo


    예시

    GET /test-index/_search 
    {
      "query": {
        "bool": { (1)      
          "must": [{
            "match": {
              "title": "Search"
            }
          }, {
            "match": {
              "content": "Elasticsearch"
            }
          }],
          "filter": [ (2)
            {
            "term": {
              "status": "published"
            }
          }, {
            "range": {
              "publish_date": {
                "gte": "2015-01-01"
              }
            }
          }]
        }
      }
    }

    1 bool 하위 두 개의 match 절은 query문을 사용하므로 각 문서가 얼마나 잘 일치하는지 score를 매기는 데 사용됩니다. 

    filter 하위의 term과 range 절은 필터 문에서 사용됩니다. 위 조건에 일치하는 문서는 score에 영향을 미치지 않고 결과를 반환합니다.


    검색하고자 하는 용도에 따라 query와 filter를 유연하게 사용해야 좋은 성능을 낼 수 있습니다.

    Query

    용어들을 설명하기에 앞서 "여러개의 물건들" 이라는 문장을 형태소 분석기에 의해 아래와 같이 쪼개졌다고 가정합니다.

    GET /test-index/_analyze 
    {
      "analyzer": "analyzer_mixed",
      "text": "여러개의 물건들"
    } {
      "tokens": [{
        "token": "여러",
        "start_offset": 0,
        "end_offset": 2,
        "type": "MM",
        "position": 0
      }, {
        "token": "개",
        "start_offset": 2,
        "end_offset": 3,
        "type": "NNB",
        "position": 1
      }, {
        "token": "물건",
        "start_offset": 5,
        "end_offset": 7,
        "type": "NNG",
        "position": 2
      }, {
        "token": "물건들",
        "start_offset": 5,
        "end_offset": 8,
        "type": "NNG",
        "position": 2
      }]
    }

    term

    term은 형태소 분석기에 의해 쪼개진 토큰들을 기반으로 동작합니다. 즉, term은 주어진 질의문과 저장된 형태소의 토큰이 정확하게 일치하는 문장을 찾습니다.

    GET /test-index/_search 
    {
      "query": {
        "term": {
          "name": "여러"
        }
      }
    }
    // 토큰 중에 "여러"가 있으므로 검색 결과 있음 
    
    GET /test-index/_search 
    {
      "query": {
        "term": {
          "name": "여러개"
        }
      }
    }
    // 토큰 중에 "여러개"가 없고 "여러"와 "개"로 나눠져 있으므로 검색 결과 없음


    이러한 이유로 검색하고자 하는 데이터 타입이 텍스트 필드라면 term 쿼리를 사용하는 것을 지양하고 match 쿼리를 사용하는 것이 좋습니다.

    terms

    term의 경우, 질의문이 1개만 가능하지만 terms는 여러 개의 질의문을 사용할 수 있습니다.

    GET /test-index/_search
    {
      "query": {
        "terms": {
          "name": ["여러", "개"]
        }
      }
    }

    match

    기본 동작은 term과 동일하게 형태소 분석기에 의해 쪼개진 토큰들을 기반으로 동작합니다. 차이점은 match는 주어진 질의를 형태소 분석기를 거쳐 쪼갠 다음 조회를 합니다. 예를 들어, "여러개" 라는 값이 들어 왔다면 형태소 분석기에 의해 "여러", "개"로 나눠지고 토큰을 조회를 하게 됩니다. 이 때, 1개라도 존재하는 토큰이 있으면 결과를 반환합니다. 즉, 형태소 분석기에 의해 쪼개진 질의들을 or 조건으로 검색하게 됩니다. 

    GET /test-index/_search 
    {
      "query": {
        "match": {
          "name": "여러개"
        }
      }
    }
    // "여러개" 라는 질의를 형태소 분석기를 통해 "여러", "개"로 쪼갠 후, 검색. 
    GET /test-index/_search 
    {
      "query": {
        "match": {
          "name": {
            "query": "주름원",
            "operator": "and"
          }
        }
      }
    }
    // operator: "and"를 명시하면 쪼개진 질의문들이 전부 일치해야만 됨. 이 때, 중괄호가 한 번 더 들어가게 됨

    multi_match

    여러 필드에 단일 질의문을 검색할 때 사용합니다.

    GET /test-index/_search
    {
      "query": {
        "multi_match": {
          "fields": ["name", "category"],
          "query": "원피스"
        }
      }
    }

    match_phase

    match_phase는 보통 문장을 검색할 때 사용합니다. match와 동일하게 형태소 분석기에 의해 토큰으로 쪼개집니다. 이 때, 차이점은 match의 경우는 순서와 상관없이 보통 1개 이상 일치하는 토큰이 있으면 검색 결과를 반환하는 반면에 match_phase는 입력된 질의와 저장된 토큰의 순서가 정확하게 일치해야 됩니다.

    GET /test-index/_search
    {
      "query": {
        "bool": {
          "must": [{
            "match_phrase": {
              "content": "여러 개"
            }
          }]
        }
      }
    }
    // "여러"와 "개"가 존재하고 토큰의 순서도 맞으므로 결과 반환 
    
    
    GET /test-index/_search 
    {
      "query": {
        "bool": {
          "must": [{
            "match_phrase": {
              "content": "물건 여러 개"
            }
          }]
        }
      }
    }
    // 저장된 토큰의 순서는 "여러", "개", "물건" 순으로 순서가 맞지 않아 결과 반환 X 
    GET /test-index/_search
    {
      "query": {
        "bool": {
          "must": [{
            "match_phrase": {
              "content": "내 물건"
            }
          }]
        }
      }
    }
    // "내"라는 토큰이 저장되어 있지 않아 결과 반환 X

    Bool

    bool 쿼리는 다른 쿼리들을 조합하여 결과와 score들을 결합하거나 동작을 변경합니다. 즉, bool 하위의 여러 쿼리들을 결합하여 결과를 낼 때 사용합니다. bool은 쿼리와 필터에서의 동작이 상이합니다. 쿼리로 사용할 때는 score가 결합되어 일치하는 절이 많을 수록 더 좋습니다.

    bool의 기본 쿼리는 must, must_not, should가 있습니다.

    • must:  모든 필터가 매치되어야 함 (and)
    • filter: 위에서 설명한 filter와 동작은 동일. score를 무시하고 결과가 캐싱됨
    • must_not: 문사거 필터에 매치되지 않아야 함 (not). 필터 절에서 실행되므로 score가 무시되고 캐싱이 고려. score가 무시되므로 score는 0으로 반환.
    • should: 최소 minimum_should_match개의 필터에 매치되어야 함 (or)
      • minimum_should_match: should에서 최소 match 개의 수 (기본값 1 - must가 함께 사용되면 기본값 0)
    GET /test-index/_search
    {
      "query": {
        "bool": {
          "must": {
            "term": {
              "user": "kimchy"
            }
          },
          "filter": {
            "term": {
              "tag": "tech"
            }
          },
          "must_not": {
            "range": {
              "age": {
                "gte": 10,
                "lte": 20
              }
            }
          },
          "should": [{
              "term": {
                "tag": "wow"
              }
            },
            {
              "term": {
                "tag": "elasticsearch"
              }
            }
          ],
          "minimum_should_match": 1,
        }
      }
    }

    query_string

    syntax를 강력하게 체크하는 파서를 사용하여 제공된 쿼리 문자열을 기반으로 문서를 반환

    이 쿼리는 syntax를 사용하여 AND 또는 NOT과 같은 연산자를 기반으로 제공된 쿼리 문자열에 대해 질의문을 분석하고 분할합니다. 그런 다음 일치하는 문서를 반환하기 전에 앞에서 분석하고 분할한 각 텍스트를 독립적으로 분석합니다. query_string 조회를 사용하여 와일드 카드, 다중 필드 검색 등을 포함하는 복잡한 검색을 작성할 수 있습니다. 여러 방면에서 좋지만 쿼리의 유효성 검사는 엄격하고  쿼리 문자열에 유효하지 않은 구문이 포함 된 경우 오류를 반환합니다.

    잘못된 구문에 대해서는 오류를 반환하므로 검색 창에 query_string 검색어를 사용하지 않는 것이 좋습니다.

    쿼리 syntax를 지원할 필요가 없으면 match 쿼리를 사용하는 것이 좋습니다. 만약 쿼리 syntax의 기능이 필요한 경우에는 덜 엄격한 simple_query_string 쿼리를 사용하는 것이 좋습니다.


    GET /_search
    {
      "query": {
        "query_string": {
          "query": "(new york city) OR (big apple)",
          "default_field": "content"
        }
      }
    }

    다음 검색을 실행하면 query_string 쿼리가 (new york city) 와 (big apple) 두 부분으로 나뉩니다. content 필드의 분석기는 일치하는 문서를 반환하기 전에 new york city와 big apple을 각각 토큰으로 변환합니다. 쿼리 syntax는 공백을 연산자로 사용하지 않으므로 new york city는 그대로 분석기에 전달됩니다.


    query_string 은 아래와 같은 옵션을 가지고 있습니다.

    • query: 검색 하고자 하는 질의
    • default_field: query의 적용 대상 필드. 정의되지 않으면 인덱스의 모든 필드에 적용
    • allow_leading_wildcard: 기본값으로 true이며, true일 경우, query string의 첫 문자열에 *나 ?이 올 수 있음
    • analyze_wildcard: 기본값으로 false이며 true일 경우, wildcard terms을 분석

    • fields: 어레이 필드로 여러 필드에 query string을 적용할 수 있음

    simple_query_string

    query_string에 비해 좀 더 유연한 parser를 사용하여 제공된 쿼리 문자열을 기반으로 문서를 반환

    이 쿼리는 simple syntax를 사용하여 제공된 쿼리 문자열을 특수 연산자를 기반으로 구성된 질의문을 분석하고 분할합니다. 그런 다음 쿼리는 일치하는 문서를 반환하기 전에 앞에서 분석하고 분할한 각 텍스트를 독립적으로 분석합니다. simple_query_string은 query_string 쿼리보다 제한적이지만 잘못된 syntax에 대한 오류를 반환하지 않고 쿼리 문자열의 유효하지 않은 부분을 무시합니다.


    GET /_search
    {
      "query": {
        "simple_query_string": {
          "query": "\"fried eggs\" +(eggplant | potato) -frittata",
          "fields": ["title^5", "body"],
          "default_operator": "and"
        }
      }
    }

    query_string의 옵션과 대부분 유사합니다.

    필터

    필터 쿼리는 사실상 위의 bool에서 설명이 됐습니다. 현재 elastic search에서는 filtered 문법이 사라지고 bool 쿼리를 사용하도록 변경됐습니다.

    ## ES 7.4 기준으로 더이상 사용하지 않음
    GET /_search
    {
      "query": {
        "filtered": {
          "query": {
            "match": {
              "text": "quick brown fox"
            }
          },
          "filter": {
            "term": {
              "status": "published"
            }
          }
        }
      }
    }
     ## 아래와 같이 사용해야함 GET /_search
    {
      "query": {
        "bool": {
          "must": {
            "match": {
              "text": "quick brown fox"
            }
          },
          "filter": {
            "term": {
              "status": "published"
            }
          }
        }
      }
    }


    댓글