Pull to refresh

Comments 21

А я просто указал в DATABASES коннект к сфинксу с бекендом для MySQL и пользоватлся просто курсором. Не очень понял зачем нужны заморочки с ОРМ, там где использовать её не удобно.
Бесспорно, можно и ручками запросы писать, и результаты разбирать самостоятельно — всё от потребностей зависит.

ORM избавляет от рутины, упрощает поддержку, ускоряет разработку. На мой взгляд, от ORM стоит отказываться только если оно неспособно решить конкретную задачу, либо в целях оптимизации кода, когда необходимо выжать из кода всё.
Я правильно понял: наполнение индекса (если данные собираются по нескольким моделям сразу) лежит на плечах разработчика?
Что мне нравится в django-haystack, так это умение не только искать в бакэндах, но и удобный инструментарий для наполнения индексов.
Смотря, что вы подразумеваете под наполнением. Sphinx принципиально не имеет средств по наполнению индексов (что по одной, что по нескольким таблицам/моделям) на лету. RT-индексы — это всё-таки немножко другое. Поэтому их обновление — это полная индексация с нуля. Например по крону.

А вот работать с RT-индексами пока что нельзя. Но функционал будет реализован в скором будущем.

Если Вы про несколько моделей в одном индексе, то:

а) для разнородных моделей это занятие бессмысленное
б) если вы храните данные в нескольких связанных моделях (как это, например, делается для модели Users и профилей), то эти модели можно поместить в один индекс уже сейчас (при условии, что вы не храните данные более чем в двух последовательно связанных моделях). Хотя я что-то затрудняюсь ответить, как на автомате проиндексировать ту же модель Users — внести правки в неё проблематично. Разве что манкипатчить.
в) если несколько моделей имеют схожую структуру, то опять же их индексировать вместе не обязательно — сфинкс умеет искать по нескольким индексам и отдавать данные скопом. единственное требование тут — одинаковые поля у всех моделей (как SQL UNION)
Sphinx принципиально не имеет средств по наполнению индексов (что по одной, что по нескольким таблицам/моделям) на лету.

Да, я понял. Но функционал по полной переиндексации нужно реализовывать самому?

в) если несколько моделей имеют схожую структуру, то опять же их индексировать вместе не обязательно — сфинкс умеет искать по нескольким индексам и отдавать данные скопом. единственное требование тут — одинаковые поля у всех моделей (как SQL UNION)

Спасибо! Не знал об этом. С помощью ORM Вашего приложения это возможно?
Да, можно искать сразу по нескольким индексам — django-sphinx сам определит к какой модели относится каждый документ и вернёт объекты в том же порядке, в котором получил от sphinx.

Да, я понял. Но функционал по полной переиндексации нужно реализовывать самому?


Да. В данный момент — самому.
Но это выполняется единственной командой, которую можно поместить, например, в крон.
UFO just landed and posted this here
О, брат :) github.com/semirook/sphinxit. Дописать документацию всё никак руки не дойдут, но в коде много комментариев, можно разобраться (вообще это был внутренний проект). Поддержка свежего SphinxQL, Python 2 и 3, есть тесты. Не решаем ли мы одну и ту же задачу? Правда я изначально не хотел привязываться к фреймворку, поэтому Sphinxit можно использовать в любом python-проекте. Интеграцию лучше каким-то addon`ом уже выпустить, типа интроспекция моделей и обновление дельта-индексов по post-save сигналу.
Хотя ваша библиотека не поддерживает работу с RealTime-индексами, думаю, не сложно будет и её добавить.

Вы решили одну задачу из тех, что стоят перед django-sphinx, но решили уж точно лучше, чем я. Уже задумываюсь над тем, а не возложить ли эту задачу на sphinxit.

Кстати, сразу бросилось в глаза:

    def Search(self, *args):
        ....

    def Snippets(self, index, data, query):
        ....


Почему с заглавной?
RT-индексы будут, просто не было повода добавить их поддержку раньше, нигде не использовал. Основной индекс + дельта хорошо себя ведут, хотя несколько раз хотелось «бросить всё к чёрту и уехать в Кисловодск», то есть переписать реализацию на RT-индексах.

С заглавной, так как это функция-фабрика и я захотел подчеркнуть таким образом точку входа. В реальном проекте выглядит как:

Sphinxit = SphinxConnector(**settings.SPHINXIT_CONNECTION)
SphinxSearch = Sphinxit.Search
...
def get_sphinx_query(self):
    query = (SphinxSearch(*self.search_indexes).match(self.query)
                                               .limit(self.get_offset(), self.paginate_by))
 
    self.apply_filters_to_sphinx_query(query)
    self.apply_sortings_to_sphinx_query(query)

    return query
...


Ну а потом где надо:

...
search_result = self.get_sphinx_query().process()
self.sphinx_result = search_result['result']
self.total_found = int(search_result['meta']['total_found'])
...


Есть места, которые мне не нравятся и ещё массу всего нужно доделать и переделать. Но даже в текущем состоянии вполне неплохо зарекомендовавший себя в продакшене продукт получился. Надо как-то взять себя в кулак и таки дописать документацию с примерами :)
У вас в коде комментариев больше, чем самого кода :)
Куда уж больше примеров.
Кстати, вы решили как-то проблему фильтрации выдачи через OR? Вот из доки Сфинкса фрагмент:
WHERE clause. This clause will map both to fulltext query and filters. Comparison operators (=, !=, <, >, <=, >=), IN, AND, NOT, and BETWEEN are all supported and map directly to filters. OR is not supported yet but will be in the future.

Меня нехило это пробесило, пришлось долго гуглить, чтобы найти хак, который в итоге стал Q-объектом в Sphinxit. Но соединять таким образом MVA-атрибуты всё равно нельзя. Вот я по сей день думаю, как же так?!
Нет, даже не брался ещё за её решение.

Что-то мне подсказывает, что в самом Sphinx не было изначально предусмотрено такой возможности. Ни в API, ни даже в SphinxSE я не увидел упоминаний об OR.

Похоже, остаётся ждать нативной поддержки.
Если кроме OR никаких других преобразований не предвидится (т.е. по сути нужен банальный union матчей по разным фильтрам) — можно кинуть пачку запросов — с одинаковой полнотекстовой частью, но разными фильтрами. Запросы разделяем точкой с запятой и кидаем через интерфейс, который умеет работать с мультизапросами (mysqli например). Результаты потом объединяем ручками.
Возможно, в конфиге сфинкса также стОит подкрутить опции, отвечающие за кэширование результатов во время мультизапросов.

В идеальном случае сработает оптимизация: сфинкс увидит одинаковую полнотекстовую часть и вычислит её единственный раз. А потом для каждого запроса прогонит закэшированный результат через фильтры.
Там есть более просто решение:

SELECT id, (id=1) OR (id>=5) AS cnd FROM index WHERE MATCH('Hello') AND cnd>0;


Но соединять через OR MVA-атрибуты нельзя, вот в этом случае иначе как ручками я способа не знаю.
Оптимизация в сфинксе работает как-то странно. Я пробовал экспериментировать. Если я правильно понял, сфинкс оптимизирует запросы только если в списке полей указана звёздочка (*). Соответственно способ @artiflex из комментария ниже не будет оптимизироваться.

Поправьте меня, если ошибаюсь.

Поскольку речь идёт от python, mysqli — не вариант. А вот MySQLdb, похоже, мультизапросы не умеет. По крайней мере я не смог найти, как через него выполнить такой запрос.

@artiflex, а oursql умеет такое?

PS, а как имя пользователя вставить в коммент? Вроде правильный тег поставил — а что-то не то получилось.
Не совсем так. Условий гораздо больше.
Нужно, чтобы
1. Совпадали строки поиска (очевидно)
2. Если указаны веса — совпадали веса.
3. Совпадал режим поиска.
4. Совпадал режим ранкера
5. Совпадала отсечка (cutoff)
6. Совпадали фильтры
7. Совпадали выражения сортировки (если используются)
8. Совпадали гео-якори (если используются)
9. В select-листах не было выражений.

(10. В секции searchd в конфиге должны быть заданы размеры кэшей. Например:
subtree_docs_cache = 1M
subtree_hits_cache = 1M
)

В этом случае будет работать оптимизация (кэширование результатов) на уровне пачки запросов. (пачка — строка запросов, разделённых ;. В пачке допустимы select и show meta вперемешку).

(Та же оптимизация может работать и на кусочках единственного сложного запроса — для этого нужно только размеры кэшей).
Спасибо, что разжевали. Кстати, было бы неплохо, если б это присутствовало в документации.

Я пробовал даже совсем простые запросы к индексу с ну очень небольшим количеством документов (до 1000).

Запросы вида:

SELECT * FROM index; SELECT * FROM index ORDER BY field;

и

SELECT some_field FROM index; SELECT some_field FROM index ORDER BY field;

Значения кешей дефолтные. Параметры запросов — дефолтные. В первом случает оптимизирует, во втором — нет. От набора полей в SELECT не зависит никак. Любой список (даже идентичный тому, что возвращает "*") сразу же отключает оптимизацию. То есть про сложные выражения, кучи различных условий вообще речи не идёт.

Можете на данном примере объяснить, почему так происходит?
Попробую глянуть на выходных (или даже завтра).
Если удастся — либо откомментирую, либо поправлю :D

(сейчас в работе более забавные фичи — вроде HA (внутренняя замена HAproxy и LoadBalancing) и поддержка JSON (уже есть) ).
В общем — разбирались и отрыли древний код.
Очень давно, когда работали только по api и на каждый запрос слали каждый раз полный пакет всех атрибутов из схемы, какие-либо спец. выражения шли в select-лист. Собственно, наличие этого листа и позволяло сразу сказать, есть в запросе выражения или нет. И если есть — вырубить оптимизацию. Сейчас пока что лишь пришли к мнению, что «с этим надо что-то делать». Но насколько быстро — непонятно. На нынешней архитектуре — это слишком поздний и сложный анализ выходит.
Как говорится, лучше поздно, чем никогда.
Sign up to leave a comment.

Articles