Pull to refresh

Быстрый полнотекстовый поиск ElasticSearch

Reading time 6 min
Views 218K
image
При разработке высоконагруженных сайтов или корпоративных систем частенько возникает проблема с разработкой быстрого и удобного поискового движка. Ниже перечислены наиболее важные, на мой взгляд, требования к такому движку:

  • Скорость
  • Простота установки и настройки
  • Цена (желательно бесплатно и с открытым кодом)
  • Обмен информацией в формате JSON (по HTTP)
  • Масштабируемость (возможность распределения на несколько серверов)
  • Индексация в режиме реального времени
  • Multi-tenancy (гибкость в настройках под индивидуального пользователя)
  • Возможность переноса системы в облако

Хочу рассказать вам о новом поисковом движке Elasticsearch, который полностью удовлетворяет всем этим требованиям. В статье будет краткое описание, ссылка на авторитетную презентацию, а также описание установки и работы с ним.

На сегодняшний день существует множество различных реализаций таких систем, у каждой из них есть свои плюсы и минусы. Так как я не являюсь экспертом в этой области, то всю нижеперечисленную информацию прошу воспринимать только как моё субъективное мнение.

Так вот, недавно на глаза мне попалась презентация Андрея Змиевского (Andrei Zmievski), где он описывал возможности elasticsearch. Презентацию можно посмотреть тут (на английском).

Сайт проекта http://www.elasticsearch.org/

К сожалению, никакой информации на русском языке я найти не смог.

Что же это такое?


По сути — это новый фронт-енд к широко известному индексу Lucene. Главное отличие от конкурентов — это гибкость и простота в использовании. Добавление информации в индекс и поиск по индексу производятся с помощью простых HTTP запросов.

Установка и примеры работы с движком


Меня эта тема заинтересовала и я решил собственноручно испытать этот чудо-движок.
Итак, приступим

Установка

	1. Скачиваем архив (http://www.elasticsearch.org/download/) и распаковываем его
	2. Запускаем сервер 
		Unix :   bin/elasticsearch –f
		Windows :  bin/elasticsearch.bat
	3. Проверяем сервер 
		curl -X GET http://localhost:9200/
	Если все работает, сервер вернет вам JSON массив с какой-то информацией.

Индексация данных

Для примера создадим индекс пользователей хабра

Добавляем данные о первом пользователе
$ curl -XPUT 'http://localhost:9200/habrahabr/users/1' -d '
{ 
 "firstname"   : "Piotr",
 "surname"     : "Petrov",
 "birthDate"   : "1981-01-01",
  "location"    : "Moscow, Russian Federation",
 "skills"      : ["PHP", "HTML", "C++", ".NET", "JavaScript"]
}'

Добавляем данные о втором пользователе
$ curl -XPUT 'http://localhost:9200/habrahabr/users/2' -d '
{ 
 "firstname"   : "Ivan",
 "surname"     : "Sidorov",
 "birthDate"   : "1978-12-13",
  "location"    : "Briansk, Russian Federation",
 "skills"      : ["HTML", "Ruby", "Python"]
}'

Добавляем третьего пользователя
$ curl -XPUT 'http://localhost:9200/habrahabr/users/3' -d '
{ 
 "firstname"   : "Stepan",
 "surname"     : "Fomenko",
 "birthDate"   : "1985-06-01",
  "location"    : "Ukraine",
 "skills"      : ["HTML", "XML", "Java", "JavaScript"]
}'

Поиск: пробуем в деле

Для ознакомления я приведу несколько простых примеров поиска. На самом деле этот движок полностью соответствует своему названию “elastic” и можно создавать самые разнообразные запросы. Подробнее о запросах можно прочитать на сайте проекта www.elasticsearch.org/guide/reference/api

параметр pretty=true отображает ответ в более читабельном виде

пример 1: ищем всех пользователей с именем Ivan
$ curl -XGET 'http://localhost:9200/habrahabr/users/_search?q=firstname:Ivan&pretty=true'

пример 2: ищем всех пользователей из Украины со знанием PHP
$ curl -XGET 'http://localhost:9200/habrahabr/users/_search?pretty=true' -d '
{ 
    "query" : { 
        "term" : { "location": "Ukraine", "skills": "PHP" } 
    } 
}'

пример 3: ищем пользователей из России
$ curl -XGET 'http://localhost:9200/habrauser/_search?q=location:Russian%20Federation&pretty=true'

пример 4: подсчитываем количество пользователей из России
$ curl -XGET 'http://localhost:9200/habrauser/_count?q=location:Russian%20Federation&pretty=true'

P.S. UTF8 поддерживает нормально

Тестирование с большим объёмом данных


К сожалению у меня нет большого опыта работы с другими поисковыми движками, поэтому нет возможности и сравнить их с elasticsearch. Любопытства ради решил создать индекс из 5,000,000 пользователей.

Простенький скрипт для заполнения индекса (данные генерируются, но информация более-менее похожа на реальную)

<?php

ini_set('max_execution_time', 36000);

class userGenerator {

  // вписал только несколько стран для примера
  private $countries = array(
      'Russian Federation',
      'Ukraine',
      'Germany',
      'France',
      'Lithuania',
      'Latvia',
      'Poland',
      'Finland',
      'Sweden'
  );

  public function run($cnt) {

    for ($i = 0; $i < $cnt; $i++) {
      $query = $this->generateQuery($i);
      echo "generating user " . $i . " ... ";
      exec($query);
      echo "done" . PHP_EOL;
    }
  }

  private function generateQuery($id) {
    
    // Допустим самый старый программист родился в 1960-ом году
    $date = new DateTime('1960-01-01');

    return 'curl -XPUT \'http://localhost:9200/habrahabr/users/' . $id . '\' -d \'
     { 
       "id"          : "' . $id . '",
       "firstname"   : "' . ucfirst($this->generateWord(10)) . '",
       "surname"     : "' . ucfirst($this->generateWord(10)) . '",
       "birthDate"   : "' . $date->modify('+' . rand(0, 14600) . ' days')->format('Y-m-d') . '",
       "location"    : "' . $this->generateWord(10) . ', ' . $this->countries[array_rand($this->countries)] . '",
       "skills"      : ["' . strtoupper($this->generateWord(3)) . '", "' . strtoupper($this->generateWord(4)) . '", "' . strtoupper($this->generateWord(3)) . '"]
     }\' ';
  }

  private function generateWord($length) {

    $letters = array(
        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
        "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z");

    $word = '';
    for ($i = 0; $i < $length; $i++) {
      $word .= $letters[rand(0, 25)];
    }
    return $word;
  }

}

$generator = new userGenerator();
$generator->run(5000000);
echo "complete";
?>


На создание индекса на моём домашнем (не особо мощном) ПК ушло где-то 5 часов. Учитывая то что я абсолютно ничего не настраивал и не оптимизировал, считаю что результат довольно неплохой. Тем более что время генерирования индекса для меня не особо критично. Думаю если покопаться в настройках, да ещё и оптимизировать мой скрипт так чтобы высылал не одиночные а групповые запросы (подробнее тут), то время сократилось бы в разы. Ну а если ещё и распараллелить этот процесс — тогда время можно сократить до часа.

Проверяем количество записей в индексе
curl -XGET 'http://localhost:9200/habrahabr/users/_count?q=*&pretty'
{
  "count" : 5128888,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  }


Проверяем скорость добавления новой записи
time curl -XPUT 'http://localhost:9200/habrahabr/users/5128889' -d '
{ 
 "firstname"   : "Василий",
 "surname"     : "Фёдоров",
 "birthDate"   : "1975-07-11",
  "location"    : "Riga, Latvia",
 "skills"      : ["PERL", "PYTHON", "ActionScript"]
}'
{"ok":true,"_index":"habrahabr","_type":"users","_id":"5128891","_version":2}

real	0m0.007s
user	0m0.004s
sys	0m0.000s


Проверяем скорость поиска информации
time curl -XGET 'http://localhost:9200/habrahabr/users/_search?q=location:Riga&pretty'
{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 8.854725,
    "hits" : [ {
      "_index" : "habrahabr",
      "_type" : "users",
      "_id" : "5128891",
      "_score" : 8.854725, "_source" : 
{ 
 "firstname"   : "Василий",
 "surname"     : "Фёдоров",
 "birthDate"   : "1975-07-11",
  "location"    : "Riga, Latvia",
 "skills"      : ["PERL", "PYTHON", "ActionScript"]
}
    } ]
  }
}
real	0m0.011s
user	0m0.004s
sys	0m0.000s


$ time curl -XGET 'http://localhost:9200/habrahabr/users/_count?q=location:Germany&pretty'
{
  "count" : 570295,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  }
}
real	0m0.079s
user	0m0.004s
sys	0m0.000s


Выводы

На мой взгляд движок быстрый, качественный, простой в использовании. По ощущениям он гораздо быстрее того же Zend_Search_Lucene.

В этой статье я описал лишь небольшую часть его функционала — самые простые и примитивные функции. За пределами этой статьи остались транзакции, репликaции, фильтры и очень много других полезных функций. Также стоит упомянуть что к этому движку уже написаны библиотеки на Java и PHP (возможно и на других языках).

П.С. Прошу прощения за некоторое косноязычие текста и терминов.
Tags:
Hubs:
+57
Comments 61
Comments Comments 61

Articles