AI/MLOps

[MLOps] Elasticsearch 쿼리 DSL (1) - Leaf Query(full text, term, range)

heeee__ya 2022. 5. 16. 13:20

 

Elasticsearch Query DSL(Domain Specific Language)

  엘라스틱서치에서 쿼리를 사용하는 방법은 쿼리 스트링(query string)과 쿼리 DSL 두 가지가 있다. 쿼리 스트링은 REST API의 URI 주소에 쿼리문을 작성하는 방식으로, 간단한 조건 검색 시 편리하다. 하지만 복잡한 논리 조건의 경우 괄호를 사용해야 해서, 조건이 복잡해질수록 가독성이 떨어지고 오류가 나기 쉽다. 한편, 쿼리 DSL은 엘라스틱서치에서 제공하는 쿼리 전용 언어이다. 쿼리 DSL은 REST API의 요청 본문 안에 JSON 형태로 쿼리를 작성하며, 엘라스틱서치의 모든 쿼리 스펙을 지원하기 때문에 검색을 수행함에 있어 대부분 이를 활용하게 된다.

 

# Query string
GET kibana_sample_data_ecommerce/_search?q=customer_full_name:Mary

# Query DSL
GET kibana_sample_data_ecommerce/_search
{
  "query": {
    "match": {
      "customer_full_name": "Mary"
    }
  }
}

 

  엘라스틱서치는 검색을 위해 크게 리프 쿼리(leaf query)복합 쿼리(compound query)를 지원한다.

 

출처 - https://medium.com/elasticsearch/introduction-to-elasticsearch-queries-b5ea254bf455

 

  • 리프 쿼리 : 특정 필드에서 용어를 찾는 쿼리
  • 복합 쿼리 : 쿼리를 조합해 사용되는 쿼리

이번 포스트에서는 리프 쿼리 중 가장 자주 쓰이는 전문 쿼리(full text query), 용어 수준 쿼리(term queries), 그리고 범위 쿼리(range query)에 초점을 맞춰 각각의 사용법을 설명해보겠다.

 

 

Leaf Query

1. 전문 쿼리 (full text query)

  전문 쿼리는 일반적으로 블로그처럼 텍스트가 많은 필드에서 특정 용어를 검색할 때 사용되며, 전문 검색을 할 필드는 인덱스 매핑 시 텍스트 타입으로 매핑하도록 권장된다. 

전문 쿼리 동작 과정

 

  먼저 sample_index 인덱스를 생성하고 contents 필드를 갖는 도큐먼트를 인덱싱한다. 텍스트 타입으로 매핑된 문자열은 분석기에 의해 토큰으로 분리되며, 마찬가지로 검색어인 'elastic search'도 분석기에 의해 토큰 단위로 분리된다. 그리고 토큰화된 검색어 ['elastic', 'search']와 토큰화된 도큐먼트 용어들 ['studying', 'elastic', 'stack']이 매칭되어 스코어를 계산하고 검색된다. 위의 예시에서는 match 쿼리를 사용하였는데, 다른 쿼리들도 함께 살펴보자.

 

1-1. match query

  전체 텍스트 중에서 특정 용어를 검색할 때 사용한다. 매치 쿼리를 사용하기 위해서는 검색하고 싶은 필드를 알아야 한다. 필드명을 모르면 쿼리를 짤 수가 없으므로 재빨리 _mapping을 통해 인덱스에 포함된 필드를 알아놓아야한다(...)

 

GET kibana_sample_data_ecommerce/_search
{
  "_source": "customer_first_name", # 해당 필드만 보여줌
  "query": {
    "match": {
      "customer_full_name": "Mary"
    }
  }
}

 

  참고로 분석기 종류에 따라 다르지만 일반적인 분석기를 사용했다면 대문자를 소문자로 변경하기 때문에 검색어를 'mary'로 바꿔도 같은 결과를 얻을 수 있다.

  그리고 매치 쿼리에서 용어들 간의 공백은 OR로 인식되기 때문에, 'mary bailey'로 검색한다면 'Mary Barber'나 'Abd Bailey' 등이 등장하게 된다. 이를 때는 operator 파라미터에 "and"를 넘겨주면 된다.

 

GET kibana_sample_data_ecommerce/_search
{
  "_source": "customer_full_name",
  "query": {
    "match": {
      "customer_full_name": {
        "query" : "mary bailey",
        "operator": "and"
      }
    }
  }
}

 

1-2. match phrase query

  (phrase)를 검색할 때 사용한다. 위에서 operator 파라미터를 통해 'mary'와 'bailey'가 모두 포함된 도큐먼트를 찾았는데, 매치 프레이즈 쿼리는 한 단계 더 나아가 용어의 순서까지 맞는 것만 찾아준다(가령 'mary ray bailey'나 'bailey mary'는 매칭되지 않는다)

 

GET kibana_sample_data_ecommerce/_search
{
  "_source": "customer_full_name",
  "query": {
    "match_phrase": {
      "customer_full_name": "mary bailey"
    }
  }
}

 

1-3. multi-match query

  지금까지 쿼리를 작성할 때는 반드시 필드명을 적었어야 했는데, 가끔 우리는 검색하고자 하는 용어나 구절이 정확히 어떤 필드에 있는지 모를 때가 있다. 가령 '트럼프'를 검색할 때 기사 제목 필드에 검색해야 할지, 사람 이름 필드에 검색해야 할지 정확히 모를수도 있다. 이럴 경우 하나의 필드가 아닌 여러 개의 필드에서 검색을 해야한다. 여러 개의 필드에서 검색을 하기 위한 멀티 매치 쿼리는 전문 검색의 일종으로, 텍스트 타입으로 매핑된 필드에서 사용하는 것이 좋다.

 

GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_first_name", "customer_last_name", "customer_full_name"],
  "query": {
    "multi_match": {
      "query": "mary",
      "fields": [
        "customer_first_name",
        "customer_last_name",
        "customer_full_name"]
        # 참고로 이름이 유사한 복수의 필드를 검색할 때는
        # * 와일드카드를 이용하면 편하다
        # "customer_*_name"을 "fields"에 지정해줘도 위와 같은 결과
    }
  }
}

 

 

 

2. 용어 수준 쿼리 (term queries)

  용어 수준 쿼리는 일반적으로 숫자, 날짜, 범주형 데이터를 정확하게 검색할 때 사용된다. 동작 과정에서 category 필드는 키워드 타입으로 매핑하는데, 키워드 타입은 인덱싱 과정에서 분석기를 사용하지 않는다. 분석되지 않은 검색어('elastic')과 분석되지 않은 도큐먼트 용어('Elastic')를 매칭하기 때문에, 아래의 그림에서는 'Elastic'과 'elastic'의 대소문자 차이로 매칭에 실패하게 된다.

 

위에서의 전문 쿼리와 비교해봤을 때, 용어 수준 쿼리는 정확한 용어를 검색할 때 사용해야 된다는 것을 알 수 있다. 키워드, 숫자형, 범위형 타입의 필드에서 정확히 일치하는 도큐먼트들을 검색할 때 유용하며, 강제성은 없지만 사용 시 주의하지 않으면 원하는 결과를 얻지 못할 수도 있다. 반면, 전문 쿼리는 텍스트 타입의 필드에서 검색어를 찾을 때 유용하다.

 

2-1. term query

  용어 수준 쿼리 중 가장 대표적인 쿼리이다. 사용 방법은 매치 쿼리와 비슷하지만 검색어가 분석기를 거치지 않는다는 점이 큰 차이이다. 'mary bailey'의 경우 전문 검색을 사용하면 분석기에 의해 ['mary', 'bailey']로 토큰화되고 'mary'나 'bailey'가 있는 경우 매칭되었다. 하지만 용어 검색은 용어 수준 쿼리에 속하기 때문에 'mary bailey'가 토큰화되지 않고 대소문자도 정확히 맞아야 매칭된다.

 

GET kibana_sample_data_ecommerce/_search
{
  "_source": "customer_full_name",
  "query": {
    "term": {
      "customer_full_name": "Mary Bailey"
    }
  }
}
## 검색 실패, 아무것도 매칭되지 않음

GET kibana_sample_data_ecommerce/_search
{
  "_source": "customer_full_name",
  "query": {
    "term": {
      "customer_full_name": "Mary"
    }
  }
}
## 마찬가지로 검색 실패

 

  첫 번째 쿼리로 도큐먼트를 아무것도 찾을 수 없는 이유는 customer_full_name 필드가 텍스트로 매핑되어 있기 때문이다. 즉, 'Mary Bailey'가 ['mary', 'bailey']로 토큰화되어 소문자로 저장되어 있는데 용어 쿼리는 'Mary Bailey'를 찾기 때문에 매칭되지 않는다. 두 번째 쿼리도 대문자가 소문자로 변경되어 매핑되어 'Mary'라는 검색어로는 아무것도 찾지 못한다.

  강제하지는 않지만 용어 쿼리는 키워드 타입으로 매핑된 필드에서 사용해야 한다. 마침 customer_full_name 필드를 _mapping으로 확인해보면 텍스트와 키워드 타입을 갖는 멀티 필드로 지정되어 있는 것을 알 수 있다. 키워드 타입인 customer_full_name.keyword 필드에 용어 쿼리를 요청해보자.

 

GET kibana_sample_data_ecommerce/_search
{
  "_source": "customer_full_name",
  "query": {
    "term": {
      "customer_full_name.keyword": "Mary Bailey"
    }
  }
}

 

2-2. terms query

  용어들 쿼리는 여러 용어들을 검색해준다. 마찬가지로 키워드 타입으로 매핑된 필드에서 사용해야 하며, 분석기를 거치지 않았기 때문에 대소문자도 신경써야 한다.

 

GET kibana_sample_data_ecommerce/_search
{
  "_source": "day_of_week",
  "query": {
    "terms": {
      "day_of_week": ["Saturday", "Sunday"]
    }
  }
}

 

 

 

3. 범위 쿼리 (range query)

  특정 날짜나 숫자의 범위를 지정해 범위 안에 포함된 데이터들을 검색할 때 사용된다.

 

  • 날짜/숫자/IP 타입의 데이터는 범위 쿼리 가능
  • 문자형/키워드 타입의 데이터는 범위 쿼리 불가능

쿼리에서 사용한 날짜/시간 포맷과 도큐먼트에 지정된 날짜/시간 포맷이 맞아야 검색이 가능하다. kibana_sample_data_flights 인덱스의 timestamp 필드는 yyyy-mm-dd 형식의 날짜/시간 포맷을 사용하기 때문에 범위 쿼리도 이에 맞춰 작성하면 된다(맞지 않을 시 parse_exception 오류 발생).

 

GET kibana_sample_data_flights/_search
{
  "query": {
    "range": {
      "timestamp": {
        "gte": "2022-05-10",
        "lte": "2022-05-11"
      }
    }
  }
}

 

 

 엘라스틱 서치의 범위 쿼리는 다음 네 가지 파라미터를 지원한다.

출처 : Elastic 가이드북(https://esbook.kimjmin.net/05-search/5.6-range)

 

위의 파라미터들을 조합해 데이터를 검색할 범위를 지정하면 된다.

 

 

 

 

 

자료 출처 : https://medium.com/elasticsearch/introduction-to-elasticsearch-queries-b5ea254bf455

https://esbook.kimjmin.net/05-search/5.6-range