$30 off During Our Annual Pro Sale. View Details »

Поиск? Sphinx!

Поиск? Sphinx!

Sphinx считается одним из самых быстрых и гибких поисковых движков на рынке, но не является "коробочным" решением, чем отпугивает многих разработчиков. Я расскажу как быстро поднять полнотекстовый поиск для своего проекта на базе Sphinx, почему он крут и какие существуют интеграционные решения для Python.

Roman Zaiev

June 08, 2013
Tweet

More Decks by Roman Zaiev

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. БД вакансий

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. НО И ЭТО НЕ ВСЁ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. ШАРА
    GPL2

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. NATIVE. FAST.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  49. СНИППЕТЫ

    View Slide

  50. RTFM, please

    View Slide

  51. WORKFLOW

    View Slide

  52. searchd & indexer
    это...

    View Slide

  53. 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

    View Slide

  54. 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

    View Slide

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

    View Slide

  56. DISK RT

    View Slide

  57. DISK RT
    pull push

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  62. ПРАКТИКУМ

    View Slide

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

    View Slide

  64. sphinx.conf

    View Slide

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

    View Slide

  66. 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
    }

    View Slide

  67. 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
    }
    выборка
    атрибуты

    View Slide

  68. 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
    ...
    }

    View Slide

  69. 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
    ...
    }
    «джентельменский набор»
    десятки других опций

    View Slide

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

    View Slide

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

    View Slide

  72. 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
    }

    View Slide

  73. 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

    View Slide

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

    View Slide

  75. 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).

    View Slide

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

    View Slide

  77. SphinxQL
    API

    View Slide

  78. $ 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>

    View Slide

  79. 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)

    View Slide

  80. 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)

    View Slide

  81. 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)

    View Slide

  82. REAL LIFE

    View Slide

  83. «Порционный» запрос
    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
    }

    View Slide

  84. Расширяем «охват» индекса
    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
    }

    View Slide

  85. 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
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  89. 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'
    }
    Модифицируем основной индекс

    View Slide

  90. 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
    }

    View Slide

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

    View Slide

  92. 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)

    View Slide

  93. А питон?..

    View Slide

  94. :(

    View Slide

  95. https://github.com/semirook/sphinxit

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  102. $ pip install sphinxit

    View Slide

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

    View Slide

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

    View Slide

  105. 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

    View Slide

  106. 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

    View Slide

  107. sphinxit | result
    {
    u'result': [
    {
    'date_created': 2008L,
    'owner_email': u'[email protected]',
    'products': u'2,5',
    'id': 5015L,
    'name': u'\u0422\u041d\u041a',
    },
    {
    'date_created': 2009L,
    'owner_email': u'[email protected]',
    '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'
    }
    }

    View Slide

  108. 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

    View Slide

  109. 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'}
    ]
    }

    View Slide

  110. 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
    )

    View Slide

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

    View Slide

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

    View Slide

  113. СПАСИБО!
    @semirook
    [email protected]

    View Slide