티스토리 뷰

Query Lite

검색 본문이 없이도 검색 요청이 가능한 형태.
curl같은 명령어들을 사용할 때 URL 안에 모든 것을 다 집어 넣으면 훨씬 효율적일 것이다.
예를 들면 다음과 같은 형태이다.

  • /movies/_search?q=title:star
    • 영화 제목에 star가 포함되어 있는 검색 결과를 리턴
  • /movies/_search?q=+year:>2010+title:trek
    • 개봉 연도가 2010년 이후이고, 영화 제목이 trek이 포함되는 검색 결과 리턴
  • ?q= 는 쿼리를 하겠다는 뜻으로, 뒤의 내용을 질의하겠다는 의미이다.
  • + 연산자를 사용하여 위의 예시와 같은 boolean 연산을 수행할 수 있으며, 관계형 쿼리도 사용할 수 있다.
    • 여기서의 관계형은 상대적인 크기를 의미한다. 관계형 데이터베이스에서의 관계가 아니다.
  • URL을 사용해 안전하게 인터넷망을 통해 전송할 수 있도록 인코딩해야한다. 이는 결국 가독성을 떨어뜨리게 된다.
    • 따라서 URL 사용으로 신속함의 가치를 얻을 수 있지만 가독성은 떨어지는 trade off가 발생한다.

또한 다음의 이유로 중요한 작업에서는 query lite를 사용해서는 안된다.

  • 복잡하고 디버깅이 어렵다.
    • 변수가 생기기 쉽고 빠르게 변질이 되기도 한다.
  • 보안에 취약하다.
    • 모든 사용자가 URL에 검색 쿼리 문자열을 쉽게 사용할 수 있다면 이로 인해 클러스터는 다운될 수 있다.
  • 취약하다.
    • 변수들이 난해해질 수 있고, 하나의 잘못된 문자로 곤경에 빠질 수 있으며, 무슨 일이 일어나는지 알아채기 힘들 때가 있다.

따라서 간단한 로컬 테스트와 같은 작업에서 유용하게 사용할 수 있는 기능이다.

JSON 검색

위에서 사용한 예제를 query lite가 아닌 일반적인 JSON 형태로 검색하려면 다음과 같이 검색할 수 있다.

curl -XGET 127.0.0.1:9200/movies/_search?pretty -d '{
    "query": {
        "bool": {
            "must": {"term": {"title": "trek"}},
            "filter": {"range": {"year": {"gte": 2010}}}
        }
    }
}'

사용할 수 있는 필터는 다양하다.

  • Term - 정확한 용어나 값으로 필터링 해야하는 경우
    • {"term": {"year": 2014}}
  • terms - 일치하는 결과값 리스트와 일치하는 항목을 찾고자 하는 경우
    • {"terms": {"genre": ["Sci-Fi", "Adventure"]}}
  • Range - 범위를 필터링
    • {"range": {"year": {"get": 2010}}}
  • Exists - field가 document 안에 존재하는지 찾고자 하는 경우
    • {"exists": {"field": "tags"}}
    • 존재하는지'만' 찾고자 할때는 exist 사용
  • Missing - 지정된 field가 없는 document를 찾는 경우
    • {"missing": {"field": "tags"}}
  • Bool - must = AND, must_not = NOT, should = OR

쿼리 또한 다양하다.

  • Match_all - 모두 반환되는 기본값. 쿼리를 지정하지 않으면 기본적으로 모든 결과가 반환된다.
    • {"match_all": {}}
  • Match
    • {"match": {"title": "star"}}
  • Multi_match
    • {"multi_match": {"query": "star", "field": ["title", "synopsis"]}}
  • Bool - 관련성 별로 결과를 표시한다는 차이점이 존재한다.

구문 검색

때로는 개별 검색이 아닌 Star Wars처럼 특정 순서로 함께 검색해야 할 수도 있다. 이러한 구절 검색이 Elasticsearch에서 어떻게 작동하는지 알아보자.
Elasticsearch에서는 여러 개의 용어를 같은 순서로 검색하는 것은 쉽다. match_phrase를 사용하면 되기 때문이다.

curl -XGET 127.0.0.1:9200/movies/_search?pretty -d '{
    "query": {
        "match_phrase": {
            "title": "star wars"
        }
    }
}'

만약 검색어의 순서가 중요하지만, 바로 붙어있을 필요는 없는 상황이라면 slop을 사용한다. slop은 어느 방향으로 얼마나 움직여도 구절 매치를 허용할 것인지를 나타낸다. 만약 'star trek beyond'를 검색하고 싶다면 다음과 같이 작성할 수 있다. 중간에 어떤 단어가 들어와도 검색될 것이다. 또한 reverse 검색도 허용한다. 검색 결과 중 'beyond star'와도 일치한다는 뜻이다.
slop 값을 1로 설정했다고 반드시 중간 사이의 단어가 1개 빈다는 것을 알고 있어야할까? 그것은 아니다. 실제로 slop 값을 100으로 설정해도 모든 검색 결과를 찾는다. 다만 둘 사이가 가까울수록 관련성 점수가 높다.

curl -XGET 127.0.0.1:9200/movies/_search?pretty -d '{
    "query": {
        "match_phrase": {
            "title": {"query": "star beyond", "slop": 1}
        }
    }
}'

페이징

from, size를 사용하여 페이지네이션을 할 수 있다. 여기서 from은 0부터 시작한다. 데이터베이스 쿼리문의 limit, offset을 떠올리면 이해하기 쉽다.
from - 몇개의 결과만을 받을 것인가
size - 한 페이지에 몇개의 검색 결과를 표시할 것인가

curl -XGET '127.0.0.1:9200/movies/_search?size=2&from=2&pretty'

curl -XGET 127.0.0.1:9200/movies/_search? -d '{
    "from": 2,
    "size": 2,
    "query": {"match": {"genre": "Sci-Fi"}}
}'

일반적으로 페이지네이션에 대한 주의사항은 Elasticsearch에도 적용된다. 바로 깊은 페이징은 성능을 저하할 수 있다는 것이다. 이를 피하기 위해서는 나름대로의 상한선을 적용하는 방법이 있다.

정렬

일반적으로 숫자 field나 검색하기 쉬운 field를 다루는 것은 매우 간단히 sort=를 사용하면 된다.

  • curl -XGET '127.0.0.1:9200/movies/_search?sort=year&pretty'

하지만 문자열을 다룰 때는 고려해야할 사항이 있다.

  • text field가 있다면 해당 field는 '전체 텍스트 검색'을 위해 분석된다. 따라서 부분 match, purge query 등의 작업을 할 수 있다.
  • 역색인이 해당 제목의 용어들을 가지고 있기 때문에 이 기능은 문서를 정렬하는데 사용할 수 없다.
  • 영화 제목같은 경우를 예로 들면 '개별 단어'와 '단어 순서'만 역색인에 보관된다.

이를 해결하는 방법은 분석되지 않은 하위 필드를 설정하는 것이다.

curl -XPUT 127.0.0.1:9200/movies -d '{
    "mappings": {
        "properties": {
    "title": {
        "type": "text",
        "field": {
            "raw": {
                "type": "keyword"
            }
        }
    }    
    }
    }
}'
  • title field가 'text' type임을 확인할 수 있다. 따라서 이는 전체 텍스트 검색을 위해 분석된다.
  • 하위에 'raw' 필드가 있으며, 이를 'keyword' 타입으로 지정하여 raw 필드는 분석되지 않도록 하여 제목 그대로를 'raw' 필드에 저장한다.
  • 따라서 '전체 텍스트 검색'에 사용할 수 있는 제목 필드 뿐만 아니라 정렬 작업이나 분석되지 않은 필드를 필요로 하는 작업에 사용할 수 있는 'title.raw' 필드도 생성한 것이다.
    • 이후 curl -XGET '127.0.0.1:9200/movies/_search?sort=title.raw&pretty' 로 검색 결과를 확인할 수 있다.
  • 기존 index의 mapping을 변경할 수 없기 때문에 index 생성 시 고려해야하는 사항 중 하나이다.

퍼지(Fuzzy) 쿼리

검색 속 오타를 다루는 쿼리이다. Fuzzy Matches의 기본 개념은 '레벤쉬테인 편집 거리'부터 시작한다. 이는 일반적인 오자와 오타를 정량화 할 수 있다. 여기에는 대체(Substitutions), 삽입(Insertions) 및 삭제(Deletion)의 세 가지 단계가 있다.

  • 대체(Substitutions)
    • 실수로 잘못된 문자를 입력했을 때 잡아낸다.
    • Interstellar -> Intersteller
  • 삽입(Insertions)
    • 사람의 실수로 있어서는 안되는 철자가 추가된 것
    • Interstellar -> Insterstellar
  • 삭제(Deletion)
    • 누락
    • Interstellar -> Interstelar

위의 예시는 모두 레벤쉬테인 편집 거리가 '1'인 상태에서는 하나의 철자만 틀렸기 때문에 검색이 된다. 이 수치는 얼마나 많은 오차를 허용할 것인지를 나타낸다.
이는 다음과 같이 설정할 수 있다.

curl -XGET 127.0.0.1:9200/movies/_search?pretty -d '{
    "query": {
        "fuzzy": {
            "title": {"value": "intrsteller", "fuzziness": 2}
        }
    }
}'

퍼지를 자동으로 설정해보자. 때로는 문자열 길이에 따라 퍼지를 다르게 설정할 수 있다. 이를 사용하면 1~2개의 문자열에 대해선 어떤 종류의 오타도 허용하지 않는다. 3~5개의 문자열에는 레벤쉬테인 편집 거리 1만 허용하고, 그 외에는 레벤쉬테인 편집 거리 2까지를 허용한다.

부분 매치

검색할 때 접두사 또는 문자열의 일부분을 일치시키는 법을 알아보자.
문자열에 대한 접두사 쿼리는 다음과 같이 작동한다.

curl -XGET '127.0.0.1:9200/movies/_search?pretty' -d '{
    "query": {
        "prefix": {
            "year": "201"
        }
    }
}'

---

curl -XGET '127.0.0.1:9200/movies/_search?pretty' -d '{
    "query": {
        "wildcard": {
            "year": "1*"
        }
    }
}'

상기 검색의 첫번째는 영화의 개봉 날짜가 '201'로 시작하는 모든 영화를 검색하여 보여준다. 두번째는 와일드 카드를 이용한 검색 방법을 보여준다. 또한 정규표현식도 지원한다.

Search As You Type

입력하는 동안 검색어가 자동으로 완성되고 몇 가지 검색 제안이 나오는 기능이다. Elasticsearch를 사용하여 이와 유사한 작업을 수행할 수 있다.
가장 쉬운 방법은 'query-time search-as-you-type'이다. 데이터를 특별히 색인화할 필요가 없기 때문이다.

curl -XGET '127.0.0.1:9200/movies/_search?pretty' -d '{
    "query": {
        "match_phrase_prefix": {
            "title": {
                "query": "star trek",
                "slop": 10
            }
        }
    }
}'

이는 가장 쉬운 방법이지만 가장 효율적인 방법은 아니다.

N-grams

'Query-Time Search-As-You-Type'의 한 가지 단점은 색인 기반 솔루션에 비해 리소스가 많이 사용된다는 것이다. 만약 대규모로 사용해야한다면 'index-time'솔루션이 필요하다. 'index-time' 솔루션에서는 N-grams 라는 간단한 개념을 사용한다. 예를 들어 'star'라는 단어를 살펴보면 다음과 같이 분류할 수 있다.

unigram: [s, t, a, r]
bigram: [st, ta, ar]
trigram: [sta, tar]
4-gram: [star]

이 개념을 'Search-As-You' Type에 적용할 수 있다. 입력값을 N-grams으로 취급하면 index에 매치하여 어떤 단어와 일치하는지 확인할 수 있다. 만약 'star trek'을 찾기 위해서 's'를 입력하면 그 유니그램은 'star'의 유니그램 중 하나인 's'와 일치한다.

Edge N-grams라는 특수한 유형의 N-grams가 존재한다. 예시로 확인했듯이 정말 중요한 것은 주어진 구절 혹은 검색어의 시작 N-grams이다. 따라서 Edge N-grams는 주어진 단어의 앞부분에 대한 N-grams만 계산하는 것이다.

이를 Elasticsearch에 적용하기 위한 단계는 다음과 같다.

  • Autocomplete Analyzer를 생성한다.
curl -XPUT '127.0.0.1:9200/movies?pretty' -d '{
    "settings": {
        "analysis": {
            "filter": {
                "autocomplete_filter": {
                    "type": "edge_ngram",
                    "min_gram": 1,
                    "max_gram": 20
                }
            },
            "analyzer": {
                "autocomplete": {
                    "type": "custom",
                    "tokenizer": "standard",
                    "filter": [
                        "lowercase",
                        "autocomplete_filter"
                    ]
                }
            }
        }
    }
}'
  • 생성한 autocomplete analyzer를 index-time에 적용한다.
  • index화하기 전에 이에 대한 mapping을 설정해주어야 한다.
curl -XPUT '127.0.0.1:9200/movies/_mapping?pretty' -d '{
    "properties": {
        "title": {
            "type": "text",
            "analyzer": "autocomplete"
        }
    }
}'

Completion Suggesters

또 하나의 방법으로는 completion suggesters를 사용하는 것이다. 이 매커니즘을 사용하여 완료 목록을 미리 업로드 할 수 있다. 최대한 효율적으로 자동 완성 기능을 완벽하게 통제하고 싶다면 이 기능을 사용하는 것을 추천한다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
글 보관함