Slide 1

Slide 1 text

ПОИСК? Роман Семирук SPHINX!

Slide 2

Slide 2 text

НО СНАЧАЛА...

Slide 3

Slide 3 text

Очень корпоративный сайт

Slide 4

Slide 4 text

Блог с кучей статей

Slide 5

Slide 5 text

БД вакансий

Slide 6

Slide 6 text

ПОЛНОТЕКСТОВЫЙ ПОИСК ключевое слово

Slide 7

Slide 7 text

Все любят сниппеты

Slide 8

Slide 8 text

СНИППЕТЫ ключевое слово

Slide 9

Slide 9 text

Кластеризация, она же группировка

Slide 10

Slide 10 text

КЛАСТЕРИЗАЦИЯ ключевое слово

Slide 11

Slide 11 text

Более лучшие сложные фильтры...

Slide 12

Slide 12 text

ФИЛЬТРАЦИЯ ключевое слово

Slide 13

Slide 13 text

Двусторонняя сортировка

Slide 14

Slide 14 text

СОРТИРОВКА ключевое слово

Slide 15

Slide 15 text

ПОВЫШАЕМ ГРАДУС ГИКОВОСТИ

Slide 16

Slide 16 text

ДОКУМЕНТЫ ключевое слово

Slide 17

Slide 17 text

АТРИБУТЫ ключевое слово

Slide 18

Slide 18 text

ИНДЕКС ключевое слово

Slide 19

Slide 19 text

ЯЗЫК ЗАПРОСОВ ключевое слово

Slide 20

Slide 20 text

МОРФОЛОГИЧЕСКИЙ АНАЛИЗ ключевое слово

Slide 21

Slide 21 text

РЕЛЕВАНТНОСТЬ ключевое слово

Slide 22

Slide 22 text

РАНКЕР / РАНЖИРОВАНИЕ ключевое слово

Slide 23

Slide 23 text

НО И ЭТО НЕ ВСЁ

Slide 24

Slide 24 text

МАСШТАБИРОВАНИЕ ключевое слово

Slide 25

Slide 25 text

НЕПОЛНОТЕКСТОВЫЙ ПОИСК ключевое слово

Slide 26

Slide 26 text

И никто даже не догадывается...

Slide 27

Slide 27 text

полнотекстовый поиск фильтрация сортировка группировка сниппеты работа с существующими документами широкий набор атрибутов быстрая индексация гибкий язык запросов морфологический анализатор управление релевантностью масштабируемость скорость качество стоимость Обычные требования к поиску

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

РЕКЛАМНАЯ ПАУЗА

Slide 30

Slide 30 text

SQL PHRASE INDEX оказывается, это...

Slide 31

Slide 31 text

НАБОР СПЕЦИАЛИЗИРОВАННЫХ ПРИЛОЖЕНИЙ* * indexer, searchd, search, spelldump, indextool, wordbreaker

Slide 32

Slide 32 text

ШАРА GPL2

Slide 33

Slide 33 text

БЫСТРО. ОЧЕНЬ.

Slide 34

Slide 34 text

ИНДЕКСАЦИЯ до 10-15 МБ/сек на ядро* * CPU решает

Slide 35

Slide 35 text

ПОИСК до 250 запросов в секунду на каждое ядро с 1 000 000 документов * * зависит от размера индекса

Slide 36

Slide 36 text

MYSQL POSTGRESQL MS SQL ORACLE XML ИСТОЧНИКИ

Slide 37

Slide 37 text

NATIVE. FAST.

Slide 38

Slide 38 text

ВЫСОКАЯ МАСШТАБИРУЕМОСТЬ* * доказано британскими учёными

Slide 39

Slide 39 text

CRAIGSLIST.ORG 250 миллионов запросов в день несколько миллиардов документов кластер из 15 инстансов Sphinx

Slide 40

Slide 40 text

LINUX BSD OS X WINDOWS ПЛАТФОРМЫ

Slide 41

Slide 41 text

АРХИТЕКТУРЫ x86 x86_64 SPARC64 ARM

Slide 42

Slide 42 text

API* * PHP, Perl, Java, Python, Ruby и некоторые другие

Slide 43

Slide 43 text

SQL-подобный язык запросов SphinxQL

Slide 44

Slide 44 text

ПОЛИГЛОТ русский français español italiano deutsch svensk suomalainen english čeština !"#$%&ا

Slide 45

Slide 45 text

ГИБКИЕ НАСТРОЙКИ тачка на прокачку

Slide 46

Slide 46 text

РАНКЕРЫ ранжирование, релевантность и всё такое

Slide 47

Slide 47 text

до 256 полей для индексации на один индекс до 32-х атрибутов различных типов * хватит на все случаи жизни

Slide 48

Slide 48 text

АТРИБУТЫ unsigned integers floating point values bool strings unix timestamp MVA – multi-value attributes JSON (new)

Slide 49

Slide 49 text

СНИППЕТЫ

Slide 50

Slide 50 text

RTFM, please

Slide 51

Slide 51 text

WORKFLOW

Slide 52

Slide 52 text

searchd & indexer это...

Slide 53

Slide 53 text

SQL database index_file.spa index_file.sph index_file.spk index_file.spp index_file.spd index_file.spi index_file.spm index_file.sps searchd indexer

Slide 54

Slide 54 text

searchd index_file.spa index_file.sph index_file.spk index_file.spp index_file.spd index_file.spi index_file.spm index_file.sps indexer APP cron SQL database

Slide 55

Slide 55 text

Disk или RT? * мир перевернулся, индексы бывают разные

Slide 56

Slide 56 text

DISK RT

Slide 57

Slide 57 text

DISK RT pull push

Slide 58

Slide 58 text

DISK RT pull эффективная структура push фрагментация

Slide 59

Slide 59 text

DISK RT pull эффективная структура монолит push фрагментация обновление на лету

Slide 60

Slide 60 text

DISK RT pull эффективная структура монолит дельта-индексы push фрагментация обновление на лету частые дампы

Slide 61

Slide 61 text

DISK RT pull эффективная структура монолит дельта-индексы push фрагментация обновление на лету частые дампы

Slide 62

Slide 62 text

ПРАКТИКУМ

Slide 63

Slide 63 text

$ apt-get install sphinx $ brew install sphinx Ubuntu OS X $ ./configure --with-pgsql $ make $ make install DIY

Slide 64

Slide 64 text

sphinx.conf

Slide 65

Slide 65 text

sphinx.conf | connection source common { type = pgsql sql_host = localhost sql_user = sphinx sql_pass = sphinx sql_db = megaportal }

Slide 66

Slide 66 text

sphinx.conf | source description source company: common { sql_query =\ SELECT company.id, \ company.name, \ company.date_created \ FROM company sql_field_string = name sql_attr_timestamp = date_created }

Slide 67

Slide 67 text

sphinx.conf | source description source company: common { sql_query =\ SELECT company.id, \ company.name, \ company.date_created \ FROM company sql_field_string = name sql_attr_timestamp = date_created } выборка атрибуты

Slide 68

Slide 68 text

sphinx.conf | index description index common { type = plain morphology = stem_en, stem_ru min_word_len = 2 charset_type = utf-8 html_strip = 1 html_remove_elements = script html_index_attrs = img=alt,title; a=title; min_stemming_len = 3 min_infix_len = 3 enable_star = 1 ... }

Slide 69

Slide 69 text

sphinx.conf | index description index common { type = plain morphology = stem_en, stem_ru min_word_len = 2 charset_type = utf-8 html_strip = 1 html_remove_elements = script html_index_attrs = img=alt,title; a=title; min_stemming_len = 3 min_infix_len = 3 enable_star = 1 ... } «джентельменский набор» десятки других опций

Slide 70

Slide 70 text

sphinx.conf | index description index company: common { source = company path = /path/to/index_files/megaportal }

Slide 71

Slide 71 text

sphinx.conf | indexer tuning indexer { mem_limit = 512M }

Slide 72

Slide 72 text

sphinx.conf | indexer tuning searchd { listen = 9306:mysql41 log = /path/to/logs/searchd.log query_log = /path/to/logs/query.log pid_file = /path/to/pid/searchd.pid }

Slide 73

Slide 73 text

sphinx.conf | fatality source common { type = pgsql sql_host = localhost sql_user = sphinx sql_pass = sphinx sql_db = megaportal } source company: common { sql_query =\ SELECT company.id, \ company.name, \ company.date_created \ FROM company sql_field_string = name sql_attr_timestamp = date_created } index common { type = plain morphology = stem_en, stem_ru min_word_len = 2 charset_type = utf-8 html_strip = 1 html_remove_elements = script html_index_attrs = img=alt,title; a=title; min_stemming_len = 3 min_infix_len = 3 enable_star = 1 } index company: common { source = company path = /path/to/index_files/megaportal } indexer { mem_limit = 512M } searchd { listen = 9306:mysql41 log = /path/to/logs/searchd.log query_log = /path/to/logs/query.log pid_file = /path/to/pid/searchd.pid } описание документов описание индексов indexer + searchd

Slide 74

Slide 74 text

$ indexer -c /path/to/sphinx.conf --all

Slide 75

Slide 75 text

Sphinx 2.0.6-release (r3473) Copyright (c) 2001-2012, Andrew Aksyonoff Copyright (c) 2008-2012, Sphinx Technologies Inc (http://sphinxsearch.com) using config file '/path/to/sphinx.conf'... indexing index 'common'... ERROR: index 'common': no valid sources configured; skipping. indexing index 'company'... collected 1066244 docs, 28.7 MB sorted 2.3 Mhits, 100.0% done total 1066244 docs, 28735925 bytes total 11.452 sec, 2509034 bytes/sec, 93097.50 docs/sec total 60 reads, 0.019 sec, 495.0 kb/call avg, 0.3 msec/call avg total 127 writes, 0.053 sec, 471.7 kb/call avg, 0.4 msec/call avg rotating indices: successfully sent SIGHUP to searchd (pid=56606).

Slide 76

Slide 76 text

$ searchd -c /path/to/sphinx.conf

Slide 77

Slide 77 text

SphinxQL API

Slide 78

Slide 78 text

$ mysql -h 0 -P 9306 Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1 Server version: 2.0.6-release (r3473) Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>

Slide 79

Slide 79 text

mysql> select * from company where match('тнк'); +--------+--------+--------------+----------+ | id | status | date_created | owner_id | +--------+--------+--------------+----------+ | 5015 | 6 | 2008 | 5019 | | 25502 | 3 | 2009 | 25507 | | 39771 | 6 | 2009 | 39776 | | 152307 | 1 | 2010 | 152380 | | 183905 | 3 | 2010 | 184097 | | 194302 | 6 | 2010 | 194517 | | 218982 | 1 | 2011 | 219439 | | 235881 | 3 | 2011 | 236408 | | 287319 | 3 | 2011 | 288131 | | 338476 | 3 | 2011 | 339574 | | 340073 | 6 | 2011 | 341177 | | 471410 | 2 | 2012 | 473498 | | 513023 | 0 | 2012 | 515276 | | 768093 | 1 | 2012 | 770983 | | 823359 | 6 | 2012 | 826706 | | 915374 | 3 | 2013 | 919765 | +--------+--------+--------------+----------+ 16 rows in set (0.00 sec)

Slide 80

Slide 80 text

mysql> select * from company where match('тнк') and date_created between 2010 and 2012; +--------+--------+--------------+----------+ | id | status | date_created | owner_id | +--------+--------+--------------+----------+ | 152307 | 1 | 2010 | 152380 | | 183905 | 3 | 2010 | 184097 | | 194302 | 6 | 2010 | 194517 | | 218982 | 1 | 2011 | 219439 | | 235881 | 3 | 2011 | 236408 | | 287319 | 3 | 2011 | 288131 | | 338476 | 3 | 2011 | 339574 | | 340073 | 6 | 2011 | 341177 | | 471410 | 2 | 2012 | 473498 | | 513023 | 0 | 2012 | 515276 | | 768093 | 1 | 2012 | 770983 | | 823359 | 6 | 2012 | 826706 | +--------+--------+--------------+----------+ 12 rows in set (0.01 sec)

Slide 81

Slide 81 text

mysql> SHOW META; +---------------+--------+ | Variable_name | Value | +---------------+--------+ | total | 6 | | total_found | 6 | | time | 0.000 | | keyword[0] | тнк | | docs[0] | 16 | | hits[0] | 16 | +---------------+--------+ 6 rows in set (0.00 sec) mysql> DESC company; +--------------+-----------+ | Field | Type | +--------------+-----------+ | id | integer | | name | field | | status | uint | | date_created | timestamp | | owner_id | uint | +--------------+-----------+ 5 rows in set (0.00 sec)

Slide 82

Slide 82 text

REAL LIFE

Slide 83

Slide 83 text

«Порционный» запрос source company: common { sql_query_range =\ SELECT MIN(id), MAX(id) from company sql_range_step = 10000 sql_query =\ SELECT company.id, \ company.name, \ company.date_created \ FROM company \ WHERE company.id BETWEEN $start AND $end sql_field_string = name sql_attr_timestamp = date_created }

Slide 84

Slide 84 text

Расширяем «охват» индекса source company: common { ... sql_query =\ SELECT company.id, \ company.name, \ company.date_created, \ "user".email as owner_email \ FROM company \ LEFT JOIN "user" \ ON company.owner_id = "user".id \ WHERE company.id BETWEEN $start AND $end sql_field_string = name sql_field_string = owner_email sql_attr_timestamp = date_created }

Slide 85

Slide 85 text

One-to-many? M2M? MVA! source company: common { ... sql_field_string = name sql_field_string = owner_email sql_attr_timestamp = date_created sql_attr_multi =\ uint products from ranged-query; \ SELECT company_id, id FROM product \ WHERE id >= $start AND id <= $end; \ SELECT MIN(id), MAX(id) FROM product }

Slide 86

Slide 86 text

mysql> select name, products from company where match('тнк') and products in (109503, 1123362); +------------------------------+----------+ | name | products | +------------------------------+----------+ | ТНК-Транс | 109503 | | ООО «ТНК-Транс» | 1123362 | +------------------------------+----------+ 2 rows in set (0.00 sec)

Slide 87

Slide 87 text

Δ -индексы * такие же как обычные, только маленькие и другие

Slide 88

Slide 88 text

Процесс индексации пред-запросы пост-запросы перестройка индекса пост-обработка выборка 1 2 3 5 4 * открытие-закрытие соединений не указаны

Slide 89

Slide 89 text

source company: common { sql_query_pre = SET @maxts:=(SELECT NOW()) sql_query = SELECT company.id, company.name FROM company \ WHERE company.created_at < @maxts ... sql_query_post =\ REPLACE INTO search_deltacounters \ VALUES (@id, 'company_tmp', @maxts) sql_query_post_index =\ DELETE FROM search_deltacounters \ WHERE tablename='company' sql_query_post_index =\ UPDATE search_deltacounters \ SET tablename='company' WHERE tablename='company_tmp' } Модифицируем основной индекс

Slide 90

Slide 90 text

source company_delta: company { sql_query_pre =\ SET @maxts=(SELECT maxts FROM search_deltacounters \ WHERE tablename='company') sql_query = SELECT company.id, company.name FROM company \ WHERE company.created_at >= @maxts ... sql_query_post = sql_query_post_index = } Создаём дельту index company_delta: common { source = company_delta path = /path/to/indexes/megaportal }

Slide 91

Slide 91 text

$ indexer -c /path/to/sphinx.conf company_delta --rotate

Slide 92

Slide 92 text

mysql> select * from company, company_delta where match('тнк'); +--------+--------+--------------+----------+ | id | status | date_created | owner_id | +--------+--------+--------------+----------+ | 5015 | 6 | 2008 | 5019 | | 25502 | 3 | 2009 | 25507 | | 39771 | 6 | 2009 | 39776 | | 152307 | 1 | 2010 | 152380 | | 183905 | 3 | 2010 | 184097 | | 194302 | 6 | 2010 | 194517 | | 218982 | 1 | 2011 | 219439 | | 235881 | 3 | 2011 | 236408 | | 287319 | 3 | 2011 | 288131 | | 338476 | 3 | 2011 | 339574 | | 340073 | 6 | 2011 | 341177 | | 471410 | 2 | 2012 | 473498 | | 513023 | 0 | 2012 | 515276 | | 768093 | 1 | 2012 | 770983 | | 823359 | 6 | 2012 | 826706 | | 915374 | 3 | 2013 | 919765 | +--------+--------+--------------+----------+ 16 rows in set (0.00 sec)

Slide 93

Slide 93 text

А питон?..

Slide 94

Slide 94 text

:(

Slide 95

Slide 95 text

https://github.com/semirook/sphinxit

Slide 96

Slide 96 text

НЕ ORM https://github.com/semirook/sphinxit

Slide 97

Slide 97 text

Лёгкий конструктор SphinxQL-запросов https://github.com/semirook/sphinxit

Slide 98

Slide 98 text

https://github.com/semirook/sphinxit Oursql & MySQLdb

Slide 99

Slide 99 text

Framework-free https://github.com/semirook/sphinxit

Slide 100

Slide 100 text

https://github.com/semirook/sphinxit Python 2 & 3 спасибо, six

Slide 101

Slide 101 text

https://github.com/semirook/sphinxit Тесты :) спасибо, тесты

Slide 102

Slide 102 text

$ pip install sphinxit

Slide 103

Slide 103 text

sphinxit | config class SearchConfig(object): DEBUG = True WITH_META = True WITH_STATUS = True SEARCHD_CONNECTION = { 'host': '127.0.0.1', 'port': 9306, }

Slide 104

Slide 104 text

sphinxit | usage from sphinxit.core.processor import Search, Snippet company_search = ( Search( indexes=['company'], config=SearchConfig ) .match('ТНК') ) SELECT * FROM company WHERE MATCH('ТНК')

Slide 105

Slide 105 text

sphinxit | usage search = Search(['company'], config=SearchConfig) search = ( search .match('ТНК') .select('id', 'name') .options( ranker='proximity', max_matches=100, ) .order_by('name', 'desc') ) SELECT id, name FROM company WHERE MATCH('ТНК') ORDER BY name DESC OPTION max_matches=100, ranker=proximity

Slide 106

Slide 106 text

sphinxit | usage search = Search(['company'], config=SearchConfig) search = ( search .match('ТНК') .filter(date_created__lte=datetime.date.today()) ) results = search.ask() SELECT * FROM company WHERE MATCH('ТНК') AND date_created<=1370552400

Slide 107

Slide 107 text

sphinxit | result { u'result': [ { 'date_created': 2008L, 'owner_email': u'5019@example.com', 'products': u'2,5', 'id': 5015L, 'name': u'\u0422\u041d\u041a', }, { 'date_created': 2009L, 'owner_email': u'25507@example.com', 'products': u'2,5', 'id': 25502L, 'name': u'\u0422\u041d\u041a \u0418\u043d\u0442\u0435\u0440\u043', } ], ... u'meta': { u'total': u'16', u'total_found': u'16', u'docs[0]': u'16', u'time': u'0.000', u'hits[0]': u'16', u'keyword[0]': u'\u0442\u043d\u043a' } }

Slide 108

Slide 108 text

sphinxit | update syntax search = Search(['company'], config=SearchConfig) search = ( search .match('ТНК') .update(products=(5,2)) .filter(id__gt=1) ) UPDATE company SET products=(5,2) WHERE MATCH('ТНК') AND id>1

Slide 109

Slide 109 text

sphinxit | snippets syntax snippets = ( Snippet(index='company', config=SearchConfig) .for_query("Me amore") .from_data("amore", "amore mia") ) CALL SNIPPETS ( ('amore', 'me amore'), 'company', 'Me amore' ); { u'result': [ {'snippet': u'amore'}, {'snippet': u'amore mia'} ] }

Slide 110

Slide 110 text

sphinxit | snippets syntax snippets = ( Snippet(index='company', config=SearchConfig) .for_query("Me amore") .from_data("amore mia") .options( before_match='', after_match='', ) ) CALL SNIPPETS ( 'amore mia', 'company', 'Me amore', '' AS before_match, '' AS after_match )

Slide 111

Slide 111 text

Pull requests, please https://github.com/semirook/sphinxit

Slide 112

Slide 112 text

http://sphinxsearch.com/docs/ So, you want to know more?.. http://sphinxsearch.com/forum/

Slide 113

Slide 113 text

СПАСИБО! @semirook semirook@gmail.com