В какой-то момент разработки проекта встал вопрос поиска по большому количеству текстов. Причем, тексты имеют различную длину: от твиттов до больших статей. Сначала, основным поисковым движком был выбран встроенный в Postgres _tsvector. Для поиска по простым правилам его было вполне достаточно. Массив текстов рос с большой скоростью, а правила поиска усложнялись, поэтому встроенный движок уже не покрывал требований.


Да, существует sphinx, у него есть отличная интеграция с Postgres, но была цель найти решение для использования elasticsearch с Postgres. Почему? elasticsearch показывал хорошие результаты в некоторых case-ах проекта. Да и уже был сервер с ним для хранения логов logstash-а. Также было желание найти такой инструмент, который полностью возьмет на себя синхронизацию данных.


В результате всего на просторах сети был найден проект ZomboDb, который как раз подходил под требования.


Страница проекта на github.


Установка расширения


Этот раздел является пересказом официальной инструкции.


Поддерживаемые на данный момент версии пакетов:


Пакет Версии
Elasticsearch 1.7.1+ (not 2.0)
Postgres 9.3, 9.4, 9.5

Моя конфигурация: Postgres 9.4, elasticsearch 1.7.5


  1. Со страницы необходимо скачать и установить пакет с плагином для Postgres (deb или rpm)
  2. В postgresql.conf добавить строку:


    local_preload_libraries = 'zombodb.so'`

  3. Перезапустить базу и создать extension:


    psql db_name -c "CREATE EXTENSION zombodb;"

  4. Далее, с той-же страницы необходимо скачать плагин для elasticsearch и установить его:


    bin/plugin -i zombodb -u file:///path/to/zombodb-plugin-X.X.X.zip

  5. Добавить в elasticsearch.yml:


    threadpool.bulk.queue_size: 1024
    threadpool.bulk.size: 12
    http.max_content_length: 1024mb
    index.query.bool.max_clause_count: 1000000

  6. Перезапустить elasticsearch.

На этом установка закончена


Создание тестового индекса


Предположим, есть таблицы, в которой лежат теги:


CREATE TABLE public.tags (
  id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('tags_id_seq'::regclass),
  word CHARACTER VARYING(100) NOT NULL
);

Создание индекса:


CREATE INDEX tags_zdb_search_idx
ON tags
USING zombodb (zdb('tags', tags.ctid), zdb(tags))
WITH (url='http://localhost:9200/');

В результате этого запроса создается индекс, и данные сразу поступают в elasticsearch.
Запрос, который найдет слова мама и папа:


SELECT *
FROM tags
WHERE zdb('tags', ctid) ==> 'word:(мама,папа)';

Где word — название поля, по которому будет производиться поиск. Поиск реализуется с помощью оператора ==>.


Также, ZomboDb предоставляет домены phrase и fulltext, базирующиеся на типе text. Используя собственные домены можно определять маппинг для elasticsearch.


Язык запросов


С помощью запросов можно искать по отдельным полям индексируемой таблицы, также и по всем полям.
Запросы поддерживают логические операции (and, or, not), скобки.
Есть возможность использовать различные поисковые операторы. Например, запрос


SELECT *
FROM texts
WHERE zdb('texts', ctid) ==> 'text:папа';

где оператором является двоеточие, вернет тексты, содержащие слово папа.
Также поддерживаются операции more like this и fuzzy like this через операторы :@ и :@~ соответственно.
Пример:


SELECT *
FROM texts
WHERE zdb('texts', ctid) ==> '(text:@папа and title:@мама) or text:тетя';

Также, есть поддержка операторов сравнения:


SELECT *
FROM texts
WHERE zdb('texts', ctid) ==> 'comments > 10';

Подробное описание языка запросов в документации.


Выводы


Проект является хорошим продуктом, который работает "из коробки". Хорошо документирован, обновляется (поддерживается последняя версия Postgres, последний коммит на момент написания статьи 27-и дневной давности). Если хорошо и стабильно покажет себя в production, напишу обертку для sqlalchemy.


Спасибо за внимание!


UPD Написал на скорую руку sqlalchemy extension

Поделиться с друзьями
-->

Комментарии (13)


  1. AlexzundeR
    21.06.2016 14:32

    Спасибо за обзор! А можно поподробнее как эта связка работает?
    1. Смогу ли я одновременно сделать WHERE и сделать полнотекстовый поиск по отдельной колонке?
    2. Где в конечном счете хранится текст статьи в индексе elastic или в postgresql?
    3. Возможно ли запрос к elastic, который идет в SQL, сделать на языке запросов elastic (например OR AND вставки или кастомные операторы какого-либо плагина к elastic)?


    1. vpiskunov
      21.06.2016 15:09
      +3

      1. Да, можно. Можно искать как по полям Postgres, так и используя индексированные эластиком поля. Тоесть, 2 варианта:


        SELECT test_elastic.text
        FROM test_elastic
        WHERE zdb('test_elastic', ctid) ==> 'text:(набор термов) and comments > 10';

        и


        SELECT test_elastic.text
        FROM test_elastic
        WHERE zdb('test_elastic', ctid) ==> 'text:(набор термов)' AND test_elastic.comments > 10;

        будут работать


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


      3. Да, это расширение парсит OR, AND, скобки. Есть свой набор операторов, которые умеет парсить (ссылка). Как простым способом определить свой dsl я пока не понял.


      1. AlexzundeR
        22.06.2016 00:45

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


        1. zombodb
          22.06.2016 01:57

          Maybe I'm not exactly understanding what you mean by 'plugins for additional languages', but ZomboDB supports, out-of-the-box, Elasticsearch's full set of language analyzers. You can read about all of that starting here: https://github.com/zombodb/zombodb/blob/master/TYPE-MAPPING.md#zombodb-custom-domain-types

          If there's a 3rd-party plugin that adds new analyzers, then ZomboDB can automatically support that too — you would just need to CREATE DOMAIN in Postgres for that analyzer and use that domain as your field type.


    1. zombodb
      21.06.2016 23:03
      +7

      Hi! I'm the ZomboDB author. I don't speak Russian so I am reading this post through Google Translate. Please pardon me if I don't exactly understand what you're asking, but I'll try to answer as best I can.

      1) Yes, you can combine standard Postgres WHERE clause predicates with ZomboDB text-search queries. For example:

      SELECT * FROM test_elastic WHERE comments > 10 AND zdb('test_elastic', ctid) ==> 'text:(term1,term2,termN, «this is a phrase)';

      Postgres will plan the query accordingly, based on the types of indexes you have on your columns. ZomboDB text queries (==>) can also work when Postgres plans a sequential scan, so it's quite powerful here.

      My experience is that with a large amount of data, it's significantly faster to just do everything within the ZomboDB text query. If you have a good Elasticsearch cluster, it's VERY fast!

      2) ZomboDB stores the tokens of each column in Elasticsearch, not the whole row. ZomboDB also operates within Postgres' „Access Method API“ interface such that it's not very different than a standard „btree“ index, as far as Postgres is concerned. As such, your source of truth is *always* Postgres and the ZomboDB index stores the minimal amount of data in Elasticsearch to enable text searching.

      3) ZomboDB has its own query language that supports quite a bit of the Elasticsearch QueryDSL (not everything, but very close!). Full boolean expressions are supported. If you needed to expose the functionality of a random Elasticsearch plugin to ZomboDB that would likely require changes to ZomboDB. If you see this is a thing you'd need to do often, I'd love to hear more details on ZomboDB's github page.

      Thanks for your interest and I hope my answers have been helpful!


      1. AlexzundeR
        22.06.2016 00:57

        Hi! Thank you for answers, it was very helpful and interesting. Later I will ask more detail question about custom query language in thirdparty plugin.
        It was very cool that you feedback potential users in so unknown in non-russian internet resource =)


        1. zombodb
          22.06.2016 02:01

          I appreciate your, and of course vpiskunov's interest. This post is very good!

          I'll probably be better able to help on ZomboDB's github site through Issues. There's so much on this site I can't read! :)


  1. stalkerg
    22.06.2016 13:18

    И всё же:
    1. Может по подробнее расписать, чего не хватает в языке запросов в самом Postgres?
    2. Если не хватало скорости то вы использовали GIN или GIST индекс?
    3. Если нужно работать с тегами то ИМХО лучше воспользоватся intarray. В моём usecase мне помогло ну и в 9.6 я исправил планировщик (точнее оценку селективности, оно теперь пытается гистограммы использовать).

    Спасибо.


    1. vpiskunov
      22.06.2016 13:28

      1. Задачи были: Искать похожие тексты, поиск по словосочетаниям. Также, была идея найти продукт, который позволит не писать отдельные запросы напрямую к elastic, а использовать его подобным образом.
      2. Да, на ts_vector есть индекс. Не хватало возможностей ts_vector из коробки. Вероятно, я что-то не знаю о его возможностях. Опять-же, см пункт 1 моего ответа.
      3. см пункт 1, задачи стояли не только в поиске по тегам. По тегам я просто описал пример синтаксиса запроса. Согласен, концептуально пример не очень валиден.


    1. the_unbridled_goose
      22.06.2016 19:13

      1. Проблема в поиске именно двух подряд идущих слов. Например, Иван Иванов. Из очевидного — искали регуляркой по тексту уже полученных через поиск по вектору данных. Гибкости абсолютно никакой. Необходимо учитывать все вариации окончаний обоих слов, плюс одно дело это формировать руками в базе, и совсем другое — в автоматическом режиме. Да еще и запрос притормаживает ощутимо, так как лайк, или симилар.
      2. Использовали GIN.


      1. stalkerg
        22.06.2016 20:09
        +2

        Этот вопрос скорее всего в новой версии будет решён, там появилась возможность указывать расстояние между словами. Вроде уже в 9.6 есть. Могу конечно ошибаться но вроде оно…


        tsquery_phrase(to_tsquery('fat'), to_tsquery('cat'))    
            'fat' <-> 'cat'
        
        tsquery_phrase(to_tsquery('fat'), to_tsquery('cat'), 10)
            'fat' <10> 'cat'


        1. vpiskunov
          22.06.2016 22:26

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


          1. stalkerg
            23.06.2016 14:00

            Если вам чего то реально не хватает в postgres но есть в elasticsearch или ещё где, то вы обязательно пишите, вполне возможно это будет реализовано.


            ЗЫ на замену GIN может скоро придёт RUM т.е. с индексами то же будет лучше.