데이터베이스/0 + Elasticsearch

[Elasticsearch] Elasticsearch 검색 심화 풀 텍스트 쿼리(Full Text Query)

힘들면힘을내는쿼카 2023. 12. 28. 01:08
728x90
반응형

Elasticsearch 검색 심화 풀 텍스트 쿼리(Full Text Query)

여기에 작성한 모든 내용은 Elastic 가이드 북를 참고하여 작성했습니다.
더 자세한 내용을 알고싶으면 해당 링크로 이동해주세요.^^

 

 

🔍 검색?

데이터 시스템의 검색은 수 많은 데이터 중에서 조건에 부합하는 데이터로 범위를 축소하는 행위 라고 합니다.

 

인터넷 쇼핑몰에 상품이 1,000만개가 있을 때 검색창에 "무선 이어폰" 이라고 입력해서 시스템에 있는 전체 1,000만개의 상품들 중 무선 이어폰과 연관된 상품만 추려내는 과정을 검색이라고 할 수 있습니다.

검색 엔진 설정에 따라 상품명이 정확히 "무선 이어폰" 인 것만 보여줄지, “애플 무선 이어폰" 처럼 전체 상품명 중에 검색어를 포함하기만 하면 보여줄지, 가격, 출시일 등과 같이 다른 조건들에 대해서는 어떻게 영향을 받도록 할 것인지 등을 결정할 수 있을 것입니다.

 

상품명이 정확히 "무선 이어폰" 인 것만 검색 하도록 조건을 엄격하게 하면 표시되는 결과 수가 적어져서 내가 찾는 상품이 나타나지 않을 수 있을 것입니다.

반대로 상품 설명에 "무선""이어폰" 이 하나라도 있는 상품을 모두 검색하도록 하면 "무선 리모컨", "이어폰 케이스" 같은 상품까지 검색이 되면서 결과가 너무 많아져서 내가 찾는 상품이 묻혀 버릴 수 있을 것입니다. 🥲

 

우리는 품질이 높은 검색 시스템을 구현하기 위해서는 이렇게 많은 부분들을 고민해야 합니다. 🤔

 

Elasticsearch 는 사용자가 이런 여러가지 검색 조건들에 대해 목표로 하는 검색 기능을 구현할 수 있도록 다양한 기능들을 제공합니다.

Elasticsearch 는 데이터를 실제로 검색에 사용되는 검색어텀(Term) 으로 분석 과정을 거쳐 저장하기 때문에 검색 시 대소문자, 단수나 복수, 원형 여부와 상관 없이 검색이 가능합니다.

이러한 Elasticsearch의 특징을 풀 텍스트 검색(Full Text Search) 이라고 하며 한국어로 전문 검색 이라고도 합니다.

 

QueryDSL?(Domain Specific Language)

Elasticsearch 는 검색을 위한 쿼리 기능을 제공합니다.

이런 데이터 시스템에서 제공하는 쿼리 기능을 Query DSL (Domain Specific Language) 이라고 이야기 하며 Elasticsearch 의 Query DSL 은 모두 json 형식으로 입력해야 합니다.^^

 

풀 텍스트 쿼리(Full Text Query)

먼저, 실습을 위해 5개의 도큐먼트를 저장하겠습니다.^^

POST toss-members/_bulk
{"index":{"_id":1}}
{"message":"The cute powerful soyoung"}
{"index":{"_id":2}}
{"message":"The cute powerful soyoung drinks hite beer"}
{"index":{"_id":3}}
{"message":"The cute powerful soyoung drinks cass beer"}
{"index":{"_id":4}}
{"message":"cute soyoung hite beer"}
{"index":{"_id":5}}
{"message":"cute terra beer"}

 

모든 도큐먼트를 검색하는 match_all

match_all별다른 조건 없이 해당 인덱스의 모든 도큐먼트를 검색하는 쿼리입니다.

검색 시 쿼리를 넣지 않으면 elasticsearch는 자동으로 match_all을 적용해서 해당 인덱스의 모든 도큐먼트를 검색합니다.

 

다음 두 요청은 동일한 응답을 받습니다.

Request

GET toss-members/_search

 

Request

GET toss-members/_search
{
  "query":{
    "match_all":{ }
  }
}

 

Response

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "toss-members",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "message" : "The cute powerful soyoung"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "message" : "The cute powerful soyoung drinks hite beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "message" : "The cute powerful soyoung drinks cass beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "message" : "cute soyoung hite beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "5",
        "_score" : 1.0,
        "_source" : {
          "message" : "cute terra beer"
        }
      }
    ]
  }
}

 

조건에 맞는 검색어를 검색하는 match

match 쿼리는 풀 텍스트 검색에 사용되는 가장 일반적인 쿼리입니다.
match 쿼리를 이용하여 toss-members 인덱스의 message 필드에 soyoung 이 포함되어 있는 모든 문서를 검색합니다.

Request

GET toss-members/_search
{
  "query":{
    "match": {
      "message": "soyoung"
    }
  }
}

 

Response
soyoung가 포함된 4개의 도큐먼트가 검색 결과로 반환되는 것을 확인할 수 있습니다.

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 0.3133171,
    "hits" : [
      {
        "_index" : "toss-members",
        "_id" : "1",
        "_score" : 0.3133171,
        "_source" : {
          "message" : "The cute powerful soyoung"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "4",
        "_score" : 0.3133171,
        "_source" : {
          "message" : "cute soyoung hite beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "2",
        "_score" : 0.24722677,
        "_source" : {
          "message" : "The cute powerful soyoung drinks hite beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "3",
        "_score" : 0.24722677,
        "_source" : {
          "message" : "The cute powerful soyoung drinks cass beer"
        }
      }
    ]
  }
}

 

여러개 검색어를 넣으려면? 🤔

match 검색에 여러 개의 검색어를 집어넣게 되면 디폴트로 OR 조건으로 검색이 되어 입력된 검색어 별로 하나라도 포함된 모든 문서를 모두 검색합니다.

다음은 검색어로 powerful soyoung을 검색 한 결과입니다.

Request

GET toss-members/_search
{
  "query":{
    "match": {
      "message": "powerful soyoung"
    }
  }
}

 

Response
powerful과 soyoung 중 하나라도 포함된 4개의 도큐먼트가 검색 결과로 반환되는 것을 확인할 수 있습니다.

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 0.90034294,
    "hits" : [
      {
        "_index" : "toss-members",
        "_id" : "1",
        "_score" : 0.90034294,
        "_source" : {
          "message" : "The cute powerful soyoung"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "2",
        "_score" : 0.71042687,
        "_source" : {
          "message" : "The cute powerful soyoung drinks hite beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "3",
        "_score" : 0.71042687,
        "_source" : {
          "message" : "The cute powerful soyoung drinks cass beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "4",
        "_score" : 0.3133171,
        "_source" : {
          "message" : "cute soyoung hite beer"
        }
      }
    ]
  }
}

 

여러개의 검색어를 모두 다 포함하려면?

powerful과 soyoung을 모두 포함한 검색 결과를 얻고 싶으면 AND 연산자를 사용하여 아래와 같이 요청하면 됩니다.

Request

GET toss-members/_search
{
  "query":{
    "match": {
      "message": {
        "query": "powerful soyoung",
        "operator": "and"
      }
    }
  }
}

 

Response

{
  "took" : 6,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 0.90034294,
    "hits" : [
      {
        "_index" : "toss-members",
        "_id" : "1",
        "_score" : 0.90034294,
        "_source" : {
          "message" : "The cute powerful soyoung"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "2",
        "_score" : 0.71042687,
        "_source" : {
          "message" : "The cute powerful soyoung drinks hite beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "3",
        "_score" : 0.71042687,
        "_source" : {
          "message" : "The cute powerful soyoung drinks cass beer"
        }
      }
    ]
  }
}

 

공백을 포함해 정확히 일치하는 내용을 검색하는 match_phrase

match 쿼리에서 quick 과 dog 검색어를 AND 조건으로 검색하는 방법을 알아보았습니다.
그런데 "cute soyoung" 라는 구문을 공백을 포함정확히 일치하는 내용을 검색하려면 어떻게 해야 할까요?

 

바로 match_phrase 쿼리를 사용하면 됩니다.!!!

 

match_phrase 쿼리는 입력된 검색어를 순서까지 고려하여 검색을 수행합니다.
다음은 cute soyoung 라는 구문을 검색하는 match_phrase 쿼리입니다.

 

Request

GET toss-members/_search
{
  "query":{
    "match_phrase": {
      "message": {
        "query": "cute soyoung"
      }
    }
  }
}

 

Response
“cute soyoung 라는 정확한 문장이 포함된 도큐먼트 1개만 검색이 되었습니다.

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.40808195,
    "hits" : [
      {
        "_index" : "toss-members",
        "_id" : "4",
        "_score" : 0.40808195,
        "_source" : {
          "message" : "cute soyoung hite beer"
        }
      }
    ]
  }
}

 

검색 단어 사이에 다른 단어가 끼어들게 허용하는 slop

match_phrase 쿼리는 slop 이라는 옵션을 이용하여 slop에 지정된 값 만큼 단어 사이에 다른 검색어가 끼어드는 것을 허용할 수 있습니다.


slop을 1로 하고 검색을 하면 다음과 같은 결과가 나옵니다.

 

Request

GET toss-members/_search
{
  "query":{
    "match_phrase": {
      "message": {
        "query": "cute soyoung",
        "slop": 1
      }
    }
  }
}

 

Response
cute와 soyoung 사이에 한 단어가 끼어들어도 검색 결과로 반환되는 것을 확인할 수 있습니다.

{
  "took" : 6,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 0.40808195,
    "hits" : [
      {
        "_index" : "toss-members",
        "_id" : "4",
        "_score" : 0.40808195,
        "_source" : {
          "message" : "cute soyoung hite beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "1",
        "_score" : 0.2711597,
        "_source" : {
          "message" : "The cute powerful soyoung"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "2",
        "_score" : 0.20007902,
        "_source" : {
          "message" : "The cute powerful soyoung drinks hite beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "3",
        "_score" : 0.20007902,
        "_source" : {
          "message" : "The cute powerful soyoung drinks cass beer"
        }
      }
    ]
  }
}

 

slop을 너무 크게 하면 검색 범위가 넓어져 관련이 없는 결과가 나타날 확률도 높아지기 때문에 1 이상은 사용하지 않는 것을 권장 합니다.^^

 

query_string

URL검색에 사용하는 루씬의 검색 문법을 본문 검색에 이용하고 싶을 때 query_string 쿼리를 사용할 수 있습니다.

아래는 message 필드에서 cuteterra을 모두 포함하거나 또는 “beer” 구문을 포함하는 도큐먼트를 검색하는 쿼리입니다. (match_phrase 처럼 구문 검색을 할 때는 검색할 구문을 쌍따옴표 " 안에 넣습니다.)

Request

GET toss-members/_search
{
  "query":{
    "query_string": {
      "default_field": "message",
      "query": "(cute AND terra) OR \"beer\""
    }
  }
}

 

Response
cute와 terra를 모두 포함하거나 beer를 포함하는 도큐먼트를 검색 결과로 반환하는 것을 확인할 수 있습니다.

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 2.105529,
    "hits" : [
      {
        "_index" : "toss-members",
        "_id" : "5",
        "_score" : 2.105529,
        "_source" : {
          "message" : "cute terra beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "4",
        "_score" : 0.3133171,
        "_source" : {
          "message" : "cute soyoung hite beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "2",
        "_score" : 0.24722677,
        "_source" : {
          "message" : "The cute powerful soyoung drinks hite beer"
        }
      },
      {
        "_index" : "toss-members",
        "_id" : "3",
        "_score" : 0.24722677,
        "_source" : {
          "message" : "The cute powerful soyoung drinks cass beer"
        }
      }
    ]
  }
}

 

참고

 

 

 

728x90
반응형