Elasticsearch alapok

Az Elasticsearch egy json rest api-val rendelkező keresőmotor, amely Lucene-t használ, és Java nyelven íródott. A motor összes előnyének leírása a következő címen érhető el hivatalos honlapján. A következőkben az Elasticsearch-ra ES-ként fogunk hivatkozni.

Hasonló motorokat használnak a dokumentum-adatbázisban végzett összetett keresésekhez. Például keresés a nyelv morfológiájának figyelembevételével vagy földrajzi koordináták alapján.

Ebben a cikkben az ES alapjairól fogok beszélni a blogbejegyzések indexelésének példáján. Megmutatom, hogyan szűrhet, rendezhet és kereshet dokumentumok között.

Annak érdekében, hogy ne függjek az operációs rendszertől, minden kérést az ES-hez küldök a CURL használatával. A google chrome-hoz is van egy bővítmény, az úgynevezett értelemben.

A szöveg hivatkozásokat tartalmaz a dokumentációra és más forrásokra. A végén hivatkozások találhatók a dokumentáció gyors eléréséhez. Az ismeretlen kifejezések definíciói itt találhatók szószedetek.

Telepítés

Ehhez először Java-ra van szükségünk. Fejlesztők ajánlani telepítse a Java 8 frissítés 20-nál vagy a Java 7 frissítés 55-nél újabb Java-verziókat.

Az ES terjesztés elérhető a következő címen: fejlesztői weboldal. Az archívum kicsomagolása után futnia kell bin/elasticsearch. Szintén elérhető csomagok apt és yum számára. Van hivatalos kép a docker számára. Bővebben a telepítésről.

A telepítés és az indítás után ellenőrizzük a működést:

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

curl -X GET $ES_URL

Valami ilyesmit fogunk kapni:

{
  "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"
}

indexelés

Adjunk hozzá egy bejegyzést az ES-hez:

# Добавим документ 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"
}'

szerver válasz:

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

ES automatikusan létrejön index blog és típus hozzászólás. Feltételes analógiát vonhatunk le: az index egy adatbázis, a típus pedig egy táblázat ebben az adatbázisban. Minden típusnak megvan a maga sémája − térképészet, akárcsak egy relációs táblázat. A leképezés automatikusan generálódik a dokumentum indexelésekor:

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

A szerver válaszában hozzáadtam az indexelt dokumentum mezőinek értékeit a megjegyzésekhez:

{
  "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"
          }
        }
      }
    }
  }
}

Érdemes megjegyezni, hogy az ES nem tesz különbséget egyetlen érték és egy értéktömb között. Például a címmező egyszerűen egy címet tartalmaz, a címkék mező pedig egy karakterlánc tömböt tartalmaz, bár ezek a leképezésben ugyanúgy vannak ábrázolva.
A térképezésről később még beszélünk.

kérelmek

Dokumentum lekérése az azonosítója alapján:

# извлечем документ с 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"
  }
}

Új kulcsok jelentek meg a válaszban: _version и _source. Általában minden billentyűvel kezdődik _ hivatalosnak minősülnek.

kulcs _version a dokumentum verzióját mutatja. Az optimista zárszerkezet működéséhez szükséges. Például egy 1-es verziójú dokumentumot szeretnénk módosítani. Beküldjük a módosított dokumentumot, és jelezzük, hogy ez egy 1-es verziójú dokumentum szerkesztése. Ha valaki más is szerkesztett egy 1-es verziójú dokumentumot, és módosításokat nyújtott be előttünk, akkor ES nem fogadja el a változtatásainkat, mert a 2-es verziójú dokumentumot tárolja.

kulcs _source tartalmazza az általunk indexelt dokumentumot. Az ES nem használja ezt az értéket a keresési műveletekhez, mert A kereséshez indexeket használnak. A helytakarékosság érdekében az ES tömörített forrásdokumentumot tárol. Ha csak az azonosítóra van szükségünk, és nem a teljes forrásdokumentumra, akkor letilthatjuk a forrástárolást.

Ha nincs szükségünk további információra, akkor csak a _source tartalmát kaphatjuk meg:

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

Csak bizonyos mezőket is kiválaszthat:

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

Indexeljünk még néhány bejegyzést, és futtassunk bonyolultabb lekérdezéseket.

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"
}'

osztályozás

# найдем последний пост по дате публикации и извлечем поля 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 ]
    } ]
  }
}

Az utolsó bejegyzést választottuk. size korlátozza a kiadandó dokumentumok számát. total a kérelemnek megfelelő dokumentumok teljes számát mutatja. sort a kimenetben egész számok tömbjét tartalmazza, amelyek alapján a rendezés végrehajtódik. Azok. a dátum egész számra lett konvertálva. A válogatásról bővebb információt itt talál dokumentáció.

Szűrők és lekérdezések

Az ES a 2. verzió óta nem tesz különbséget a szűrők és a lekérdezések között bevezetik a kontextusok fogalmát.
A lekérdezési kontextus abban különbözik a szűrőkontextustól, hogy a lekérdezés _score értéket generál, és nem kerül gyorsítótárba. Később megmutatom, mi az _score.

Szűrés dátum szerint

Használjuk a kérést hatótávolság szűrővel összefüggésben:

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

Szűrés címkék szerint

Használjuk kifejezés lekérdezés adott szót tartalmazó dokumentumazonosítók kereséséhez:

# найдем все документы, в поле 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" : [ "котята" ]
      }
    } ]
  }
}

Teljes szöveges keresés

Három dokumentumunk a következőket tartalmazza a tartalom mezőben:

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

Használjuk egyezési lekérdezés adott szót tartalmazó dokumentumazonosítók kereséséhez:

# 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
    } ]
  }
}

Ha azonban a tartalom mezőben „történetekre” keresünk, nem találunk semmit, mert Az index csak az eredeti szavakat tartalmazza, a töveket nem. A jó minőségű keresés érdekében konfigurálnia kell az analizátort.

Mező _score mutatja relevanciáját. Ha a kérést szűrőkontextusban hajtják végre, akkor a _score értéke mindig 1 lesz, ami azt jelenti, hogy a szűrő teljes egyezést mutat.

Elemzők

Elemzők szükségesek a forrásszöveg tokenek halmazává alakításához.
Az elemzők egyből állnak Tokenizátor és több választható TokenFilters. A Tokenizert több is megelőzheti CharFilters. A tokenizálók a forráskarakterláncot tokenekre, például szóközökre és írásjelekre bontják. A TokenFilter módosíthatja a tokeneket, törölhet vagy újakat adhat hozzá, például csak a szó tőjét hagyhatja meg, eltávolíthatja az elöljárókat, szinonimákat adhat hozzá. CharFilter - megváltoztatja a teljes forráskarakterláncot, például kivágja a html címkéket.

Az ES-nek több is van szabványos analizátorok. Például egy elemző orosz.

Használjuk ki api és nézzük meg, hogyan alakítják át a szabványos és az orosz elemzők a „Vicces történetek a cicákról” sztringet:

# используем анализатор 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
  } ]
}

A szabványos elemző szóközökkel osztotta fel a karakterláncot, és mindent kisbetűre konvertált, az orosz elemző eltávolította a lényegtelen szavakat, kisbetűvé alakította, és elhagyta a szavak szárát.

Lássuk, melyik Tokenizer-t, TokenFilters-t, CharFilters-t használja az orosz analizátor:

{
  "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 отсутствуют */
    }
  }
}

Leírjuk az orosz alapú elemzőnket, amely kivágja a html címkéket. Nevezzük alapértelmezettnek, mert alapértelmezés szerint egy ilyen nevű elemzőt használ a rendszer.

{
  "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"
      ]
    }
  }
}

Először az összes HTML tag eltávolításra kerül a forráskarakterláncból, majd a tokenizer szabvány tokenekre bontja, az így kapott tokenek kisbetűre kerülnek, a jelentéktelen szavak eltávolításra kerülnek, a fennmaradó tokenek pedig a szó törzse maradnak.

Index létrehozása

Fentebb leírtuk az alapértelmezett analizátort. Ez az összes karakterlánc mezőre vonatkozik. Posztunk egy sor címkét tartalmaz, így a címkéket is feldolgozza az elemző. Mert A bejegyzéseket pontos egyezés alapján keressük egy címkével, majd le kell tiltanunk a címkék mező elemzését.

Készítsünk egy index blog2-t elemzővel és leképezéssel, amelyben a címkék mező elemzése le van tiltva:

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"
        }
      }
    }
  }
}'

Adjuk hozzá ugyanazt a 3 bejegyzést ehhez az indexhez (blog2). Ezt a folyamatot kihagyom, mert... hasonló a dokumentumok blog indexhez való hozzáadásához.

Teljes szöveges keresés kifejezés támogatással

Nézzünk egy másik típusú kérelmet:

# найдем документы, в которых встречается слово 'истории'
# 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"
      ]
    }
  }
}'

Mert Orosz eredetű elemzőt használunk, akkor ez a kérés minden dokumentumot visszaad, bár ezek csak az „előzmények” szót tartalmazzák.

A kérés speciális karaktereket tartalmazhat, például:

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

Szintaxis kérése:

+ 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 поста про котиков

referenciák

PS

Ha érdeklik a hasonló cikkek-leckék, ötletei vannak új cikkekre, vagy együttműködési javaslataik vannak, szívesen fogadok üzenetet személyes üzenetben vagy e-mailben [e-mail védett].

Forrás: will.com