sphinx — универсальное средство поиска

Post on 15-Nov-2014

2.196 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

DESCRIPTION

Очередной семинар был посвящен средствам фильтрации результатов поиска поискового сервера Sphinx. Были рассмотрены методы отладки поисковых поисковых запросов, особенности языка SphinxQL, варианты фильтрации произвольного набора атрибутов в схеме EAV и реализация фасетного поиска.

TRANSCRIPT

Sphinx — универсальноесредство поиска

Interlabs

9 января 2014

1 / 32

О чем речь

• Sphinx — не только для полнотекстового поиска• real-time индексы для экспериментов со Sphinx• различные виды атрибутов и как их использовать• Sphinx и E.A.V.• Faceted Search на Sphinx

2 / 32

Индекс Sphinxid + поля + атрибуты

id уникальный идентификатор документаполе полнотекстовый поиск, хранится только индекс

атрибут поиск по значению, хранится значение

Атрибуты: Integer, Float, Bool, Timestamp, MVA, String, JSON

disk index или realtime index

disk изменяется полным перестроениемrealtime изменения явно добавляются в индекс

3 / 32

Дисковый индекс

• использует источник данных (data source)• обновление индекса = его перестроение• построение не требует поддержки в коде приложения• повышенная нагрузка на источник данных приперестроении

Время и нагрузку от перестроения можно оптимизировать засчет использования дельта-индексов только для последнихизменений.

4 / 32

Структура индекса

.spd document lists (aka doclists)

.spp keyword positions lists (aka hitlists)

Загружаются в память:

.sph header file.spi dictionary (aka wordlist).spa attribute values.spm MVA values.spk kill list (aka klist)

5 / 32

Источник SQLMySQL, PostgreSQL, MS SQL, ODBC

• результат выполнения SQL-запроса• структура индекса жестко задана в конфигурации

source sql_source{

type = mysqlsql_host = localhost...sql_query = SELECT id, ts, category, title, body ...sql_attr_uint = categorysql_attr_timestamp = ts...

}

6 / 32

Источник xmlpipe2• вывод произвольной команды в стандартном XML• структура индекса определяется содержимым XML

source xmlsrc{

type = xmlpipe2xmlpipe_command = /usr/bin/php /path/to/script.php

}

Использование

• данные не из реляционной БД• невозможно получить данные из БД одним запросом• нужно менять состав индекса без изменения конфигурации

7 / 32

xmlpipe2: данные<?xml version="1.0" encoding="utf-8"?><sphinx:docset>

<sphinx:schema><sphinx:field name="subject"/><sphinx:field name="content"/><sphinx:attr name="published" type="timestamp"/><sphinx:attr name="author_id" type="int" bits="16" default="1"/>

</sphinx:schema>

<sphinx:document id="1234"><content>this is the main content <![CDATA[[and this <cdata> entry

must be handled properly by xml parser lib]]></content><published>1012325463</published><subject>note how field/attr tags can be

in <b class="red">randomized</b> order</subject><misc>some undeclared element</misc>

</sphinx:document>...

8 / 32

Realtime index

• источник данных отсутствует• данные для индексирования явно добавляются в индексприложением

• добавление, удаление и изменение может быть выполненов любой момент времени и сразу становится доступным

• производительность близка к производительностидискового индекса

• работа с realtime-индексом ничем не отличается от работыс дисковым индексом

• удобно для различных экспериментов со SphinxQL

9 / 32

Атрибуты

Хранятся в индексе, не участвуют в полнотекстовом поиске, нопозволяют фильтровать его результаты:

uint 1-bit — 32-bittimestamp UNIX timestamp

float 32-bitstring до 4MB но загружаются в памятьMVA набор целочисленных идентификаторов,

кешируется в памятиJSON произвольные JSON-данные, также расход памяти

10 / 32

Потребление памятиДисковый индекс:

• общий объем файлов индекса минус .spd и .spp.

Real-time индекс:

• память выделяется фрагментами, rt_mem_limit.• фрагмент заканчивается — записывается на диск ивыделяется следующий.

• по умолчанию 128M.

SHOW INDEX indexname STATUS

11 / 32

SphinxQL

Язык запросов к индексам Sphinx, совместимый с SQL

• доступен по протоколу MySQL• можно использовать любой MySQL клиент• MySQL сервер не нужен• индексы в виде таблиц, SELECT для построения запросов• команды для дополнительной информации о результате• очень похож на SQL, но есть различия

12 / 32

Минимальная конфигурация

index test # тестовый индекс{ # доступен в виде таблицы SphinxQL

type = rt # real-time indexpath = /path/to/index/files # путь к каталогу с файлам индексаrt_field = title # полеrt_attr_multi = categories # MVA-атрибутrt_attr_json = attributes # JSON-атрибут

}

searchd{

listen = 9306:mysql41 # порт для клиента mysqlpid_file = /path/to/pid # путь к pid-файлуbinlog_path = /path/to/binlog # путь к binary logmax_matches = 1000 # количество результатовworkers = threads # обязательно для real-time

}

13 / 32

Минимальная конфигурацияНастроить пути можно единообразно с помощью PHP:

#!/usr/bin/php<?php function p($p) { echo __DIR__."/$p\n"; } ?>index entity{

type = rtpath = <?php p("var") ?>...

Дальше можно экспериментировать:

$ searchd -c sphinx.conf$ mysql --port=9306 --host=127.0.0.1

14 / 32

Изменение индексаINSERT INTO test (id, title, categories, attributes)

VALUES(1, ’First document’, (5,10,20),’{ "attr1": 100, "ids": [10,20,30] }’);

DELETE FROM test WHERE id=1;DELETE FROM test WHERE id > 1000;TRUNCATE test;

-- Полное изменение (необходимо для полей, JSON и string):REPLACE INTO test (id, title, categories, attributes)

VALUES(1, ’First document title’, (5,15),’{ "attr2": 200, "ids": [10,20] }’);

-- Изменение по месту (для чисел, включая MVA):UPDATE test SET categories = (10,20) WHERE id=1;-- Также для числовых атрибутов JSON:UPDATE test SET attributes.attr1=200 WHERE id=1;

-- Добавление атрибутов (только int,bigint,float,bool):ALTER TABLE test ADD COLUMN git INTEGER;

15 / 32

SELECT• все вычисляемые выражения должны быть поименованы всписке колонок

• FROM — список индексов для поиска, а не JOIN• WHERE — один MATCH() для полнотекстового поиска,остальное — фильтры

• GROUP BY — возможна группировка по несколькимколонкам и вычисляемым выражениям

• ORDER BY — только по колонкам, вычисляемые выраженияпо имени колонки

• по умолчанию применяется LIMIT• OPTION — дополнительные опции выполнения запроса

16 / 32

WITHIN GROUP ORDER BY> SELECT * FROM product ORDER BY category ASC LIMIT 3;+------+----------+-------+---------+-------------+-----------+| id | category | price | title | tags | published |+------+----------+-------+---------+-------------+-----------+| 5 | 1 | 95 | Item 6 | 39,98,382 | 1 || 20 | 1 | 72 | Item 21 | 101,393,410 | 0 || 23 | 1 | 61 | Item 24 | 2,42,84 | 1 |+------+----------+-------+---------+-------------+-----------+

> SELECT * FROM product WHERE published=1 GROUP BY categoryORDER BY category ASC LIMIT 3;

+------+----------+-------+----------+------------+-----------+| id | category | price | title | tags | published |+------+----------+-------+----------+------------+-----------+| 5 | 1 | 95 | Item 6 | 39,98,382 | 1 || 158 | 2 | 45 | Item 159 | 71,246,290 | 1 || 50 | 3 | 99 | Item 51 | 30,157,500 | 1 |+------+----------+-------+----------+------------+-----------+

> SELECT * FROM product WHERE published=1GROUP BY category WITHIN GROUP ORDER BY PRICE DESC ORDER BY categoryASC LIMIT 3;

+------+----------+-------+-----------+-------------+-----------+| id | category | price | title | tags | published |+------+----------+-------+-----------+-------------+-----------+| 3841 | 1 | 100 | Item 3842 | 149,367,418 | 1 || 1112 | 2 | 100 | Item 1113 | 58,286,375 | 1 || 360 | 3 | 100 | Item 361 | 252,350,439 | 1 |+------+----------+-------+-----------+-------------+-----------+

17 / 32

GROUP {N} BY

> SELECT * FROM product WHERE published=1GROUP 3 BY categoryWITHIN GROUP ORDER BY PRICE DESCORDER BY category ASCLIMIT 12;

+------+----------+-------+-----------+-------------+-----------+| id | category | price | title | tags | published |+------+----------+-------+-----------+-------------+-----------+| 3841 | 1 | 100 | Item 3842 | 149,367,418 | 1 || 8827 | 1 | 100 | Item 8828 | 12,272,476 | 1 || 1735 | 1 | 99 | Item 1736 | 121,220,461 | 1 || 1112 | 2 | 100 | Item 1113 | 58,286,375 | 1 || 1306 | 2 | 99 | Item 1307 | 336,371,388 | 1 || 2173 | 2 | 98 | Item 2174 | 99,187,203 | 1 || 360 | 3 | 100 | Item 361 | 252,350,439 | 1 || 1480 | 3 | 100 | Item 1481 | 54,308,363 | 1 || 8214 | 3 | 100 | Item 8215 | 179,482,495 | 1 || 558 | 4 | 99 | Item 559 | 118,133,432 | 1 || 760 | 4 | 99 | Item 761 | 113,124,458 | 1 || 6849 | 4 | 99 | Item 6850 | 158,267,469 | 1 |+------+----------+-------+-----------+-------------+-----------+

18 / 32

INTERVAL

> SELECT id, price,INTERVAL(price, 0,21,41,61,81,101) prange, count(*) numFROM productWHERE published=1GROUP BY prange WITHIN GROUP ORDER BY price DESCORDER BY prange ASC;

+------+-------+--------+------+| id | price | prange | num |+------+-------+--------+------+| 146 | 20 | 1 | 1065 || 134 | 40 | 2 | 1020 || 713 | 60 | 3 | 1026 || 55 | 80 | 4 | 970 || 360 | 100 | 5 | 947 |+------+-------+--------+------+

19 / 32

GROUP BY MVA> SELECT id, title, tags FROM product LIMIT 4;+------+--------+-------------+| id | title | tags |+------+--------+-------------+| 1 | Item 2 | 150,387,449 || 2 | Item 3 | 72,139,239 || 3 | Item 4 | 95,199,261 || 4 | Item 5 | 179,288,467 |+------+--------+-------------+

> SELECT id, title, GROUPBY() tag, COUNT(*) numFROM productGROUP BY tagsWITHIN GROUP ORDER BY price DESCLIMIT 4;

+------+----------+------+------+| id | title | tag | num |+------+----------+------+------+| 13 | Item 14 | 266 | 71 || 30 | Item 31 | 64 | 61 || 50 | Item 51 | 157 | 50 || 101 | Item 102 | 385 | 55 |+------+----------+------+------+

20 / 32

JSON-атрибуты• поддерживаются с версии 2.1• filter, sort, group• массивы: LENGTH(), LEAST(), GREATEST()• поиск по JSON: ANY(), ALL(), INDEXOF()

{"category": 10,"title": "test","tags": [ 10, 20, 30 ],"attrs": [

{ "name": "attr1", "value": 500 },...

]}

21 / 32

JSON: ANY, ALL, INDEXOF

[ANY|ALL|INDEXOF] (cond FOR var IN json.array)

ANY хотя бы один элемент удовлетворяет условиюALL все элементы удовлетворяют условию

INDEXOF индекс первого элемента, удовлетворяющегоусловию

SELECT *, ANY (item.name = "attr1" AND item.value = 500FOR item IN json.attrs

) as condFROM index WHERE cond = 1;

22 / 32

JSON: плюсы и минусыСуперфича: возможность организации индекса с

произвольной структурой, например, поддержкапроизвольного набора атрибутов товара.

Проблемы:

• существенное потребление памяти• отсутствие (пока) поддержки GROUPBY() дляэлементов-массивов (поддержка просто GROUP BY — есть.

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

23 / 32

EAV-поискВ базе реализован EAV, нужно обеспечить поиск по значениямсвойств:

• создаем JSON-атрибут для значений свойств• используем линейную структуру { "attrId": valueId }• стараемся максимально использовать справочникизначений для минимизации значений индекса, строковыезначения в JSON — накладно

• изменение набора свойств автоматически учитывается приочередном переиндексировании

• отсутствующие в атрибуте свойства просто игнорируютсяпри поиске

24 / 32

EAV-поиск: пример

> SELECT * FROM product LIMIT 1\G*************************** 1. row ***************************

id: 1price: 596title: Item 2categories: 3,17,20attrs: {"a22":1847,"a39":1369,"a147":383,"a74":1341,"a14":1877,

"a38":1992,"a50":550, "a100":630,"a10":1533,"a118":615}

> SELECT id, title FROM PRODUCT WHERE categories=10 AND attrs.a61=1068;+-------+------------+| id | title |+-------+------------+| 11100 | Item 11101 || 34807 | Item 34808 |+-------+------------+

25 / 32

Фасетный поиск• категория 1 (30)• категория 2 (64)• категория 3 (52)

КатегорииMVA-атрибут categories, документпринадлежит нескольким категориям

• значение 1 (27)• значение 2 (32)• значение 3 (60)

Свойство 1Элемент attrs.a1 JSON-атрибута attrs,документу соответствует идентификаторзначение, само значение в справочнике

• значение 4 (12)• значение 5 (44)• значение 6 (13)

Свойство 2Элемент attrs.a2 JSON-атрибута attrs,аналогично.

Основной принцип — набор запросов с одинаковым WHERE(значения, выбранные пользователем), но разным GROUP BY.

26 / 32

Фасетный поиск> SELECT * FROM product LIMIT 2;+------+-------+--------+------------+----------------------------------+| id | price | title | categories | attrs |+------+-------+--------+------------+----------------------------------+| 1 | 570 | Item 2 | 10,12,20 | {"a5":11,"a4":8,"a3":5,"a6":6} || 2 | 131 | Item 3 | 5,18 | {"a5":2,"a4":13,"a2":18,"a6":27} |+------+-------+--------+------------+----------------------------------+

> SELECT GROUPBY() c, COUNT(*) n FROM productWHERE categories IN (1,2,7) AND attrs.a1 IN (3,5,10) AND attrs.a2 IN (1,2,20)GROUP BY categories HAVING c IN (1,2,7);

+------+------+| c | n |+------+------+| 1 | 126 || 7 | 160 || 2 | 152 |+------+------+

> SELECT attrs.a1 v, COUNT(*) n FROM productWHERE categories IN (1,2,7) AND attrs.a1 IN (3,5,10) AND attrs.a2 IN (1,2,20)GROUP BY attrs.a1;

+----------+------+| v | n |+----------+------+| 3 | 3 || 5 | 1 || 10 | 1 |+----------+------+

27 / 32

Multi-query OptimizationФасетный поиск — набор запросов с одинаковым WHERE,отличия только в группировке и сортировке→ можноприменить фильтр только один раз.

Выполняем запросы в режиме multiquery — несколькозапросов через ; за одно обращение к серверу.

SELECT attrs.a1 v, COUNT(*) n FROM productWHERE categories IN (1,2,7)

AND attrs.a1 IN (3,5,10) AND attrs.a2 IN (1,2,20)GROUP BY attrs.a1;SELECT attrs.a2 v, COUNT(*) n FROM productWHERE categories IN (1,2,7)

AND attrs.a1 IN (3,5,10) AND attrs.a2 IN (1,2,20)GROUP BY attrs.a2;...

28 / 32

Ограничения multi-queryНа данный момент:

• не выполняется для вычисляемых выражений• не выполняется для строковых атрибутов

Проверка выполнения оптимизации: xN, N — число совместновыполненных запросов, в query log.

[Sun Jul 12 15:18:17.000 2009] 0.040 sec x3 [ext/0/rel 747541 (0,20)] ...[Sun Jul 12 15:18:17.000 2009] 0.040 sec x3 [ext/0/ext 747541 (0,20)] ...[Sun Jul 12 15:18:17.000 2009] 0.040 sec x3 [ext/0/ext 747541 (0,20)] ...

29 / 32

Эффективность поиска

Атрибуты позволяют строить сложные условия поиска, но этоfullscan по индексу — затратно.

Самый эффективный вариант:

• ограничиваем результат за счет полнотекстового поиска• применяем сложные фильтры уже к полученномурезультату

Если полнотекстового поиска нет, эффективность поотношению к MySQL зависит от ситуации.

30 / 32

Диагностика> SHOW META+---------------+-------+| Variable_name | Value |+---------------+-------+| total | 3 || total_found | 3 || time | 0.002 |+---------------+-------+

> SHOW INDEX product STATUS+-------------------+---------+| Variable_name | Value |+-------------------+---------+| index_type | rt || indexed_documents | 49999 || indexed_bytes | 0 || ram_bytes | 5323677 || disk_bytes | 4872172 |+-------------------+---------+

> SHOW STATUS+--------------------+-------+| Counter | Value |+--------------------+-------+| uptime | 22 || connections | 1 || maxed_out | 0 || command_search | 2 || command_excerpt | 0 || command_update | 0 || command_delete | 0 || command_keywords | 0 |

...| avg_query_cpu | OFF || avg_dist_wall | 0.000 || avg_dist_local | 0.000 || avg_dist_wait | 0.000 || avg_query_reads | OFF || avg_query_readkb | OFF || avg_query_readtime | OFF |+--------------------+-------+

31 / 32

Что читать• Sphinx 2.1.4-release reference manual1

• Real time fulltext search with Sphinx2

• Fulltext engine for non fulltext searches3

• Sphinx search performance optimization: attribute-basedfilters4

• Full JSON Support in Trunk5

• Sphinx Faceting Example6

1http://sphinxsearch.com/docs/2.1.4/2http://slidesha.re/1gj1nS33http://slidesha.re/19D0y1n4bit.ly/1cTcJNI5http://sphinxsearch.com/blog/2013/08/08/full-json-support-in-trunk/6https://github.com/adriannuta/SphinxFacetingExample

32 / 32

top related