sphinx — универсальное средство поиска
Post on 15-Nov-2014
2.213 views
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