Elasticsearch 기본 사항

Elasticsearch는 Lucene을 사용하고 Java로 작성된 json Rest API를 갖춘 검색 엔진입니다. 이 엔진의 모든 장점에 대한 설명은 다음에서 확인할 수 있습니다. 공식 웹 사이트. 다음에서는 Elasticsearch를 ES라고 하겠습니다.

문서 데이터베이스의 복잡한 검색에는 유사한 엔진이 사용됩니다. 예를 들어, 언어의 형태를 고려하여 검색하거나 지리 좌표를 기준으로 검색합니다.

이 기사에서는 블로그 게시물 색인 생성의 예를 사용하여 ES의 기본 사항에 대해 설명합니다. 문서 필터링, 정렬, 검색 방법을 알려드리겠습니다.

운영 체제에 의존하지 않기 위해 ES에 대한 모든 요청은 CURL을 사용하여 수행합니다. Google Chrome용 플러그인도 있습니다. 감각.

텍스트에는 문서 및 기타 소스에 대한 링크가 포함되어 있습니다. 마지막에는 문서에 빠르게 액세스할 수 있는 링크가 있습니다. 익숙하지 않은 용어의 정의는 다음에서 찾을 수 있습니다. 용어집.

ES 설치

이를 위해서는 먼저 Java가 필요합니다. 개발자 추천하다 Java 8 업데이트 20 또는 Java 7 업데이트 55보다 최신 Java 버전을 설치합니다.

ES 배포판은 다음에서 구할 수 있습니다. 개발자 사이트. 아카이브의 압축을 푼 후 실행해야 합니다. bin/elasticsearch. 도 가능 apt 및 yum용 패키지. 있다 도커의 공식 이미지. 설치에 대한 추가 정보.

설치 및 실행 후 기능을 확인해 보겠습니다.

# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200

curl -X GET $ES_URL

우리는 다음과 같은 것을 받게 될 것입니다:

{
  "name" : "Heimdall",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.2.1",
    "build_hash" : "d045fc29d1932bce18b2e65ab8b297fbf6cd41a1",
    "build_timestamp" : "2016-03-09T09:38:54Z",
    "build_snapshot" : false,
    "lucene_version" : "5.4.1"
  },
  "tagline" : "You Know, for Search"
}

인덱싱

ES에 게시물을 추가해 보겠습니다.

# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.

curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
  "title": "Веселые котята",
  "content": "<p>Смешная история про котят<p>",
  "tags": [
    "котята",
    "смешная история"
  ],
  "published_at": "2014-09-12T20:44:42+00:00"
}'

서버 응답:

{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : false
}

ES가 자동으로 생성됨 색인 블로그와 тип 우편. 조건부 비유를 그릴 수 있습니다. 인덱스는 데이터베이스이고 유형은 이 데이터베이스의 테이블입니다. 각 유형에는 고유한 구성표가 있습니다. 매핑, 관계형 테이블과 같습니다. 문서가 색인화되면 매핑이 자동으로 생성됩니다.

# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"

서버 응답에서 주석에 색인화된 문서의 필드 값을 추가했습니다.

{
  "blog" : {
    "mappings" : {
      "post" : {
        "properties" : {
          /* "content": "<p>Смешная история про котят<p>", */ 
          "content" : {
            "type" : "string"
          },
          /* "published_at": "2014-09-12T20:44:42+00:00" */
          "published_at" : {
            "type" : "date",
            "format" : "strict_date_optional_time||epoch_millis"
          },
          /* "tags": ["котята", "смешная история"] */
          "tags" : {
            "type" : "string"
          },
          /*  "title": "Веселые котята" */
          "title" : {
            "type" : "string"
          }
        }
      }
    }
  }
}

ES가 단일 값과 값 배열을 구별하지 않는다는 점은 주목할 가치가 있습니다. 예를 들어 제목 필드에는 제목만 포함되고 태그 필드에는 문자열 배열이 포함되지만 매핑에서는 동일한 방식으로 표시됩니다.
나중에 매핑에 대해 더 자세히 이야기하겠습니다.

요청

ID로 문서 검색:

# извлечем документ с id 1 типа post из индекса blog
curl -XGET "$ES_URL/blog/post/1?pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята",
    "content" : "<p>Смешная история про котят<p>",
    "tags" : [ "котята", "смешная история" ],
    "published_at" : "2014-09-12T20:44:42+00:00"
  }
}

응답에 새로운 키가 나타났습니다. _version и _source. 일반적으로 다음으로 시작하는 모든 키는 _ 공무원으로 분류됩니다.

열쇠 _version 문서 버전을 표시합니다. 낙관적 잠금 메커니즘이 작동하려면 필요합니다. 예를 들어 버전 1의 문서를 변경하고 싶습니다. 변경된 문서를 제출하고 이것이 버전 1의 문서 편집임을 나타냅니다. 다른 사람도 버전 1의 문서를 편집하고 우리보다 먼저 변경 사항을 제출한 경우 ES는 우리의 변경 사항을 받아들이지 않습니다. 버전 2로 문서를 저장합니다.

열쇠 _source 우리가 인덱싱한 문서가 포함되어 있습니다. ES는 검색 작업에 이 값을 사용하지 않습니다. 색인은 검색에 사용됩니다. 공간을 절약하기 위해 ES는 압축된 소스 문서를 저장합니다. 전체 소스 문서가 아닌 ID만 필요한 경우 소스 저장소를 비활성화할 수 있습니다.

추가 정보가 필요하지 않으면 _source의 내용만 얻을 수 있습니다.

curl -XGET "$ES_URL/blog/post/1/_source?pretty"
{
  "title" : "Веселые котята",
  "content" : "<p>Смешная история про котят<p>",
  "tags" : [ "котята", "смешная история" ],
  "published_at" : "2014-09-12T20:44:42+00:00"
}

특정 필드만 선택할 수도 있습니다.

# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята"
  }
}

몇 가지 게시물을 더 색인화하고 더 복잡한 쿼리를 실행해 보겠습니다.

curl -XPUT "$ES_URL/blog/post/2" -d'
{
  "title": "Веселые щенки",
  "content": "<p>Смешная история про щенков<p>",
  "tags": [
    "щенки",
    "смешная история"
  ],
  "published_at": "2014-08-12T20:44:42+00:00"
}'
curl -XPUT "$ES_URL/blog/post/3" -d'
{
  "title": "Как у меня появился котенок",
  "content": "<p>Душераздирающая история про бедного котенка с улицы<p>",
  "tags": [
    "котята"
  ],
  "published_at": "2014-07-21T20:44:42+00:00"
}'

정렬

# найдем последний пост по дате публикации и извлечем поля title и published_at
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "size": 1,
  "_source": ["title", "published_at"],
  "sort": [{"published_at": "desc"}]
}'
{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : null,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : null,
      "_source" : {
        "title" : "Веселые котята",
        "published_at" : "2014-09-12T20:44:42+00:00"
      },
      "sort" : [ 1410554682000 ]
    } ]
  }
}

우리는 마지막 게시물을 선택했습니다. size 발급할 서류의 수를 제한합니다. total 요청과 일치하는 총 문서 수를 표시합니다. sort 출력에는 정렬이 수행되는 정수 배열이 포함됩니다. 저것들. 날짜가 정수로 변환되었습니다. 정렬에 대한 자세한 내용은 다음에서 확인할 수 있습니다. 선적 서류 비치.

필터 및 쿼리

ES는 버전 2부터 필터와 쿼리를 구분하지 않습니다. 컨텍스트 개념이 도입되었습니다..
쿼리 컨텍스트는 쿼리가 _score를 생성하고 캐시되지 않는다는 점에서 필터 컨텍스트와 다릅니다. _score가 무엇인지는 나중에 보여드리겠습니다.

날짜별로 필터링

우리는 요청을 사용합니다 범위 필터의 맥락에서:

# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "filter": {
    "range": {
      "published_at": { "gte": "2014-09-01" }
    }
  }
}'

태그로 필터링

우리는 사용 용어 검색어 특정 단어가 포함된 문서 ID를 검색하려면 다음을 수행하세요.

# найдем все документы, в поле tags которых есть элемент 'котята'
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "_source": [
    "title",
    "tags"
  ],
  "filter": {
    "term": {
      "tags": "котята"
    }
  }
}'
{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "title" : "Веселые котята",
        "tags" : [ "котята", "смешная история" ]
      }
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "title" : "Как у меня появился котенок",
        "tags" : [ "котята" ]
      }
    } ]
  }
}

전체 텍스트 검색

우리 문서 중 XNUMX개에는 내용 필드에 다음이 포함되어 있습니다.

  • <p>Смешная история про котят<p>
  • <p>Смешная история про щенков<p>
  • <p>Душераздирающая история про бедного котенка с улицы<p>

우리는 사용 일치 검색어 특정 단어가 포함된 문서 ID를 검색하려면 다음을 수행하세요.

# source: false означает, что не нужно извлекать _source найденных документов
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "_source": false,
  "query": {
    "match": {
      "content": "история"
    }
  }
}'
{
  "took" : 13,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 0.11506981,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "2",
      "_score" : 0.11506981
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : 0.11506981
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 0.095891505
    } ]
  }
}

하지만 콘텐츠 필드에서 '스토리'를 검색하면 아무것도 찾을 수 없습니다. 색인에는 어간이 아닌 원래 단어만 포함됩니다. 고품질 검색을 위해서는 분석기를 구성해야 합니다.

분야 _score관련성. 요청이 필터 컨텍스트에서 실행되는 경우 _score 값은 항상 1과 같습니다. 이는 필터와 완전히 일치함을 의미합니다.

분석기

분석기 소스 텍스트를 토큰 세트로 변환하는 데 필요합니다.
분석기는 하나로 구성됩니다. 토크 나이저 그리고 몇 가지 선택 사항 토큰 필터. 토크나이저 앞에는 여러 개가 올 수 있습니다. CharFilters. 토크나이저는 소스 문자열을 공백, 구두점 문자 등의 토큰으로 나눕니다. TokenFilter는 토큰을 변경하고, 새 토큰을 삭제 또는 추가할 수 있습니다. 예를 들어 단어의 어간만 남기고, 전치사를 제거하고, 동의어를 추가할 수 있습니다. CharFilter - 전체 소스 문자열을 변경합니다. 예를 들어 html 태그를 잘라냅니다.

ES에는 여러 가지가 있습니다. 표준 분석기. 예를 들어, 분석기 러시아인.

사용하자 API를 표준 분석기와 러시아 분석기가 "고양이에 대한 재미있는 이야기"라는 문자열을 어떻게 변환하는지 살펴보겠습니다.

# используем анализатор standard       
# обязательно нужно перекодировать не ASCII символы
curl -XGET "$ES_URL/_analyze?pretty&analyzer=standard&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
  "tokens" : [ {
    "token" : "веселые",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истории",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "про",
    "start_offset" : 16,
    "end_offset" : 19,
    "type" : "<ALPHANUM>",
    "position" : 2
  }, {
    "token" : "котят",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}
# используем анализатор russian
curl -XGET "$ES_URL/_analyze?pretty&analyzer=russian&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
  "tokens" : [ {
    "token" : "весел",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истор",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "кот",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}

표준 분석기는 문자열을 공백으로 분할하여 모두 소문자로 변환했지만, 러시아어 분석기는 중요하지 않은 단어를 제거하고 소문자로 변환하고 단어의 어간을 남겼습니다.

러시아 분석기가 어떤 Tokenizer, TokenFilters, CharFilters를 사용하는지 살펴보겠습니다.

{
  "filter": {
    "russian_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "russian_keywords": {
      "type":       "keyword_marker",
      "keywords":   []
    },
    "russian_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "russian": {
      "tokenizer":  "standard",
      /* TokenFilters */
      "filter": [
        "lowercase",
        "russian_stop",
        "russian_keywords",
        "russian_stemmer"
      ]
      /* CharFilters отсутствуют */
    }
  }
}

html 태그를 잘라내는 러시아어 기반 분석기를 설명하겠습니다. 기본값이라고 부르자. 왜냐면 이 이름의 분석기가 기본적으로 사용됩니다.

{
  "filter": {
    "ru_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "ru_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "default": {
      /* добавляем удаление html тегов */
      "char_filter": ["html_strip"],
      "tokenizer":  "standard",
      "filter": [
        "lowercase",
        "ru_stop",
        "ru_stemmer"
      ]
    }
  }
}

먼저 모든 HTML 태그가 소스 문자열에서 제거된 다음 토크나이저 표준이 이를 토큰으로 분할하고 결과 토큰이 소문자로 이동하며 중요하지 않은 단어가 제거되고 나머지 토큰은 단어의 어간으로 유지됩니다.

색인 만들기

위에서는 기본 분석기에 대해 설명했습니다. 모든 문자열 필드에 적용됩니다. 게시물에는 일련의 태그가 포함되어 있으므로 태그도 분석기에서 처리됩니다. 왜냐하면 태그와 정확히 일치하는 게시물을 찾고 있는 경우 태그 필드에 대한 분석을 비활성화해야 합니다.

태그 필드 분석이 비활성화된 분석기와 매핑을 사용하여 blog2 인덱스를 생성해 보겠습니다.

curl -XPOST "$ES_URL/blog2" -d'
{
  "settings": {
    "analysis": {
      "filter": {
        "ru_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        },
        "ru_stemmer": {
          "type": "stemmer",
          "language": "russian"
        }
      },
      "analyzer": {
        "default": {
          "char_filter": [
            "html_strip"
          ],
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "ru_stop",
            "ru_stemmer"
          ]
        }
      }
    }
  },
  "mappings": {
    "post": {
      "properties": {
        "content": {
          "type": "string"
        },
        "published_at": {
          "type": "date"
        },
        "tags": {
          "type": "string",
          "index": "not_analyzed"
        },
        "title": {
          "type": "string"
        }
      }
    }
  }
}'

이 색인(blog3)에 동일한 게시물 2개를 추가해 보겠습니다. 이 과정은 생략하기 때문에... 이는 블로그 색인에 문서를 추가하는 것과 유사합니다.

표현식 지원을 통한 전체 텍스트 검색

다른 유형의 요청을 살펴보겠습니다.

# найдем документы, в которых встречается слово 'истории'
# query -> simple_query_string -> query содержит поисковый запрос
# поле title имеет приоритет 3
# поле tags имеет приоритет 2
# поле content имеет приоритет 1
# приоритет используется при ранжировании результатов
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "истории",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

왜냐하면 우리는 러시아어 형태소 분석 기능을 갖춘 분석기를 사용하고 있으며, 이 요청은 'history'라는 단어만 포함되어 있더라도 모든 문서를 반환합니다.

요청에는 다음과 같은 특수 문자가 포함될 수 있습니다.

""fried eggs" +(eggplant | potato) -frittata"

요청 구문:

+ signifies AND operation
| signifies OR operation
- negates a single token
" wraps a number of tokens to signify a phrase for searching
* at the end of a term signifies a prefix query
( and ) signify precedence
~N after a word signifies edit distance (fuzziness)
~N after a phrase signifies slop amount
# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "-щенки",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

# получим 2 поста про котиков

참조

PS

유사한 기사 레슨에 관심이 있거나, 새로운 기사에 대한 아이디어가 있거나, 협력 제안이 있는 경우 개인 메시지나 이메일로 메시지를 받게 되어 기쁩니다. [이메일 보호].

출처 : habr.com