Pull to refresh

Comments 47

>> Запрос можно было бы упростить представлением (view), но насколько я понял этого делать не рекомендуют и прямой запрос к данным с джойнами эффективнее по соображениям создаваемой нагрузки при индексировании.

Разницы между view и запросом не будет так как для mysql это всего лишь сохраненный запрос.
Оптимизатор не пальцем делан :)
В этом как раз и есть сомнения: при большом объеме данных этот оптимизитор, деланный не пальцем, может лажать. И в случае с прямым запросом — можно вручную исправить.

Спорить не буду, вот здесь в коментах прочитал www.ibm.com/developerworks/ru/library/os-php-sphinxsearch/#icomments
Две ошибки породили заблуждение.
Что бы разобраться надо прочитать комментарий внимательней.
В нем идет речь о том что документы индексируемые sphinx должны иметь уникальные id, в противном случае не гарантируется стабильная работа sphinx.
После этого подвергается сомнению корректность использования view. Что не есть правильно так как в примере использование view не приведет к потере уникальности id.
И уж тем более не приведет к потере производительности.
Перечитал еще раз комент, похоже, вы правы: в коменте речь идет об уникальности айди. К сожалению, исторически так сложилось, что представлениями не пользуемся и сказать, как себя они ведут в проекте с 20-100 млн позиций в таблицах я с уверенностью не могу.

Возможно еще сказалось то, что в соседнем проекте полностью на вьюхах даже с минимальным количеством позиций ребята не справились и в слов лог постоянно сыпались запросы от этих вьюх. Возможно просто совпадение и вы умеете правильно готовить майскл вьюхи в высоконагрженных проектах.
Тогда я вам рекомендую почитать доккументацию по mysql и больше не боятся: dev.mysql.com/doc/refman/5.5/en/view-algorithms.html

Скорее всего у ребят были view в которых использовалось нечто из этого:

Aggregate functions (SUM(), MIN(), MAX(), COUNT(), and so forth)
DISTINCT
GROUP BY
HAVING
LIMIT
UNION or UNION ALL
Subquery in the select list
Refers only to literal values (in this case, there is no underlying table)


Что заставляло mysql использовать временные таблицы и естественно работало не сильно быстро.

Это поидее не смертельно и лечится если потюнить размер кеша для временных таблиц и ограничения на размеры самих временных таблиц.
В любом случае чинить надо конкретный случай.
Спасибо за ссылку, почитаю по свободе. Согласен, личить нужно конкретный случай и возможно тупик и плавная смерть старого проекта не в представлениях, а в комплексе факторов.

К сожалению, не все лечится тюнингом конфига майскл. В нашем частном случае все индексы базы данных просто физически не могу влезть в 4Г памяти сервера. И да: в более общем случае эти 4Г нашего типового сервера делятся 5-10 однотипными проектами, то есть фактически на проект 400Мб максимум — и работает. А раз работает — не трогаем и дальше юзаем прямые запросы, а не представления.
С view в mysql я имел забавный прикол.

Оптимизатор упорно выбирал не тот индекс, какой надо — ну ок, FORCE INDEX, все отлично.

После чего вынес это дело во вьюху. Все скушало, force index в show create view видно. Но фактически force index напрочь игнорировался.

Было это давно, не исключаю, что поправили, — но осадочек остался.
У меня почему-то аналогичный опыт отложился в памяти, что FORCE INDEX корректно работает для прямых запросов, а для представлений глючит.

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

Может кто-то имеет более точную информацию по текущему положению дел?
С месяц назад поставил себе поиск на сайт объявлений очень доволен результатом.
Сортировать ещё можно по собственным параметрам, но не без релевантности (например когда одна марка товаров более приоритетная):

$sphinx->select('@weight*priority as relevance')
->matchingMode(sphinx::matchExtended2)
->sortingMode(sphinx::sortExtended, 'relevance DESC');


Где priority считается заранее.
А можно сделать двойную сортировку: релевантность, цена к примеру? То есть order by relevance desc, price asc…
Да можно, но, в основном, разбег параметров даст просто микс результатов.
В доке написано, в случае переполнения 32битного числа будут коллизии, так что я бы осторожно умножение использовал.
А зачем умножать?) Можно же делить! ) У меня priority варьировалось от 0.1 до 5, шанс получить переполнение почти нулевой (weight не будет таким большим).
Похоже, но не тестировал: сразу решил, что буду пользоваться расширенным синтаксисом запросов, так как позволяет гибко настраивать нужный набор возвращаемых результатов. А вариант «из каробки» возможно работает также, не проверял.
Так одно с другим никак не связано. Запрос (масло castrol 5w40) в режиме с синтаксисом никак не отменяет работу expand_keywords. Просто она будет сделана автоматом и на стороне Сфинкса. Меньше кода в приложении
Вся проблема в том, что куча скрытых не очевидных вещей — и чтобы о них узнать, нужно разобраться, проверить, погонять на тестах с живыми данными, а не семпловыми 2-3мя табличками с 3-5ю записями для сфинкса. Не уверен, но такие вещи называют «Best practices». То есть список типовых проблем — и такой же список наиболее коротких и правильных путей решения, чтобы каждый не ломал голову над своим велосипедом.

Я столкнулся с тем, что в сфинксе очень много функционала: он работает, бесспорно. Но не все так очевидно и явно, как хотелось бы. А почитать на русском толком кроме 2-3х статей для новичков -нечего. Основная масса информации черпалась по сути из комментариев к статьям на хабре, форуме на сайте и других сайтах таких же девелоперов. Я читаю и пишу на английском, но когда речь заходит о вещах которые не очевидны и не понятны — тяжело вникнуть в суть, даже если 80-90% слов в тексте понятны.

Для меня ступором было, что нету «service sphinx reload», «include *.conf», что ошибки при запуске не пишутся в текстовый лог — чтобы это понять я потратил кучу времени до того, как понял что именно нужно решать и как.

Я специально в статье перечислил все подобные свои грабли, как я их попытался решить, что у меня получилось и к каким выводам пришел. Надеюсь, следующему разработчику уже не нужно будет решать хотя бы эти грабли.
Про типовые проблемы мы ж ведь можем узнать только одним способом — когда люди много, но громко про них нам рассказывают.

Иначе мушка сбита!!!
Кстати, по поводу «нету«include *.conf»» помнится давно еще вопрос поднимался. И вроде даже обещали сделать так, что конфиг можно генерировать программно при запуске. Или я что-то путаю?
так уже можно и давно, #!/usr/bin/WHATEVER в начале файла и привет
опачки, вот так сюрприз!
Буду знать теперь…
А как вообще сам поиск и изложенный материал: не сильно рябит откровенными ляпами, характерными для новичка в поиске?
Мне немного не понятен п.4

Вы привели в пример метод задания весов для ручного управления релевантностью.
Правильно ли я тогда понимаю, что этот метод изменяет релевантность только в полученных результатах поиска, а не в самой базе Sphinx?
Да, веса настраиваются для запросов, а не зашиты статично в настройках сфинкса. То есть можно к одному индексу документов делать несколько разных запросов, в зависимости к примеру от настроек в профайле каждого пользователя или от пришедших гет параметров формы.

К примеру, кто то хочет по коду важному искать, а кому-то нужно именно описание запчасти.
«Релевантность» вообще в «базе» (индексе) никогда никем не хранится. Это функция от пары (документ, запрос), которая всегда и всюда считается на лету.
Не знаю, еще не читал.
Я так понял — каждый ваш запрос использует звездочки "*", т.е. поиск любого вхождения, но для этого не нужны звездочки, min_infix_len = 3 без enable_star = 1, так работает по дефолту. Попробуйте отключить enable_star = 1 и передавать запрос без преобразований «масла Castrol 5W40».
Ругается сфинкс indexer:
ERROR: index 'price_group': infixes and morphology are enabled, enable_star=0

не дает создать такой индекс
Важный момент для windows пользователей: при отказе запуска службы — ошибку нужно искать не в логах сфинкса, а в Панель управления — Администрирование — Просмотр событий.
Очень вовремя конечно задаю вопрос :) Но всё же, в версии 2.0.3 под win32 всё та же проблема. Система Windows 7 (без SP). При запуске сервиса вылежает ошибка, что файл не найден. До логов додумался сам, но там, собственно, ничего внятного не написано. Куда копать?

У знакомого сфинкс службой поднялся, но у него, емнип, SP 1.
>Так что в комментариях приветствуется более корректный пример запроса для получения того же, но средствами сфинкс группировки.

Группированный велосипед через мультизапросы исправил более корректной логикой сфинкса, пример использования ниже. Фактически используется функция из апи:
$oSphinxClient->SetGroupBy("id_price_group", SPH_GROUPBY_ATTR, "@count desc" );

и нужный результат берется из $aResult[matches][attrs][@count]

Исходник на сервере обновил, вывод результата на первом шаге также добавил

Добавлен новый метод, который использует тот же шаблон, но вместо 100 запросов (кол-во груп запчастей) шлет сфинксу один.
public function AllPriceGrouped()
{
	if ($this->sQuery) {

		require(SERVER_PATH.'/lib/sphinx/sphinxapi.php');
		$sSphinxKeyword=$this->GetSphinxKeyword($this->sQuery);

		$oSphinxClient = new SphinxClient();
		$this->SetDefaultSetting($oSphinxClient);

		$oSphinxClient->SetGroupBy("id_price_group", SPH_GROUPBY_ATTR, "@count desc" );
		$aResult = $oSphinxClient->Query($sSphinxKeyword, 'price_group');
		Base::$sText.=$sSphinxKeyword."
";

		if ( $aResult === false ) {
			Base::$sText.="Query failed: ".$oSphinxClient->GetLastError()."
";
		}
		else {
			if ($oSphinxClient->GetLastWarning() ) {
				Base::$sText.="WARNING: ".$oSphinxClient->GetLastWarning()."
";
			}
			if ($aResult['matches']) {
				$sPriceGroupAssoc=Db::GetAssoc('Assoc/PriceGroup',array(
				'visible'=>1,
				'multiple'=>1,
				));
				foreach ($aResult['matches'] as $aValue){
					$PriceGroupRow=$sPriceGroupAssoc[$aValue['attrs']['id_price_group']];

					if ($PriceGroupRow) {
						$PriceGroupRow['total_found']=$aValue['attrs']['@count'];
						$PriceGroupRow['price_group']=$PriceGroupRow;
						$PriceGroupRow['price_group']['id']=$aValue['attrs']['id_price_group'];
						$aResultPriceGroup[]=$PriceGroupRow;
					}
				}

				Base::$tpl->assign('aResultPriceGroup',$aResultPriceGroup);
				Base::$sText.=Base::$tpl->fetch($this->sPrefix.'/all_price_group.tpl');
			}
			Base::$sText.=Debug::PrintPre($aResult,false,true);
		}
	}
}
Вы ничего не сломали, просто создали такой запрос, по которому ничего не найдено.

Аналогично по более простому «корола» autoklad.biz/?search%5Bquery%5D=%D0%BA%D0%BE%D1%80%D0%BE%D0%BB%D0%B0&action=search
ничего не найдено.

Я в статье писал, что не работают еще синонимы и запчасти привязаны только те, которые имеет смысл искать через поиск. Бампера к таким запчастям не очень подходят — для бамперов лучше через вин искать.
Вы, конечно, правы, я поторопился с выводами, простите меня.
Замечательная статья, большое вам спасибо.
а я вот такой поиск делал на сфинксе www.1aauto.com/search?words=toyota+headlights www.am-autoparts.com/search.html?words=toyota+headlights (обратите внимание на колонку с фильтрами). Тоже, кстати, автозапчасти.

Ооочень долго мучился с «AddQuery, RunQueries» из за следующих факторов:

1) Подзапросы к сфинксу для построения фасеточных фильтров нужно делать используя мультизапрос, т.е. AddQuery из соображений производительности.
2) Сфинксовское PHP апи представляет из себя один единственный класс… Принцип работы API следующий:
....1. создаем объект сфинксовского API класса и подключаемся к серверу
....2. вызываем методы типа setFilter() setGroupBy() setMatchMode() для задания критериев сортировки и фильтрации
....3. вызываем метод addQuery() для добавления собранного запроса в очередь мультизапроса
....4. Начинаем формировать следующий запрос для следующего addQuery() начиная с п. 2. нужное кол-во раз.
....5. Вызываем runQueries() и получаем результаты по всем запросам.

Проблема в том, что в п. 4 нам нужно ОБНУЛИТЬ установленные для предыдущего запроса setFilter() setGroupBy() и т.п., потому что они при addQuery() не обнуляются. Пришлось придумывать какие-то обертки для этого, в общем времени потратил прилично.
bq. Проблема в том, что в п. 4 нам нужно ОБНУЛИТЬ установленные для предыдущего запроса setFilter() setGroupBy() и т.п., потому что они при addQuery() не обнуляются.

Есть же ResetFilters() и ResetGroupBy() для обнуления, а по-умолчанию они и не должны обнуляться — я написал в статье о них. Или может я что-то пропустил и все такие есть какие грабли с мультизапросами?

По вашему примеру сайта: найдено 767 results — честно говоря, я когда себе фару буду искать, врятли меня устроит такой широкий выбор :) Для фар, бамперов, и других 90% запчастей машины поиск сфинкс не подходит — и об этом тоже написано в статье. Или вы считаете что ваш поиск по фарам востребован пользователями?
Есть же ResetFilters() и ResetGroupBy() для обнуления, а по-умолчанию они и не должны обнуляться — я написал в статье о них. Или может я что-то пропустил и все такие есть какие грабли с мультизапросами?
Я, честно говоря, не очень хорошо помню деталей проблемы, но точно помню что они были и были по этой причине.

По вашему примеру сайта: найдено 767 results — честно говоря, я когда себе фару буду искать, врятли меня устроит такой широкий выбор :) Для фар, бамперов, и других 90% запчастей машины поиск сфинкс не подходит — и об этом тоже написано в статье. Или вы считаете что ваш поиск по фарам востребован пользователями?
Во первых, я и запрос то привел чересчур общий. Вряд ли юзер прям будет искать любую фару для любой тойоты. Наверняка наберет типа «правая фара для тойота камри». Во вторых, фасеточные фильтры как раз служат для решения этой проблемы.
да, я уточнил свой вопрос чуть ниже.
ResetFilters, ResetGroupBy же есть
>обратите внимание на колонку с фильтрами
Упустил я эту колонку из виду. Скажите, а как решаете проблему с возвратами по неправильному подбору. То есть если все параметры заданы правильно, а к примеру для запчасти не учтен к примеру параметр «Страна назначения авто» или «Праворукость» — как-то контролируется подбор и проверка запчасти по вин или все заказы идут как на мобильники?
Тут не смогу помочь, магазин не мой, я занимался только разработкой на аутсорсе. Сама контора находится в США. Но, насколько я знаю, 1aauto.com это типа «премиум-сервис» с завышенными ценами и запчасти там принимают назад без объяснения причин. Через am-autoparts.com продают те же запчасти, но дешевле и без возвратов, без колл-центра и поддержки. Как то так.
UFO landed and left these words here
да, причем «масла Castrol 5W40» находит * Масла моторные (6)
autoklad.biz/?action=search&search[query]=%D0%BC%D0%B0%D1%81%D0%BB%D0%B0%20Castrol%205W40&search[id_price_group]=35

эта часть не должна была изменится. Можете привести урл или скриншот, где ничего не нашлось по этому азпросу?
> Также очень частым на форуме и сайте разработчиков вопросом является «Как поднять повыше точное вхождение фразы?», то есть чтобы вес документа «Искомое_слово» был выше «Искомое_слово а также еще кучу текста». Ответ — нужно использовать SPH_RANK_SPH04, специально созданный под эту типовую задачу, как я понял.

Есть еще другой метод. В запросе индекса вставляется что-то типа «SELECT crc32(name) as name_hash»

Потом при выборке делается SetSelect('name_hash='.crc32($name).' as name_equal') ну и SetSortMode идет сортировка по этому полю.

К сожалению с postgresql там не совсем получается подружить такой метод, в постгресе нету функции crc32, но есть функция hashtext, и чтобы ее использовать, пришлось делать предварительно запрос в базу, для выяснения этого hashtext.

Также на sphinx можно организовать замену Mysql фичи MATCH()… AGAINST(), которой нету в постгресе, то есть ранжирование записей между собой для поиска «Похожие товары». Результаты tsearch2 модуля (rank_cd функция и т.п.) оказались не совсем хорошими. Единственная там проблема оказалась которую не особо получилось решить, это если у товаров есть категории и желательно чтобы совпадение категорий тоже учитывалось при ранжировании, то приходится в индексном запросе id категорий преобразовывать в текстовое поле через запятую, а затем уже в запрос вставлять " @category 897 | @category 34" и т.п. Вот на это поле @category почему-то SetWeight особо как-то не влияет.
В общем добиться идеального ранжирования не получилось, но приемлемого результата добиться можно.

Если у кого есть опыт организации фичи «Похожие товары» через sphinx+postgresql или напрямую через postgresql — поделитесь.

Выше речь шла о версии 0.9.9-release, может есть у кого опыт 2.0.1? И как вообще 2.01 в продакшн кто-то использует? Стабильная?
> может есть у кого опыт 2.0.1?

децл

> И как вообще 2.01 в продакшн кто-то использует? Стабильная?

да
Используем на продакшене 2.0.1, проблем не возникает.
Only those users with full accounts are able to leave comments. Log in, please.