И это снова о поиске в избранное  новое    подписка   модер. 
От: akasoft 
Дата: 18.06.06 16:49
Оценка:6 (2)
Поиск информации в ЛБД Януса занимает дольше времени, чем поиск на сайте и переход к найденным сообщениям в Янусе. Это есть ненормально. И с этим надо что-то делать. Дальше рассмотрено одно из возможных решений.

Постановка проблемы


Поиск в Янусе реализован с помощью LIKE-запросов ко всей ЛБД целиком, что при большом количестве сообщений (уже при >300) выливается в длительные ожидания. В то же время, LIKE-поиск довольно "прост" в составлении поискового запроса.

Что хотелось бы от поиска?

  1. Существенного ускорения оного.
  2. Возможность прерывания хода поиска с выдачей промежуточных результатов.
  3. Подсветки искомых слов в теле сообщения, по примеру MS Document Explorer или HTML Help.
  4. Регистронезависимый поиск.
  5. Хотелось бы исключать из поиска символы разметки форматтера.
  6. Хотелось бы искать фрагменты со специальными символосочетаниями программистов — "=", "==", ";" и пр. операторами.

Всё это вполне возможно.

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

Поисковый лисапет движка


Как обычно, я начну втирать про построение собственного простейшего велосипеда поиска, и буду обещать, что он будет работать быстрее, нежели существующий.

Сначала про идею. Идея заключается в построении 2-х (двух) таблиц: словаря и списка вхождения слов.

dictionary {
int id, // первичный ключ
string word // одно слово, число либо оператор - всё то, что позволяется искать
}

wordlist {
int id, // первичный ключ
int wid, // ссылка на на слово (id из dictionary)
int mid, // ссылка на на сообщение (id из messages)
int count // количество повторений слова в сообщении, для статистики
}


Чтобы воплотить идею, нужно определиться с тем, что мы будем считать словом, научиться разбивать сообщения на слова и подключать их к поисковому словарю.

Затем мы сможем воспользоваться обычным LIKE-поиском, только искать мы будем по гораздо компактной табллице словаря, а результат можем отбирать и показывать порциями. Это в совокупности позволит нам существенно ускорить поиск и получить возможность прервать его или посмотреть часть результатов.

Для того, чтобы иметь возможность искать по сообщению, его нужно подключить к словарю. Вначале в сообщении выделяем поисковые слова, затем для каждого слова (включая повторы) проверяем наличие его в словаре, и в случае отсутствия добавляем новое слово в словарь. Затем по имеющемуся id слова и mid сообщения проверяем наличие записи в списке вхождения слов, и опять же в случае наличия увеличиваем поле count на единичку, иначе добавляем новую запись с count == 1.

Можно оптимизировать эту процедуру, изначально объединив повторяющиеся слова в сообщении в один элемент списка — пару {слово, количество}, что позволит минимизировать количество запросов на обработку одного слова.

Так же полагаю, что будет полезно совместить поиск по телу сообщения с поиском по теме сообщения. Хотя, можно сделать отдельный словарь или флажок, но зачем?

Отделяем зёрна от плевел


Итак, у нас есть одно сообщение. Необходимо получить его набор поисковых слов. Пробельными принято считать собственно пробел, табуляцию, символы пунктуации и начало и конец сообщения. Кроме того, в сообщениях используется разметка в "[тег]" и "[/тег]", которую необходимо исключить из поиска. Но хотелось бы искать сообщения, где есть цитаты, замечания модераторов, URI и картинки (в том числе по самому URI для ссылок и картинок), фрагметны кода на выбранных языках и т.п.

Т.е. понятно, что появляются предопределённые псевдопоисковые слова-флажки, которые заранее определены в программе, заранее добавлены в словарь и при подключении сообщения к словарю мы смотрим наличие тегов оформления и заносим информацию в wordlist с учётом количества цитат, фрагментов кода и пр. Сюда же можно отнести и операторы во фрагментах кода, и иные метаслова, которые мы хотим позволить искать. Важно понимать, что, например, задав к поиску открывающию и закрывающую квадратные скобки, набрав "[ ]", мы не найдём сообщения с разметкой, а только сообщения, где эти символы встречаются не в контексте разметки.

Таким образом, наклёвывается порядок обработки сообщения. Инициализируем пустой список слов для сообщения. Анализируем тело сообщения на предмет наличия разметки, и добавляем в список слов сообщения найдённые виды разметки. Затем разметку исключаем, забивая её пробелами, за исключением "хитрого" тега url, где сохраняем всё то, что идёт после "[[url=" и до "]". Помним и о том, что двойная открывающая скобка "[[" отменяет действие тега. Все теги разметки идут как флажки в расширенных настройках поиска.

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

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

Теперь мы готовы к подключению сообщения к словарю.

Подключаем словарь и ... отлучаем от него!


Для предопределённых слов разметки и пунктуации возможна оптимизация, т.к. искать такое слово в словаре не нужно, ибо оно там есть всегда. Получаем их id и подключаем. Обычные слова искать нужно. Да, слова все юникодные. Для каждого слова получаем его id, и добавляем в wordlist запись о нём.

Зачем подключается словарь? Тут просто всё, чтобы иметь возможность поиска.

Когда подключается словарь? Тут есть несколько подходов.

Во-первых, подключать можно сразу по окончании синхронизации. Это может быть как накладно, так и не очень. В сутки приходит 2-3 тысячи сообщений при полной подписке. Во-вторых, можно завести список неподключенных сообщений, и активировать подключение по команде пользователя или по расписанию. Само собой, по неподключенным сообщениям искать нельзя. В третьих, изначально у пользователя есть большущая ЛБД с кучей неподключенных сообщений. Это поле для работы, которую нужно тоже учесть наряду с новыми сообщениями. Ну и конечно, очень хочется, чтобы механизмус подключал сообщения в фоновом режиме во время простоя при просмотре-чтении пользователем других сообщений. В стиле "а Васька слушает, да ест".

Поэтому плавно вытекает добавление в messages поля indict

messages {
...
bool indict // true - подключено, false - нет
}


При получении нового сообщения в поле просто пишется false, и поисковый движок опознает и подключит такое сообщение. Просто? Да!

Но что делать, когда получено обновлённое сообщение? А если сообщение, не дай Б-г, подлежит удалению?

Нужен механизм отключения сообщений от словаря. Он весьма прост — достаточно удалить из списка записи с нужным mid:

DELETE FROM wordlist WHERE mid = @mid;


Таким образом, при удалении сообщения мы выполняем этот запрос и удаляем собственно сообщение, а при обновлении сообщения (модерирование) мы выполняем этот запрос и сбрасываем флаг messages.indict в false.

Go-o-o-o-ogle Ja-a-a-anus! Гавкус-гавкус!!!


Собственно, у нас есть словарь, механизм выделения слов и подключения сообщений к словарю, всё это работает параллельно и асинхронно, никому не мешает, а при необходимости пользователь может отключить поиск и съэкономить место на диске, или перестроить словарь заново.

"Но Холмс, как мы будем пользоваться этим богатством?"

А пользоваться мы будем по старому! С применением LIKE! В этом финт ушами.

Человек вводит поисковую строку "большая пьян харьков c#" и ставит галочку искать сообщения с картинками (тег img).

Движок обнаруживает 4 обычных слова ("большая", "пьян", "харьков", "c"), один диезик "#" и одно псевдослово. Рисуем запросину:

SELECT *
FROM dictionary
WHERE word LIKE "%большая%" OR word LIKE "%пьян%" OR word LIKE "%харьков%"
    OR word LIKE "%c%" OR word LIKE "#" OR word LIKE "[img]"


Получаем id слов. Здесь можно позволить пользователю нажать кнопочку Cancel.

Если ничего не найдено — радуем пользователя соответствующим сообщением.

Определяем количество сообщений, подпадающих под этот набор слов

SELECT Count(*) FROM wordlist WHERE wid IN (@wid) GROUP BY mid;


Опять Cancel, если что.

Делаем выборку последних по дате (свежих) 10 (десяти) сообщений (с учётом настройки количества для поиска) или всех, показываем, позволяем прервать процесс и сразу или в фоне догружаем остаток.

Смотрим


Конечно, хотелось бы встроить механизм выделения искомых слов в форматтер, но это можно сделать и не внедряясь в оный. В Янусе уже давно используется своя надстройка над форматтером. Поэтом добавляется ещё один span class в janus.css, где проставляется цвет символов и фон нашего выделения, ну и любые иные атрибуты на вкус. И затем поиском заменяются вхождения слов на <span class="hot">слово</span>.

Отдельно стоит отметить подсветку ссылок, если в URI найдено искомое, и аналогично картинок при тех же условиях. Тут надо сочинить по отдельному классу.


Кстати! Неплохо бы вообще оттенять ссылки цветом фона, а не только префиксом иконки. По моему, лучше смотрится.

Заключение


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

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

Ну и конечно нужно отметить, что АВК уже приводил ключевое слово "Lucene" из Апача, и даже уверял
Автор: AndrewVK
Дата: 13.06.06
, что есть работающий порт на .net.
... << RSDN@Home 1.2.0 alpha rev. 652>> SQL Express 2005
Re: И это снова о поиске в избранное  новое    модер. 
От: Lloyd 
Дата: 18.06.06 17:05
Здравствуйте, akasoft, Вы писали:

A>Поиск информации в ЛБД Януса занимает дольше времени, чем поиск на сайте и переход к найденным сообщениям в Янусе. Это есть ненормально. И с этим надо что-то делать. Дальше рассмотрено одно из возможных решений.


А вы вот это смотрели: Lucene.Net?
Re: И это снова о поиске в избранное  новое    модер. 
От: Petrovich_Alex 
Дата: 18.06.06 17:54
Здравствуйте, akasoft, Вы писали:

A>Поиск информации в ЛБД Януса занимает дольше времени, чем поиск на сайте и переход к найденным сообщениям в Янусе. Это есть ненормально. И с этим надо что-то делать. Дальше рассмотрено одно из возможных решений.



Cделай локальный http сервер для rsdn@home. (нужен http интерфейс для rsdn@home..)

к примеру:

http://locahost/rsdn/all/
http://locahost/rsdn/java/
http://locahost/rsdn/janus/

И всё... и весь поиск готов.

И каждый потом сможет чем угодно его индексировать.
Я вот могу гугло-декстопом...

А если дашь возможность еще и html интерфейс менять, или расширять сервер скриптами(питон,js,php и т.д.) то всё, ты бог...
а если нет, то и на этом будут "большое спасибо". (а интервейс и Greasemonkey можно подправить..)
... << RSDN@Home 1.1.4 stable rev. 510>>
Re: И это снова о поиске в избранное  новое    модер. 
От: AndrewVK модератор 
Дата: 19.06.06 09:56
Здравствуйте, akasoft, Вы писали:

A>Ну и конечно нужно отметить, что АВК уже приводил ключевое слово "Lucene" из Апача, и даже уверял
Автор: AndrewVK
Дата: 13.06.06
, что есть работающий порт на .net.


Так может для начала нужно было посмотреть, прежде чем выдумывать то же самое? Или у Lucene есть фатальный недостаток?
... << RSDN@Home 1.2.0 alpha rev. 642>>
Re[2]: И это снова о поиске в избранное  новое    модер. 
От: akasoft 
Дата: 19.06.06 14:01
Здравствуйте, AndrewVK, Вы писали:

AVK>Так может для начала нужно было посмотреть, прежде чем выдумывать то же самое?


А показать? Сслыку бы дал на то, что сам видел где.

То, что Ллойд выше написал — посмотрел, ему ответил.

Пока что я просто описал то, что мог бы реализовать без дополнительных танцев и исследований.

AVK>Или у Lucene есть фатальный недостаток?


Ты же знаешь — такой недостаток есть у каждого ПО.
... << RSDN@Home 1.2.0 alpha rev. 652>> SQL Express 2005
Re[2]: И это снова о поиске в избранное  новое    модер. 
От: akasoft 
Дата: 19.06.06 14:01
Здравствуйте, Lloyd, Вы писали:

L>А вы вот это смотрели: Lucene.Net?


Посмотрел. Скачал 700 кил, перевёл автоматом в 2005 Студию, без ошибок. Скомпилировал и запустил примеры. Англоязычный текст ищет. При вводе русского слова (кирилицей на запрос Query: ) вылетает и ничего не ищет. А ещё юникод, блин.

Индекс по файлам строит быстро, и даже запоминает пути. Возможно, что есть возможность связать тексты и mid сообщений и скормить их индексатору.

Ты сам с этой штукой работал? Каковы впечатления? Поддержка кирилицы/юникода в данном конкретном порте?

Есть ли ещё где-нибудь описание принципов работы, кроме как на официальном сайте Lucene под Яву? Примеры там какие. Конечно, предпочтительны языки Шарп и русский.
... << RSDN@Home 1.2.0 alpha rev. 652>> SQL Express 2005
Re: И это снова о поиске в избранное  новое    модер. 
От: orange_ 
Дата: 13.08.06 19:06
Здравствуйте, akasoft, Вы писали:

<skip>

Очень даже интересно. Но вот воплощать такое некому и некогда.
Re[3]: И это снова о поиске в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 13.11.06 22:12
Оценка:20 (3) +2
Здравствуйте, akasoft, Вы писали:

A>Здравствуйте, Lloyd, Вы писали:


L>>А вы вот это смотрели: Lucene.Net?


A>Посмотрел. Скачал 700 кил, перевёл автоматом в 2005 Студию, без ошибок. Скомпилировал и запустил примеры. Англоязычный текст ищет. При вводе русского слова (кирилицей на запрос Query: ) вылетает и ничего не ищет. А ещё юникод, блин.


Все ищет — только надо использовать RussianAnalyzer.

A>Индекс по файлам строит быстро, и даже запоминает пути. Возможно, что есть возможность связать тексты и mid сообщений и скормить их индексатору.


В общем, сделал я Lucent-based поиск для Януса — ищет очень быстро даже на ноутбуке в базе из 1.5 миллионов сообщений (450 мегабайт построенного индекса).
если интересно, могу запостить исходники.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re[4]: И это снова о поиске в избранное  новое    модер. 
От: akasoft 
Дата: 14.11.06 10:01
Здравствуйте, Igor Sukhov, Вы писали:

IS>В общем, сделал я Lucent-based поиск для Януса — ищет очень быстро даже на ноутбуке в базе из 1.5 миллионов сообщений (450 мегабайт построенного индекса).

IS>если интересно, могу запостить исходники.

Ты его интегрировал в Янус, или отдельной утилитой сделал? Индекс где хранишь? А сообщения — из ЛБД берёшь? SQL Express или Акцесс?
... << RSDN@Home 1.2.0 alpha rev. 667>> SQLE 2005
олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 14.11.06 20:34
Оценка:46 (4)
Здравствуйте, akasoft, Вы писали:

A>Здравствуйте, Igor Sukhov, Вы писали:


IS>>В общем, сделал я Lucent-based поиск для Януса — ищет очень быстро даже на ноутбуке в базе из 1.5 миллионов сообщений (450 мегабайт построенного индекса).

IS>>если интересно, могу запостить исходники.

A>Ты его интегрировал в Янус, или отдельной утилитой сделал? Индекс где хранишь? А сообщения — из ЛБД берёшь? SQL Express или Акцесс?


По порядку:

Поиск встроен в сам Янус, а точнее, старая реализация (powered by like % statement) поиска заменена на новую, гораздо более прогрессивную – понимающую русские словоформы, знающую про русские stop words и гораздо более скоростную. Смысла во внешнем поиске не вижу, так как после того как искомое сообщений найдены, часто требуется изучить всю тему, или ответить на вопрос. Очевидно, что из Януса это сделать намного проще.

Индексирование и сам поиск используют lucene.net версии 1.9.1. Исходники поискового движка взять на сайте lucene .net и обязательно убедится, что файлы в папке Analysys\RU\ проекта включены в компиляцию)

Индекс хранится в виде файлов, в специальной папочке. Если компьютер слабенький а сообщений много — папку с индексом удобно расположить рядом с файлами данных локальной базы сообщений, чтобы упростить процедуру backup-а, т.к. построение индекса с нуля может занять несколько часов. Другое дело, что если компьютер слабый а сообщений куча и вдруг у вас навернулась система и побились файлы БД – то нужно очищать карму. Вот так вот хранится индекс.

БД – понятно, что MS SQL, т.к. делал я все для себя. Реализация – новый метод в DatabaseManager и четыре совсем новых класса, один из которых – data accessor к таблице messages и,. Для Access надо сделать скопировать скопировать последний и выполнить следующее действие: replace”Sql” на “OleDb”.


Коротко, как эти 4 класса взаимодействуют с Янусом.

Класс MessageIndexer вычитывает порциями данные из БД и создает индекс из полей таблицы messages. Первичное индексирование производится по явному действию пользователя, новые же сообщения индексируются после каждой синхронизации.

Класс MessageSearcher ищет в ранее созданном индексе, вытягивает список идентификаторов сообщений, которые удовлетворяют условиям поиска. Список идентификаторов передается в DatabaseManager.GetSearchMessages3 – ну а далее все как было раньше.


Исходный код:

using System;
using System.Collections.Generic;
using System.Text;

using System.Data;
using System.Data.SqlClient;

namespace Rsdn.Janus.Search
{
    /// <summary>
    /// Класс содержит методы для получение данных об сообщениях из локальной базе сообщений..
    /// </summary>
    static class  MessageDataAccessor
    {
        static readonly string _connectionString = Config.Instance.ConnectionString;


        /// <summary>
        /// По указанному интервалу идентификаторов, считывает из БД данные из таблицы сообщений 
        /// и создает из этих данных список MessageSearchInfo объектов.
        /// </summary>
        /// <param name="firstId">Начальный Id.</param>
        /// <param name="lastId">Конечный Id.</param>
        /// <remarks>Список отсортирован по возрастанию идентификаторов сообщений.</remarks>
        /// <returns></returns>
        public static List<MessageSearchInfo> GetMessages(int firstId, int lastId)
        {
            List<MessageSearchInfo> result = new List<MessageSearchInfo>();

            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
                string sqlText = "select * from [messages] where mid between @firstId and @lastId order by mid";


                using (SqlCommand command = new SqlCommand(sqlText, connection))
                {
                    command.CommandTimeout = 10000000;

                    command.Parameters.Add(new SqlParameter("@firstId", firstId));
                    command.Parameters.Add(new SqlParameter("@lastId", lastId));


                    connection.Open();

                    SqlDataReader sqlDataReader = command.ExecuteReader();


                    int iMid = sqlDataReader.GetOrdinal("mid");
                    int iAuthor = sqlDataReader.GetOrdinal("usernick");
                    int iAuthorId = sqlDataReader.GetOrdinal("uid");
                    int iSubject = sqlDataReader.GetOrdinal("subject");
                    int iText = sqlDataReader.GetOrdinal("message");
                    int iGid = sqlDataReader.GetOrdinal("gid");
                    int iDateTime = sqlDataReader.GetOrdinal("dte");
                    
                    while (sqlDataReader.Read())
                    {
                        MessageSearchInfo msi =
                        new MessageSearchInfo
                        (
                            sqlDataReader.GetInt32(iMid),

                            sqlDataReader.GetString(iAuthor),
                            sqlDataReader.GetInt32(iAuthorId),

                            sqlDataReader.GetString(iSubject),
                            sqlDataReader.IsDBNull(iText) ? "" : sqlDataReader.GetString(iText),

                            sqlDataReader.GetInt32(iGid),

                            sqlDataReader.GetDateTime(iDateTime)
                        );

                        result.Add(msi);
                    }

                }


            }


            return result;
        }

    


        /// <summary>
        /// Возвращает максимальный Id сообщения из тех что есть в локальной базе сообщений.
        /// </summary>
        /// <returns></returns>
        public static int GetMaxMessageId()
        {
            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
                string sqlText = "select max(mid) from [messages]";


                using (SqlCommand command = new SqlCommand(sqlText, connection))
                {
                    connection.Open();

                    return (int)command.ExecuteScalar();
                }
            }
        }

        /// <summary>
        /// Возвращает количество сообщений в локальной базе сообщений.
        /// </summary>
        /// <returns></returns>
        public static int GetMessageCount()
        {
            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
                string sqlText = "select count(mid) from [messages]";


                using (SqlCommand command = new SqlCommand(sqlText, connection))
                {
                    connection.Open();

                    return (int)command.ExecuteScalar();
                }
            }
        }

    }
}



using System;
using System.Collections.Generic;
using System.ComponentModel;

using System.Text;

using Lucene.Net.Index;
using Lucene.Net.Documents;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Analysis.RU;
using Lucene.Net.Search;
using Lucene.Net.QueryParsers;

namespace Rsdn.Janus.Search
{
    /// <summary>
    /// Класс содержит набор методов для создания и обновления индекса сообщений.
    /// </summary>
    class MessageIndexer
    {
        private string _indexPath;


        /// <summary>
        /// Конструктор - инициализируем путем до папки где хранится, или будет хранится, индекс.
        /// </summary>
        /// <param name="indexPath"></param>
        public MessageIndexer(string indexPath)
        {
            _indexPath = indexPath;
        }

        /// <summary>
        /// На это событие подписываемся, чтобы отслеживать прогресс при индексации большого
        /// количества сообщений - например при первичной индексации базы.
        /// 
        /// При обработке этого события имеется возможность остановить процесс индексации.
        /// </summary>
        public event CancelEventHandler Progress;
        
        /// <summary>
        /// Обновляем индекс, добавляя в него новые, еще не проиндексированные, сообщения.
        /// </summary>
        /// <returns>Количество новых проиндексированных сообщений.</returns>
        public int UpdateIndex()
        {
            int maxMidInDb = MessageDataAccessor.GetMaxMessageId();
        
            const int step = 9999;

            int firstId         = GetMaxMidIndexer() + 1;
            int lastId          = firstId + step;
            int indexedCount    = 0;

            while (firstId <= maxMidInDb)
            {
                List<MessageSearchInfo> portion = MessageDataAccessor.GetMessages(firstId, lastId);

                // Do index
                //
                Index(portion);

                CancelEventArgs ea = new CancelEventArgs();

                if (null != Progress)
                {
                    Progress(this, ea);
                }               

                firstId         = lastId + 1;
                lastId          = firstId + step;
                indexedCount    += portion.Count;

                if (ea.Cancel)
                {
                    break;
                }
            }

            return indexedCount;
        }

        
        /// <summary>
        /// Индексируем данные списка MessageSearchInfo объектов.
        /// </summary>
        /// <param name="listMessages"></param>
        private void Index(List<MessageSearchInfo> listMessages)
        {
            IndexWriter writer;

            try
            {
                writer = new IndexWriter(_indexPath, new RussianAnalyzer(), false);
            }
            catch
            {
                writer = new IndexWriter(_indexPath, new RussianAnalyzer(), true);
            }

            foreach (MessageSearchInfo msi in listMessages)
            {
                writer.AddDocument( CreateDocument(msi) );
            }

            writer.Close();
        }

     
        /// <summary>
        /// Создаем Lucene-кий документ (объект хранящий поля индексируемой сущности).
        /// </summary>
        /// <param name="msi"></param>
        /// <returns>Созданный документ.</returns>
        private static Document CreateDocument(MessageSearchInfo msi)
        {
            Document doc = new Document();

            doc.Add(new Field("id", msi.Id.ToString(), true, true, false));

            doc.Add(Field.Text("authorNickname", msi.AuthorNickname));
            doc.Add(new Field("authorId", msi.AuthorId.ToString(), true, true, false));

            doc.Add(Field.Text("subject", msi.Subject));

            // DO NOT STORE message text in the index.
            //
            doc.Add(new Field("text", msi.Text, false, true, true));
            doc.Add(new Field("forumId", msi.ForumId.ToString(), true, true, false));

            doc.Add(new Field("creationDateTime", DateField.DateToString(msi.CreationDateTime), Field.Store.YES, Field.Index.UN_TOKENIZED));

            return doc;
        }


        /// <summary>
        /// Возвращаем количество документов в индексе
        /// (проиндексированных за все время сообщений).
        /// </summary>
        /// <returns></returns>
        public int GetDocumentCount()
        {
            try
            {
                IndexSearcher searcher = new IndexSearcher(_indexPath);

                int documentCount = searcher.MaxDoc();
              
                searcher.Close();

                return documentCount;
            }
            catch
            {
                return 0;
            }
        }


        /// <summary>
        /// Возвращает максимальный Id сообщения находящегося в индексе.
        /// </summary>
        /// <returns></returns>
        private int GetMaxMidIndexer()
        {
            try
            {
                IndexSearcher searcher = new IndexSearcher(_indexPath);

                int maxDoc = searcher.MaxDoc();
                int maxMid = 0;

                if (maxDoc > 0)
                {
                    Document maxDocument = searcher.Doc(maxDoc - 1);

                    maxMid = int.Parse(maxDocument.Get("id"));
                }

                searcher.Close();

                return maxMid;
            }
            catch
            {
                return 0;
            }
        }

       
    }
}



using System;
using System.Collections.Generic;
using System.Text;

using Lucene.Net.Index;
using Lucene.Net.Documents;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Analysis.RU;
using Lucene.Net.Search;
using Lucene.Net.QueryParsers;

namespace Rsdn.Janus.Search
{
    /// <summary>
    /// Класс содержит набор методов для поиска использующего Lucent движок.
    /// </summary>
    class MessageSearcher
    {
        private readonly string _indexPath;

        /// <summary>
        /// Конструктор - инициализируем путем до папки где хранится индекс.
        /// </summary>
        /// <param name="indexPath"></param>
        public MessageSearcher(string indexPath)
        {
            _indexPath = indexPath;
        }


        /// <summary>
        /// Ищем сообщения по заданным параметрам и возвращаем список идентификаторов
        /// сообщений.
        /// </summary>
        /// <param name="query">Текст который ищем. См. формат запросов на сайте lucene.</param>
        /// <param name="searchText">Искать ли в тексте сообщений.</param>
        /// <param name="searchSubject">Искать ли в заголовках сообщений.</param>
        /// <param name="searchAuthorNickname">Искать ли в никах авторов сообщений.</param>
        /// <param name="forumId">Если -1 - искать во всех форумах, иначе искать сообщения только в указанном форуме.</param>
        /// <returns>Спиков идентификаторов сообщений (идентификаторы - строки, не числа!) удовлетворяющих условиям поиска.</returns>
        public List<string> Search(
                                    string query,

                                    bool    searchText,
                                    bool    searchSubject,
                                    bool    searchAuthorNickname,

                                    int     forumId
                                    )
        {
            List<string> result = new List<string>();

            BooleanQuery bq = new BooleanQuery();

            if (searchText)
            {
                bq.Add(QueryParser.Parse(query, "text", new RussianAnalyzer()), false, false);
            }

            if (searchSubject)
            {
                bq.Add(QueryParser.Parse(query, "subject", new RussianAnalyzer()), false, false);
            }

            if (searchAuthorNickname)
            {
                bq.Add(QueryParser.Parse(query, "authorNickname", new RussianAnalyzer()), false, false);
            }

            Filter filter = null;

       
            if (-1 != forumId)
            {
                TermQuery tq = new TermQuery(new Term("forumId", forumId.ToString()));

                filter = new QueryFilter(tq);
            }


            IndexSearcher searcher = new IndexSearcher(_indexPath);

            Hits hits = (filter != null) ? searcher.Search(bq, filter) : searcher.Search(bq);


            for (int i = 0; i < hits.Length(); i++)
            {
                Document doc = hits.Doc(i);

                result.Add(doc.Get("id"));
            }

            searcher.Close();

            return result;
        }

    }
}



using System;
using System.Collections.Generic;
using System.Text;

namespace Rsdn.Janus.Search
{
    /// <summary>
    /// DTO с полями сообщения (табл. messages в БД) из которых будем строить индекс.
    /// </summary>
    class MessageSearchInfo
    {
        /// <summary>
        /// Id сообщения (столбец uid в БД)
        /// </summary>
        public int Id;

        /// <summary>
        /// Ник автора сообщения (столбец userNick в БД)
        /// </summary>
        public string AuthorNickname;

        /// <summary>
        /// Id автора сообщения (столбец uid в БД)
        /// </summary>
        public int AuthorId;

        /// <summary>
        /// Заголовок сообщения (столбец subj в БД)
        /// </summary>
        public string Subject;

        /// <summary>
        /// Текст сообщения (столбец text в БД)
        /// </summary>
        public string Text;

        /// <summary>
        /// Id форума (столбец gid в БД)
        /// </summary>
        public int ForumId;

        /// <summary>
        /// Время создания сообщения (столбец dte в БД)
        /// </summary>
        public DateTime CreationDateTime;

        public MessageSearchInfo
            (
                int id,

                string authorNickname,
                int authorId,
            
                string subject,
                string text,

                int forumId,
                DateTime creationDateTime


            )
        {
            this.Id = id;

            this.AuthorNickname = authorNickname;
            this.AuthorId = authorId;


            this.Subject = subject;
            this.Text = text;

            this.ForumId = forumId;

            this.CreationDateTime = creationDateTime;
        }

    }
}


    public static List<LinearTreeMsg> GetSearchMessages3(
            List<string> idList,
            bool SearchInMarked,
            bool SearchInMyMessages,
            bool SearchAnyWords,
            bool SearchInQuestions,
            DateTime from,
            DateTime to,
            SortType sortType)
        {
            ArrayList sqlParams = new ArrayList();

            string strWhere = String.Empty;

            #region Обработка "моих сообщений"

            if (SearchInMyMessages)
            {
                if (strWhere.Trim().Length > 0)
                    strWhere += " AND ";

                strWhere += "([uid] = @uid)";
                sqlParams.Add("@uid"); sqlParams.Add(Config.Instance.SelfId);
            }

            #endregion

            #region Обработка вопросов

            if (SearchInQuestions)
            {
                if (strWhere.Trim().Length > 0)
                    strWhere += " AND ";

                strWhere += "([tid] = 0)";
            }

            #endregion

            #region Обработка пометок пользователя

            if (SearchInMarked)
            {
                if (strWhere.Trim().Length > 0)
                    strWhere += " AND ";

                strWhere += "([ismarked] = " + SqlBuilder.True() + ")";
            }

            #endregion

            #region Обработка дат - from, to

            if (from.Ticks != 0)
            {
                // Сбросить время, оставить только дату
                from = new DateTime(from.Year, from.Month, from.Day);

                if (strWhere.Trim().Length > 0)
                    strWhere += " AND ";

                strWhere += "([dte] >= @fromdate)";
                sqlParams.Add("@fromdate"); sqlParams.Add(from);
            }

            if (to.Ticks != 0)
            {
                // Сбросить время, оставить только дату
                to = new DateTime(to.Year, to.Month, to.Day);

                if (strWhere.Trim().Length > 0)
                    strWhere += " AND ";

                strWhere += "([dte] <= @todate)";
                sqlParams.Add("@todate"); sqlParams.Add(to);
            }

            #endregion

   
            StringBuilder sb = new StringBuilder(10 * idList.Count);

            foreach (string messageId in idList)
            {
                sb.Append(messageId).Append(",");
            }

            sb.Remove(sb.Length - 1, 1);

             if (strWhere.Trim().Length > 0)
             {
                    strWhere += " AND ";
             }


             strWhere += String.Format("mid in({0})", sb.ToString());


            string commandText = string.Format(@"
                SELECT
                    [mid],
                    [gid],
                    [tid],
                    [pid],
                    [dte],
                    [uid],
                    [usernick],
                    [uclass],
                    [subject],
                    [isread],
                    [ismarked],
                    (SELECT Sum([rate]*[rby]) FROM [rating] WHERE [mid]=m.[mid] AND [rate] > 0) AS [rate],
                    (SELECT COUNT(*) FROM [rating] WHERE [mid]=m.[mid] AND [rate] = {0})    AS [smiles],
                    (SELECT COUNT(*) FROM [rating] WHERE [mid]=m.[mid] AND [rate] = {1})    AS [agree],
                    (SELECT COUNT(*) FROM [rating] WHERE [mid]=m.[mid] AND [rate] = {2})    AS [p1rate],
                    (SELECT COUNT(*) FROM [rating] WHERE [mid]=m.[mid] AND [rate] = {3})    AS [disagree],
                    (SELECT Sum([rate]*[rby]) FROM [rating] WHERE [tid]=m.[mid] AND [rate] > 0) AS [themerate],
                    (SELECT {4} FROM [subscribed_forums] WHERE [id]=m.[gid])              AS [Forum]
                FROM [messages] m
                {5}
                {6}",
                (int)MessageRates.Smile,
                (int)MessageRates.Agree,
                (int)MessageRates.Plus1,
                (int)MessageRates.DisAgree,
                Config.Instance.ForumDisplayConfig.ShowFullForumNames ? "[descript]" : "[name]",
                strWhere.Length != 0 ? "WHERE " + strWhere : String.Empty,
                DataSorter.SortCriteria(sortType, SortType.ByIdDesc));

            return JanusDB.ExecuteList<LinearTreeMsg>(commandText, sqlParams.ToArray());
        }



Ну, вот почти и все. Хотя нет – вот что надо еще сделать, не забыл конечно добавить Lucene.Net.Dll в references проекта Janus:

Изменения в SearchDummyForm.cs (метод btnSearch_Click):

            using (ProgressFormManager pfm = new ProgressFormManager(
                    SR.Search.Searching))
            {
                List<LinearTreeMsg> res = new List<LinearTreeMsg>();

                if (searchText.Length == 0)
                {
                    res = DatabaseManager.GetSearchMessages2(
                                         (Config.Instance.AdvancedSearch ? Config.Instance.SearchForumId : -1),
                                         Config.Instance.SearchText,
                                         Config.Instance.SearchInText,
                                         Config.Instance.SearchInSubject,
                                         Config.Instance.SearchAuthor,
                                         Config.Instance.SearchInMarked,
                                         Config.Instance.SearchInMyMessages,
                                         Config.Instance.SearchAnyWord,
                                         Config.Instance.SearchInQuestions,
                                         searchFromDate.Checked ? searchFromDate.Value : new DateTime(0),
                                         searchToDate.Checked ? searchToDate.Value : new DateTime(0),
                                         Config.Instance.SearchSortCriteria);
                }
                else
                {
                    MessageSearcher searcher = new MessageSearcher(_indexPath);



                    List<string> result = searcher.Search(Config.Instance.SearchText.Trim(),
                                                            Config.Instance.SearchInText,
                                                            Config.Instance.SearchInSubject,
                                                            Config.Instance.SearchAuthor,
                                                            (Config.Instance.AdvancedSearch ? Config.Instance.SearchForumId : -1));

                    if (result.Count > 0)
                    {
                        res = DatabaseManager.GetSearchMessages3(
                                                                result,
                                                                Config.Instance.SearchInMarked,
                                                                Config.Instance.SearchInMyMessages,
                                                                Config.Instance.SearchAnyWord,
                                                                Config.Instance.SearchInQuestions,
                                                                searchFromDate.Checked ? searchFromDate.Value : new DateTime(0),
                                                                searchToDate.Checked ? searchToDate.Value : new DateTime(0),
                                                                Config.Instance.SearchSortCriteria);
                    }
                }



                if (searchInOverquoting.Checked)
                    FilterOverquoting(res);



Изменения в Synchronizer.cs (метод Sync)

                if (ApplicationManager.Instance.OutboxManager.DownloadTopics.Count > 0)
                {
                    ApplicationManager.Instance.Logger.LogInfo(SR.Sync.DownloadTopics);
                    JanusTopicRequest topicRequest = PrepareTopicRequest();
                    JanusTopicResponse topicResponse = GetTopicResponse(svc, topicRequest);
                    ProcessTopicResponse(topicResponse);
                }

                // Update index
                //
                MessageIndexer messageIndexer = new MessageIndexer(this._indexPath);

                string indexedMsg = String.Format("Проиндексировано {0} сообщений.", messageIndexer.UpdateIndex());

                ApplicationManager.Instance.Logger.Log(indexedMsg);


                OnSyncFinished();



Не смотрите на _indexPath — это обман зрения, на самом деле это должно называться Config.Instance.SearchIndexPath

Ну и напоследок — known features:

1. Начальное создание индекса – это долгий процесс, многие эстеты, в том числе считают что для этого должно иметь отдельный пункт меню, нарошный диалог с индиктором прогресса и кнопку – чтобы превать потенциально длительную операцию. Я, сумел подавить свою тягу к прекрасному. Если вы не сумеете – обратите внимание на MessageDataAccessor.GetMessageCount, MessageIndexer.Progress и Progress.GetDocumentCount

2. Если сообщение удаляют – то ссылка на него отстается в индексе. Это проблема тех кто пишет такие сообщения – я не виноват.

3. Если сообщение исправляют или переносят в другой форум (т.е. меняется или текст, заголовок, ник автора, или id форума) – то индекс в данном случае, также не обновляется. Простое решение – это искать сразу во всех форумах и не искать те слова, которые обычно удаляют модераторы. Более сложное решение – переделать логику индексирования – т.е. передавать в нее id только что полученных сообщений и в случае если в индексе уже есть сообщения с таким id – обновлять поля (см. Document.RemoveFields & Document.AddField). Экзотическое решение — это приехать обратно в Сидней, переписать БД и папку с индексом на RAID массив, удалить файлы индекса и вызвать синхронизацию чтобы пересоздать индекс с нуля. Но это конечно на любителя



Писалось в один проход, без рефакторинга — в коде возможно наличие рудиментов.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re: олюцернивание в избранное  новое    модер. 
От: Holms 
Дата: 14.11.06 23:00
Оценка: +1
Здравствуйте, Igor Sukhov, Вы писали:

интересно, а можно получить вашу версию простым смертным?
Спасибо
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
The life is relative and reversible.
Re[2]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 15.11.06 16:55
Здравствуйте, Holms, Вы писали:

H>интересно, а можно получить вашу версию простым смертным?

H>Спасибо

Хороший вопрос. А что ты используешь в качестве базы для Януса – если MSSQL,
то все что нужно — это скачать последние исходники Януса (используйте поиск
чтобы найти ссылку =), добавить мои изменения в проект и пересобрать его еще раз.

Все что для этого нужно — VS2005 и 10 минут времени.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 15.11.06 19:41
Здравствуйте, Igor Sukhov, Вы писали:

Как я понимаю, все поля в Люцене должны быть текстовыми, в т.ч. числовые. Строки должны быть токенизированы, если мы хотим искать по словам, а если не хотим, то нет. А что даёт сохранение поля в индексе?

В твоём примере id сохраняется, текст нет, см. выделенное ниже. Если не сохранять, то типа экономия места? А можно ли при этом использовать фильтры? Раз Люцена так хорошо ищет, так может загнать в её документ все поисковые поля, условия поиска задавать фильтрами, а в исходном коде оставить только выборку по найденным mid.

        private static Document CreateDocument(MessageSearchInfo msi)
        {
            Document doc = new Document();

            doc.Add(new Field("id", msi.Id.ToString(), true, true, false));

            doc.Add(Field.Text("authorNickname", msi.AuthorNickname));
            doc.Add(new Field("authorId", msi.AuthorId.ToString(), true, true, false));

            doc.Add(Field.Text("subject", msi.Subject));

            // DO NOT STORE message text in the index.
            //
            doc.Add(new Field("text", msi.Text, false, true, true));
            doc.Add(new Field("forumId", msi.ForumId.ToString(), true, true, false));

            doc.Add(new Field("creationDateTime", DateField.DateToString(msi.CreationDateTime), Field.Store.YES, Field.Index.UN_TOKENIZED));

            return doc;
        }


Хм, что-то у меня последнее время сильное желание вытащить тексты сообщений из ЛБД, оставив там только индекс. Например, последней идеей было таки вытащить тела сообщений в текстовые файлы вида <mid>.txt, но их засунуть в RAR- или ZIP-архив. Экономия места, простота доступа. а ЛБД оставить только "шапки".

Это я к тому, что если Люцена позволяет хранить поля, в т.ч. текст сообщений, то может в ней и хранить. Хотя, тела она не сжимает, эти её индексы из демки ещё потом в 2 раза пакуются...
... << RSDN@Home 1.2.0 alpha rev. 666>> SQL Express 2005
Re[3]: олюцернивание в избранное  новое    модер. 
От: Holms 
Дата: 15.11.06 21:38
Здравствуйте, Igor Sukhov, Вы писали:

IS>Хороший вопрос. А что ты используешь в качестве базы для Януса – если MSSQL,

IS>то все что нужно — это скачать последние исходники Януса (используйте поиск
IS>чтобы найти ссылку =), добавить мои изменения в проект и пересобрать его еще раз.

IS>Все что для этого нужно — VS2005 и 10 минут времени.

использую то что идет в поставке с Янусом, т.е. Акцес.
Под простым смертным я имел ввиду тех к кого нету VS2005, ведь можно же выложить где-то саму програму и хелп для настройки SQL
Спасибо
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
The life is relative and reversible.
Re[2]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 15.11.06 23:27
Здравствуйте, akasoft, Вы писали:

A>Здравствуйте, Igor Sukhov, Вы писали:


A>Как я понимаю, все поля в Люцене должны быть текстовыми, в т.ч. числовые. Строки должны быть токенизированы, если мы хотим искать по словам, а если не хотим, то нет. А что даёт сохранение поля в индексе?


Да — поля надо перевести в строки. Если поле сохранить — его можно будет получить из Document в оригинальном виде,
т.е. если не экономить на размере индекса, можно сформировать dataset результатов, не обращаясь к БД.

A>В твоём примере id сохраняется, текст нет, см. выделенное ниже. Если не сохранять, то типа экономия места? А можно ли при этом использовать фильтры? Раз Люцена так хорошо ищет, так может загнать в её документ все поисковые поля, условия поиска задавать фильтрами, а в исходном коде оставить только выборку по найденным mid.


Как я написал — у меня была такая идея. Но — во первых это сложнее в реализации, а во вторых у меня есть смутные подозрания что на индексе
в 4ГБ люцен может медленнее раза в 1.5 чем на индексе объемом в 10 раз меньше и как следствие медленнне чем текущая реализация (Люцене+БД).

Сомнения были вызваны моими экспериментами на фильтрами — я так не понял, как сделать фильтр (наследник Filter)
фильтрующий по более чем одному условию — нашел что надо сделать ChainedFilter — но его в .NET реализации нету.

Но это не самое страшное — узкое место это фильтрация по временному интервалу. Документация перебивает саму себя,
предлагая использовать то DateFilter то RangeFilter, ни то ни другое — естественно ни работает — или отфильтровывало
все или не фильтровало ничего . В news-ах есть множество сообщений про то что поиск по промежутку дат работает
чрезвычайно медленно, хотя я все таки сделал такую фильтрацию через Query — но результатов так и не дождался.
Есть подозрения на то что если у дат прибить временную составляющую — все будет работать быстрее, но мне кажется
это тот случай, когда орешек крепче тубуса микроскопа. Тем более что под рукой есть молоток — сиквел.

Так что авторам Люцене есть пока что доводить до ума и на их месте я бы начал с документации и нормальных примеров,
но видимо портировать юнит-тесты им интереснее

A>
A>        private static Document CreateDocument(MessageSearchInfo msi)
A>        {
A>            Document doc = new Document();

A>            doc.Add(new Field("id", msi.Id.ToString(), true, true, false));

A>            doc.Add(Field.Text("authorNickname", msi.AuthorNickname));
A>            doc.Add(new Field("authorId", msi.AuthorId.ToString(), true, true, false));

A>            doc.Add(Field.Text("subject", msi.Subject));

A>            // DO NOT STORE message text in the index.
A>            //
A>            doc.Add(new Field("text", msi.Text, false, true, true));
A>            doc.Add(new Field("forumId", msi.ForumId.ToString(), true, true, false));

A>            doc.Add(new Field("creationDateTime", DateField.DateToString(msi.CreationDateTime), Field.Store.YES, Field.Index.UN_TOKENIZED));

A>            return doc;
A>        }
A>


A>Хм, что-то у меня последнее время сильное желание вытащить тексты сообщений из ЛБД, оставив там только индекс. Например, последней идеей было таки вытащить тела сообщений в текстовые файлы вида <mid>.txt, но их засунуть в RAR- или ZIP-архив. Экономия места, простота доступа. а ЛБД оставить только "шапки".


В чем выражается простота доступа? Не говоря уже о том, что приложение может слететь только из за того, что файл был случайно открыт, удален, скопирован, и так далее. Я не вижу простоты — вижу работу ручками с файлами , вижу пустой catch , вижу тормоза ... чур меня

A>Это я к тому, что если Люцена позволяет хранить поля, в т.ч. текст сообщений, то может в ней и хранить. Хотя, тела она не сжимает, эти её индексы из демки ещё потом в 2 раза пакуются...

Возможность сохранить поля непосредственно в Люцене дана нам свыше как оптимизация и ей не следует злоупотреблять.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re[4]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 15.11.06 23:27
Здравствуйте, Holms, Вы писали:

H>Здравствуйте, Igor Sukhov, Вы писали:


IS>>Хороший вопрос. А что ты используешь в качестве базы для Януса – если MSSQL,

IS>>то все что нужно — это скачать последние исходники Януса (используйте поиск
IS>>чтобы найти ссылку =), добавить мои изменения в проект и пересобрать его еще раз.

IS>>Все что для этого нужно — VS2005 и 10 минут времени.

H>использую то что идет в поставке с Янусом, т.е. Акцес.
H>Под простым смертным я имел ввиду тех к кого нету VS2005, ведь можно же выложить где-то саму програму и хелп для настройки SQL
H>Спасибо

Я бы мог поделиться своим exe-ник, но у меня в нем есть несколько хаккерских модификаций которые будут работать только на моем ноутбуке.
Поэтому выкладывать не буду. Вот если akasoft подружит поиск с Access-ом, думаю можно будет выложить на сайте.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re[5]: олюцернивание в избранное  новое    модер. 
От: Holms 
Дата: 16.11.06 01:54
Здравствуйте, Igor Sukhov, Вы писали:

IS>Я бы мог поделиться своим exe-ник, но у меня в нем есть несколько хаккерских модификаций которые будут работать только на моем ноутбуке.

IS>Поэтому выкладывать не буду. Вот если akasoft подружит поиск с Access-ом, думаю можно будет выложить на сайте.
ок, ясно, будем ждать
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
The life is relative and reversible.
Re[2]: олюцернивание в избранное  новое    модер. 
От: AndrewVK модератор 
Дата: 16.11.06 09:16
Здравствуйте, akasoft, Вы писали:

A>А что даёт сохранение поля в индексе?


Это так называемые index includes. Позволяет иметь часть данных без доболнительного обращения к источнику данных (это практически всегда лишнее дерганье башкой диска).

A>Это я к тому, что если Люцена позволяет хранить поля, в т.ч. текст сообщений, то может в ней и хранить.


Ага. И при порче или перестроении индекса терять все данные .
... << RSDN@Home 1.2.0 alpha rev. 642>>
Re[3]: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 16.11.06 10:51
Здравствуйте, AndrewVK, Вы писали:

AVK>Это так называемые index includes. Позволяет иметь часть данных без доболнительного обращения к источнику данных (это практически всегда лишнее дерганье башкой диска).


Короче, имеет смысл хранить только mid, чтобы потом получать его из найденных люценой документов и использовать в выборках. Остальное (имя, дата, форум, тему) хранить смысла нет, а только индексировать.

AVK>Ага. И при порче или перестроении индекса терять все данные .


Ага, можно подумать, что в других случаях при порче есть выбор.
... << RSDN@Home 1.2.0 alpha rev. 667>> SQLE 2005
Re[3]: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 16.11.06 10:53
Здравствуйте, Igor Sukhov, Вы писали:

В примере с поиском файлов дата вообще переводится в некое подобие unix timestamp, и уже по этому числу в строковом представлении фильтруется. М.б. имеет смысл поступить так же?
... << RSDN@Home 1.2.0 alpha rev. 667>> SQLE 2005
Re[4]: олюцернивание в избранное  новое    модер. 
От: AndrewVK модератор 
Дата: 16.11.06 12:18
Здравствуйте, akasoft, Вы писали:

AVK>>Ага. И при порче или перестроении индекса терять все данные .


A>Ага, можно подумать, что в других случаях при порче есть выбор.


Вероятности порчи даже убогого джета и индексного файла мягко говоря не одинаковая.
... << RSDN@Home 1.2.0 alpha rev. 642>>
Re[4]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 16.11.06 14:44
Здравствуйте, akasoft, Вы писали:

A>В примере с поиском файлов дата вообще переводится в некое подобие unix timestamp, и уже по этому числу в строковом представлении фильтруется. М.б. имеет смысл поступить так же?


Попробуй и сравни. Т.к. обращения к БД все равно не избежать, тот тут надо сравнить скорость поиска по индексу в БД (поле dte) со скоростью поиска по, как ты сказал, timestamp полю в индексе Lucene.

Если Lucene будет фильтровать по timestamp-у путем тупого сравнения значений, так как не пожертвовав минутами и секундами, кучности значений, а значит и эффективного индекса не добиться — то это будет фигня, медленная фигня. Если это реализовано умнее, то:

может получиться что Access например по датам ищет медленно и в этом случае имеет смысл заранее отфильтровать идентификаторы сообщений по временному интервалу, а для MSSQL дополнительное наложенное условие по индексированному полю — только ускорит поиск, во всяком случае у меня щас он просто летает — запрос по всем форумам с интервалом поиска в 2 года выполняется за 2-3 секунды.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re[5]: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 16.11.06 21:55
Здравствуйте, Igor Sukhov, Вы писали:

IS>Попробуй и сравни.


Попробую. Я думал, ты уже попробовал и знаешь.

Хотя, с другой стороны, уж лучше там оставить минимум данных, всё компактнее будет. Попробовал ейный оптимизатор (вызов Optimize() после построения части индекса), требует 2 раза по столько, сколько весит индекс. Пока не понял, оптимизирует, или в дуду играет. А может, это для удалённых документов задумано.

Формочку индексации я уже наваял. На своих > 1.7 млн. сообщений тоскливо ждать финала не видя конца туннеля и не имея возможности приостановить/продолжить. Хотя, можно наваять утилиту командной строки по переиндексированию ЛБД Януса.

Долго индекс строится, сабака! Обнадёживает, что ежедневный прирост сообщений "всего" 2-2.5 тыс. шт.

Ищет, конечно, уматно. Особенно словоформы. Ну и заморочка с пробелами и &, типа "сеансы в php" не то же самое, что и "сеансы&в&php". А нету ли у тебя ссылки на язык люсеновских поисковых запросов? Я невод покидал-покидал, но пока кроме док на 9 метров, Seekafile Server, книги по люсене и исходников к ней так пока и не нашёл. Вдруг завтра скачаю, а окажется, что там всё на яве. Не отчаиваюсь.

Ты писал тут
Автор: Igor Sukhov
Дата: 15.11.06
кучу отмазок в конце про удаление, переносы... В общем, всё это побороть можно. При получении пакета сообщений мы можем построить список mid, а затем по этому списку скомандовать люсене удалить документы из индекса, и затем сделать выборку этих mid и переиндексировать. Тысячу сообщений она будет жевать порядка 20 секунд. Приемлимо. Сотню жуёт пару секунд. Хотя, наверное, больше тратит времени на открытие/закрытие индекса.
... << RSDN@Home 1.2.0 alpha rev. 666>> SQL Express 2005
Re[6]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 16.11.06 23:23
Оценка:16 (1)
Здравствуйте, akasoft, Вы писали:

A>Здравствуйте, Igor Sukhov, Вы писали:


IS>>Попробуй и сравни.


(1)
A>Попробую. Я думал, ты уже попробовал и знаешь.

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

Чего мне сейчас в поиске не хватает — так это то что нельзя с найденными
сообщениями работать, как если бы я просматривал форум в штатном режиме —
ни поменить для дальнейшего просмотра, ни поменить как прочинатное/непрочитанное,
ни добавить в "Избранное". Вот это действительно серьезнейший косяк в дизайне.

A>Хотя, с другой стороны, уж лучше там оставить минимум данных, всё компактнее будет. Попробовал ейный оптимизатор (вызов Optimize() после построения части индекса), требует 2 раза по столько, сколько весит индекс. Пока не понял, оптимизирует, или в дуду играет. А может, это для удалённых документов задумано.


И сколько по времени эта оптимизация выполняется — если быстро, то имеет смысл вызывать ее после индексирования новой порции.

A>Формочку индексации я уже наваял. На своих > 1.7 млн. сообщений тоскливо ждать финала не видя конца туннеля и не имея возможности приостановить/продолжить. Хотя, можно наваять утилиту командной строки по переиндексированию ЛБД Януса.


Отлично.
Утилита командной строки идея неплохая — пускай с нее начнут те кто хочет Янус на linux переводить.

A>Долго индекс строится, сабака! Обнадёживает, что ежедневный прирост сообщений "всего" 2-2.5 тыс. шт.


Индексация 2-х тыщ сообщений — это секунды.

A>Ищет, конечно, уматно. Особенно словоформы. Ну и заморочка с пробелами и &, типа "сеансы в php" не то же самое, что и "сеансы&в&php". А нету ли у тебя ссылки на язык люсеновских поисковых запросов? Я невод покидал-покидал, но пока кроме док на 9 метров, Seekafile Server, книги по люсене и исходников к ней так пока и не нашёл. Вдруг завтра скачаю, а окажется, что там всё на яве. Не отчаиваюсь.


А зачем & ? AND — более привычная форма. Синтакис языка запросов тут — http://www.dotlucene.net/documentation/QuerySyntax.html, там и про что делать с & тоже написано.
Кстати, посмотри примеры запросы для date range поиска — как раз для твоего тестирования (*1).

A>Ты писал тут
Автор: Igor Sukhov
Дата: 15.11.06
кучу отмазок в конце про удаление, переносы... В общем, всё это побороть можно. При получении пакета сообщений мы можем построить список mid, а затем по этому списку скомандовать люсене удалить документы из индекса, и затем сделать выборку этих mid и переиндексировать. Тысячу сообщений она будет жевать порядка 20 секунд. Приемлимо. Сотню жуёт пару секунд. Хотя, наверное, больше тратит времени на открытие/закрытие индекса.


Не нужно удалять сами документы, Document.RemoveFields и затем AddFields решают проблему и перенесенных и удаленных сообщений.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re[7]: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 18.11.06 14:12
Здравствуйте, Igor Sukhov, Вы писали:

IS>Не нужно удалять сами документы, Document.RemoveFields и затем AddFields решают проблему и перенесенных и удаленных сообщений.


Похоже, это не так. Из метаданных:

        // Summary:
        //     Removes all fields with the given name from the document.  If there is no
        //     field with the specified name, the document remains unchanged.   Note that
        //     the removeField(s) methods like the add method only make sense prior to adding
        //     a document to an index. These methods cannot be used to change the content
        //     of an existing index! In order to achieve this, a document has to be deleted
        //     from an index and a new changed version of that document has to be added.
        
        public void RemoveFields(string name);

... << RSDN@Home 1.2.0 alpha rev. 666>> SQL Express 2005
Re[8]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 18.11.06 19:58
Здравствуйте, akasoft, Вы писали:

A>Здравствуйте, Igor Sukhov, Вы писали:


IS>>Не нужно удалять сами документы, Document.RemoveFields и затем AddFields решают проблему и перенесенных и удаленных сообщений.


A>Похоже, это не так. Из метаданных:


A>

A>
A>        // Summary:
A>        //     Removes all fields with the given name from the document.  If there is no
A>        //     field with the specified name, the document remains unchanged.   Note that
A>        //     the removeField(s) methods like the add method only make sense prior to adding
A>        //     a document to an index. These methods cannot be used to change the content
A>        //     of an existing index! In order to achieve this, a document has to be deleted
A>        //     from an index and a new changed version of that document has to be added.
        
A>        public void RemoveFields(string name);
A>


Нда — точно И почему могли по-нормальному сделать
Ксати, а что Optimize то делает — ты писал что он

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


в он в 2 раза увеличивает размер индекса ? На сайте есть только что:

Merges all segments together into a single segment, optimizing an index for search.



сами же сегменты, а точнее их количество влияют как на скорость индексации так и на скорость поиска.
Нормального же примера или рекомендаций, как изменять количество сегментов, при

    1 Batch индексации
    2 Индексации небольшого количества документов
    3 Активном поиске

я найти у них не смог
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re[9]: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 20.11.06 12:07
Здравствуйте, Igor Sukhov, Вы писали:

IS>Ксати, а что Optimize то делает — ты писал что он


Оптимизирует индекс, удаляет удалённые документы (их можно восстановить при необходимости, как в dbf), сливает все сегменты в один, поэтому требует в 2 раза больше места. Полезно иногда делать, только не злоупотреблять. Сродни акцессовскому "сжать и восстановить". По индексу из одного сегмента и без хлама поиск быстрее и производительнее.

IS>я найти у них не смог


Я нашёл некоторые рекомендации на сайте zend, они люценовский движок используют.

По умолчанию SetMaxBufferedDocs выставлен в 10, что приводит к частому сбросу мелких порций на диск, надо выставить больше, например 4096. Тогда индекс строится существенно быстрее, но и память требует порядка 100 — 200 МБ. Это всё актуально при обработке (индексировании) большого массива данных. Поиск и на 10 вполне шустр.

Прикрутил я Люську к Янусу, вроде даже работает , тестирую сейчас...
... << RSDN@Home 1.2.0 alpha rev. 667>> SQLE 2005
Re[10]: олюцернивание в избранное  новое    модер. 
От: ironwit 
Дата: 20.11.06 12:33
Здравствуйте, akasoft, Вы писали:

A>Прикрутил я Люську к Янусу, вроде даже работает , тестирую сейчас...

ты же не забудь ее на access прикрутить
... << RSDN@Home 1.2.0 alpha rev. 667>>
Я не умею быть злым, и не хочу быть добрым.
Re[10]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 20.11.06 14:46
Здравствуйте, akasoft, Вы писали:

A>Я нашёл некоторые рекомендации на сайте zend, они люценовский движок используют.


A>По умолчанию SetMaxBufferedDocs выставлен в 10, что приводит к частому сбросу мелких порций на диск, надо выставить больше, например 4096. Тогда индекс строится существенно быстрее, но и память требует порядка 100 — 200 МБ. Это всё актуально при обработке (индексировании) большого массива данных. Поиск и на 10 вполне шустр.


Ну 200 мегабайт это не много — а "существенно быстрее" — во сколько раз быстрее ?

A>Прикрутил я Люську к Янусу, вроде даже работает , тестирую сейчас...


Давно пора
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *
Re[11]: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 20.11.06 16:42
Оценка:45 (4)
Здравствуйте, Igor Sukhov, Вы писали:

IS>Ну 200 мегабайт это не много — а "существенно быстрее" — во сколько раз быстрее ?


(c) "Сколько вешать в граммах?".

Тестов не делал, всё на глаз.

На моих 1.7 млн. сообщений при 10 индекс строился порядка 6-7 часов, потому что компьютер (P4/3.0GHz/2Gb RAM/SATA RAID) буквально вешался: через 30 сек после начала построения процессы System и TSVNCache.exe начинали жрать процессорное время до 20 и до 55 соответственно, остальное шло на Janus.exe.

Я, конечно, понимаю, что SVN ведёт мониторинг файловой системы, но не до такой же степени. Ну а от имени System у меня "бьёт в барабаны" DrWeb.

Оказалось, что Люськин индексатор клепает мелкие файлы со скоростью звука, тут же их затирает, объединяет... В общем, пипец как мне это не понравилось, и полез я искать чего по оптимизации. На моё удивление, нашёл на русском у zend про php , они Люську используют для поиска.

Со второй попытки нашёл нужную комбинацию , при первой Янус сожрал 2 с лишним гига памяти ничего не сбрасывая на диск, словил OutOfMemory. Не советую менять MaxMergeDocs, пусть будет MaxInt.

Сейчас выставил MaxBufferedDocs 4096, менее чем за 1.5 часа можно проиндексировать мои 1.7 млн. сообщений, пропуск проиндексированного массива в 800 тыс. занял порядка 10 мин.

Итого: менее 1.5 часа параллельно работая против более 6 часов с полной вешалкой для 1.7 млн. , объём индекса — 388 МБ, ЛБД SQL 2005 Express — 3.4 ГБ, при индексации Janus.exe хапает местами до 450 МБ, потом отдаёт до 70-90 "обычных" МБ.
... << RSDN@Home 1.2.0 alpha rev. 667>> SQL Express 2005
Re[11]: олюцернивание в избранное  новое    модер. 
От: akasoft 
Дата: 21.11.06 16:05
Здравствуйте, ironwit, Вы писали:

I>ты же не забудь ее на access прикрутить


Это бесплатно прилагается к SQL Express.

Сегодня испытал, на Акцессе тоже работает. Испытывал на моей конца прошлого года 2Г .mdb, более 1.2 млн. сообщений. За 10 мин. проиндексировала более 340 тыс. сообщений (27%). Быстро индексирует, быстрее SQLE, видимо, можно чего-то в SQLE подкрутить ещё. Прервал.

Поиск даже быстрее, бо акцесс у меня шустрее SQLE, выборки результата из ЛБД идут за секунду, в то время как SQLE требует нескольких секунд. Ну, конечно у меня акцессовская ЛДБ сжата и всё такое.
... << RSDN@Home 1.2.0 alpha rev. 667>> SQL Express 2005
Re[12]: олюцернивание в избранное  новое    модер. 
От: ironwit 
Дата: 22.11.06 05:41
Оценка: +2 :)
Здравствуйте, akasoft, Вы писали:

A>Здравствуйте, ironwit, Вы писали:


I>>ты же не забудь ее на access прикрутить


A>Это бесплатно прилагается к SQL Express.


точно издеваешься. Бинарник в студию точнее из студии в файлы на рсдн
... << RSDN@Home 1.2.0 alpha rev. 667>>
Я не умею быть злым, и не хочу быть добрым.
Re[5]: олюцернивание в избранное  новое    модер. 
От: nitr0 
Дата: 08.12.06 09:48
Здравствуйте, Igor Sukhov, Вы писали:

IS>Я бы мог поделиться своим exe-ник, но у меня в нем есть несколько хаккерских модификаций которые будут работать только на моем ноутбуке.


А что за модификации, если не секрет ?
Для производительности ?
Re[6]: олюцернивание в избранное  новое    модер. 
От: Igor Sukhov rsdn 
Дата: 09.12.06 09:15
Оценка:2 (1)
Здравствуйте, nitr0, Вы писали:

IS>>Я бы мог поделиться своим exe-ник, но у меня в нем есть несколько хаккерских модификаций которые будут работать только на моем ноутбуке.

N>А что за модификации, если не секрет ?

Вот доделаю — и покажу и расскажу. А пока — неготово.
... << RSDN@Home 1.2.0 alpha rev. 0>>
* thriving in a production environment *