Оптимизация индексирования Shpinx для LiveStreet, исправляем древнюю проблему с закрытыми блогами

Суть проблемы — LS ищет и находит сфинксом заметки и комментарии из всех блогов, независимо от того имеете вы к ним доступ или нет.

Чинится это просто:
  1. вносим в поисковый индекс id закрытого блога к которому относится топик или комментарий, либо 0 если этот блог не закрытый.
  2. передаём в поиск id текущего пользователя
  3. находим список закрытых блогов в которые имеет доступ текущий пользователь.
  4. используем этот список расширенный нулём (что добавит все не закрытые блоги в результаты поиска для любого пользователя) со стандартной возможностью фильтрации Сфинкса


Расписываю по шагам (LiveStreet 1.0.2, Sphinx 2.0.6 ).

I. Вносим некоторые изменения в конфигурацию сфинкса. Берём конфигурацию от сюда livestreet.ru/blog/dev_documentation/13482.html (к слову, конфигурация там существенно лучше стандартной, настоятельно рекомендую) и правим в ней entityprefixTopicsSource и entityprefixCommentsSource. Они должны стать такими:
source entityprefixTopicsSource : entityprefixSource
{                                                               
sql_query = \
SELECT t.topic_id, t.topic_title, UNIX_TIMESTAMP(t.topic_date_add) as topic_date_add, \
tc.topic_text, t.topic_publish, b.blog_title, u.user_login, \
IF( b.blog_type = 'close', b.blog_id,0 ) as blog_id \
FROM x50_topic t, x50_topic_content tc, x50_blog b, x50_user u \
WHERE t.topic_id=tc.topic_id AND t.topic_publish=1 \
AND b.blog_id=t.blog_id AND t.user_id=u.user_id \
AND t.topic_id>=$start AND t.topic_id<=$end
 
sql_joined_field = tags from query; select topic_id, topic_tag_text \
from x50_topic_tag order by topic_id ASC

sql_query_range = SELECT MIN(topic_id),MAX(topic_id) FROM x50_topic
        
sql_range_step = 1000

sql_attr_uint = blog_id

sql_attr_bool = topic_publish

sql_attr_timestamp = topic_date_add

sql_attr_multi = uint tag from query; SELECT topic_id, topic_tag_id FROM x50_topic_tag
}

source entityprefixCommentsSource : entityprefixSource
{
sql_query = \
SELECT m.comment_id, m.comment_text, UNIX_TIMESTAMP(m.comment_date) as comment_date, \
m.comment_delete, u.user_login, \
IF( b.blog_type = 'close', b.blog_id, 0 ) as blog_id \
FROM x50_comment m, x50_user u, x50_topic t, x50_blog b \
WHERE m.target_type='topic' AND m.comment_delete=0 AND m.comment_publish=1 AND m.user_id=u.user_id \
AND m.target_id = t.topic_id AND b.blog_id = t.blog_id \
AND m.comment_id>=$start AND m.comment_id<=$end

sql_range_step = 5000

sql_attr_uint = blog_id

sql_query_range = SELECT MIN(comment_id),MAX(comment_id) FROM x50_comment

sql_attr_bool = comment_delete

sql_attr_timestamp = comment_date
}


II. Создаем Mapper для модуля Sphinx /classes/modules/sphinx/mapper/Sphinx.mapper.class.php следующего содержания:
<?php
class ModuleSphinx_MapperSphinx extends Mapper {
	
	public function GetUsersCloseBlogs($sUserId) {
		$sql = "SELECT b.blog_id 
			FROM ".Config::Get('db.table.blog')." as b, ".Config::Get('db.table.blog_user')." as bu 
			WHERE bu.user_id = ?d AND bu.blog_id = b.blog_id AND b.blog_type='close' AND bu.user_role = 1
			UNION DISTINCT
			SELECT blog_id
			FROM ".Config::Get('db.table.blog')."
			WHERE blog_type='close' AND user_owner_id = ?d";
		
		$aBlogIDs=array();
		if ($aRows=$this->oDb->select($sql,$sUserId,$sUserId)) {
			foreach ($aRows as $aBlogID) {
				$aBlogIDs[]=$aBlogID['blog_id'];
			}
		}
		return $aBlogIDs;
	}
}
?>


Замечание (26.09.2013): В mapper добавлена поддержка случая, когда пользователь является создателем, но не подписчиком закрытого блога.

III. Мелкие изменения функции PrepareResults в /classes/actions/ActionSearch.class.php

204 строка была такой:
$aRes['aCounts'][$sType] = intval($this->Sphinx_GetNumResultsByType($aReq['q'], $sType, $aExtra));
Стала такой:
$aRes['aCounts'][$sType] = intval($this->Sphinx_GetNumResultsByType($this->User_GetUserCurrent()?$this->User_GetUserCurrent()->getId():null,$aReq['q'], $sType, $aExtra));


Добавялем новую строку, сразу после строки 222 — первый параметр в вызове функции Sphinx_FindContent:
$this->User_GetUserCurrent()?$this->User_GetUserCurrent()->getId():null,


IV. Изменения класса /classes/modules/sphinx/Sphinx.class.php

Самое начало класса должно стать таким (декларация и инициализация мэппера):
class ModuleSphinx extends Module {
	/**
	 * Объект сфинкса
	 *
	 * @var SphinxClient|null
	 */
	protected $oSphinx = null;

	protected $oMapper;
	/**
	 * Инициализация
	 *
	 */
	public function Init() {
		$this->oMapper=Engine::GetMapper(__CLASS__);
		$this->InitSphinx();
	}
Функция GetNumResultsByType меняется так(новый, первый параметр):
public function GetNumResultsByType($iUserId,$sTerms, $sObjType = 'topics', $aExtraFilters){
		$aResults = $this->FindContent($iUserId,$sTerms, $sObjType, 1, 1, $aExtraFilters);
		return $aResults['total_found'];
	}
Самое начало функции FindContent, всё, до первого if меняем на вот это (новый, первый параметр; получение списка нужных блогов; расширение ключа кеша):
public function FindContent($iUserId, $sTerms, $sObjType, $iOffset, $iLimit, $aExtraFilters){

		/**
		* Получаем id закрытых блогов пользователя
		*/
		$aCloseBlogs=array(0);
		if (isset($iUserId ) ) {
			$blogsCacheKey = Config::Get('module.search.entity_prefix')."userCloseBlogs_{$iUserId}";
			if (false === ($blogsData = $this->Cache_Get($blogsCacheKey ) ) ) {
				$blogsData = $this->oMapper->GetUsersCloseBlogs($iUserId);
				$this->Cache_Set($blogsData, $blogsCacheKey, array(), 60*15);
			}
			$aCloseBlogs = array_merge($aCloseBlogs,$blogsData);
		}
		$sCloseBlogs = serialize($aCloseBlogs);
		/**
		 * используем кеширование при поиске
		 */
		$sExtraFilters = serialize($aExtraFilters);
		$cacheKey = Config::Get('module.search.entity_prefix')."searchResult_{$sObjType}_{$sTerms}_{$iOffset}_{$iLimit}_{$sExtraFilters}_{$sCloseBlogs}";
И внутри этого if, сразу перед комментарием «Ищем» добавляем:
/**
	* Фильтруем закрытые блоги
	*/
	$this->oSphinx->SetFilter('blog_id', $aCloseBlogs);


V. Итого:
  • Два незначительно изменённых SQL запроса, и две новых декларации в конфигурации Sphinx
  • Новый, примитивный Mapper менее, чем в 20 строк
  • Два мелких изменения в экшене
  • 15 добавленных, или слегка модифицированных строк в самом поиске, и даже с поддержкой кэша.


Господин ort , может воткнёте это на GitHub? Вместе с улучшенной конфигурацией Sphinx? А то нытьё про плохой стандартный поиск слегка достало.

17 комментариев

avatar
Как касается последний тег темы топика? Зачем эта неприкрытая реклама? Вы уже 6 топиков написали с этим тегом как и ваши друзья.
avatar
Я во все свои заметки добавляю этот тег и буду добавлять. Тщеславие.
avatar
Я не вижу в этом проблемы. Вообще то ребята для вас опубликовали полезное решение и имеют полное право упомянуть кто они такие.

Главное что вам не тычут в морду своей плашкой или лучше б тыкали?
avatar
Топик шел хорошо, но в конце тон неприятный.
avatar
у меня вот вопрос, а вот эти модификации сфинска, была еще одна с улучшенным поиском. Они будут работать на ls051, тут же не как не связано с самим движком или связано структурой БД
avatar
Насколько я знаю, интеграция Sphinx в LS не менялась с незапамятных времён. Должно работать. Тут скорее нужно позаботится о том, что сам Sphinx был актуальной версии. В старом топе я столкнулся с тем, что у некоторых людей работали какие-то допотопные версии Sphinx типа 0.9. А эта конфигурации использует директивы которые появились только в 1.10+
avatar
спасибо за ответ!
avatar
Господин ort сообщения о багах предпочитает игнорировать. Такое впечатление создалось, когда я писал о другой старинной проблеме, связанной с удалением постов, имеющих комментарии в прямом эфире, когда айди комментария к несущеcтвующему топику почему-то остаётся в соответствующей таблице, в результате чего в блоке прямого эфира на месте, где должен быть этот комментарий красуется ошибка и на этом месте вывод html обрывается со всеми вытекающими. После сообщения о проблеме прошло много времени, а отец проекта до сих пор не нашёл возможности не только ответить, но и исправить эту досадное недоразумение.
avatar
Может самим фиксы вносить? Публичный же репозиторий, кажется. А улучшение будущий версий — наш прямой интерес.
avatar
фигушки вы в официальную версию что внесете, если только через пулл реквест, который могут проигнорировать просто :) хотя откуда нам знать
avatar
Ну я просто никогда не пробовал и вяло интересуюсь самой принципиальной возможностью :)
avatar
Репозиторий не публичный, коммитить в него нельзя, только через пулл-реквест мейнтейнеру. Единственный вариант — сделать общий публичный форк для этих целей, широко освещать изменения в нём и предлагать уже из него Максу пулл-ревесты.
avatar
ага давайте сделаем LS-community version =) вот максу потеха
avatar
Так то вообще интересная затея :))
avatar
ну с меня улучшенный шаблон :) что дальше?
avatar
Cделать-то не сложно. Кто это поддерживать потом будет, вот в чём вопрос.
avatar
ответ кроется в название =) люди кто же еще, будет выбран 3-4 разработчика которые решают включать или не включать те или иные идеи :) так сказать голосованием
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.