Быстрый текстовый поиск в более чем 600 000 файлов

У меня есть php, linux-сервер. У него есть папка с именем notes_docs, которая содержит более 600 000 файлов txt. Структура папки notes_docs выглядит следующим образом:

 - notes_docs
   - files_txt
     - 20170831
           - 1_837837472_abc_file.txt
           - 1_579374743_abc2_file.txt
           - 1_291838733_uridjdh.txt
           - 1_482737439_a8weele.txt
           - 1_733839474_dejsde.txt
     - 20170830
     - 20170829

Мне нужно предоставить утилиту быстрого поиска текста, которая может показывать результаты в браузере. Поэтому, если мой пользователь ищет "новый йорк", все файлы, имеющие в них "новый йорк", должны быть возвращены в массив. Если пользователь ищет "foo", все файлы с "foo" в них должны быть возвращены.

Я уже пробовал код с помощью scandir и Directory Iterator, который слишком медленный. Для поиска требуется больше минуты, даже тогда поиск не был завершен. Я попробовал ubuntu find, который снова замедлился, заработав минуту. потому что слишком много итераций папок, а notes_docs текущий размер составляет более 20 ГБ.

Любое решение, которое я могу использовать для его ускорения, приветствуется. Я могу внести изменения в дизайн, интегрировать свой PHP-код в завиток к другому языковому коду. Я также могу вносить изменения в инфраструктуру в крайних случаях (как при использовании в памяти чего-то).

Я хочу знать, как это делают люди в промышленности? Люди в действительности, Zip Recruiter обеспечивают поиск файлов.

Обратите внимание: у меня 2 ГБ - 4 ГБ в ОЗУ, поэтому загрузка всех файлов в ОЗУ все время неприемлема.

EDIT - Все приведенные ниже вводы великолепны. Для тех, кто пришел позже, мы закончили использование Lucene для индексирования и текстового поиска. Это было действительно хорошо

Ответы

Ответ 1

Чтобы все было просто: нет быстрого доступа к открытию, поиску и закрытию 600k документов каждый раз, когда вы хотите выполнить поиск. Ваши тесты с "минутой", вероятно, с одиночными тестовыми счетами. Если вы планируете искать их через многопользовательский веб-сайт, вы можете быстро забыть об этом, потому что ваш disk IO будет отключен от диаграмм и блокирует весь ваш сервер.

Итак, ваши единственные варианты - индексировать все файлы. Как и любая другая утилита быстрого поиска. Независимо от того, используете ли вы Solr или ElasticSearch, как указано в комментариях, или создайте что-то свое. Файлы будут проиндексированы.

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

Затем вы можете включить full text search в своей базе данных (mysql, mssql и другие ее поддерживают), и я уверен, что время ответа будет намного лучше. Имейте в виду, что для создания этих indexes требуется пространство для хранения, но то же самое относится и к другим решениям.

Теперь, если вы действительно хотите ускорить процесс, вы можете попытаться проанализировать резюме на более подробном уровне. Попробуйте найти места, образование, разговорные языки и другую информацию, которую вы регулярно просматриваете и размещаете в отдельных таблицах/столбцах. Это очень сложная задача и почти сам проект, но если вы хотите получить ценный результат поиска, это путь. Поскольку поиск в тексте без контекста дает очень разные результаты, просто подумайте о своем примере "new york":

  • Я живу в Нью-Йорке.
  • Я учился в Нью-Йоркском университете.
  • Мне нравится песня "new york" от Алисии Кис в личной биографии.
  • Я работал в New York Pizza.
  • Я родился в Нью-Йоркшире, Великобритания.
  • Я провел лето, размножаясь новыми йоркширскими терьерами.

Ответ 2

Я не пойду слишком глубоко, но я попытаюсь дать рекомендации по созданию доказательной концепции.

1

Сначала скачайте и извлеките упругий поиск отсюда: https://www.elastic.co/downloads/elasticsearch, а затем запустите его:

bin/elasticsearch

2

Загрузите https://github.com/dadoonet/fscrawler#download-fscrawler извлеките его и запустите:

bin/fscrawler myCustomJob

Затем остановите его (Ctrl-C) и отредактируйте соответствующий myCustomJob/_settings.json (он был создан автоматически, и путь был напечатан на консоли).
Вы можете редактировать свойства: "url" (путь для сканирования),   "update_rate" (вы можете сделать это 1m),   "includes" (например, ["*.pdf","*.doc","*.txt"]), "index_content" (сделать его ложным, оставаясь только на имени файла).

Запустить еще раз:

bin/fscrawler myCustomJob

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

3

Теперь начните добавлять файлы в каталог, указанный в свойстве "url".

4

Загрузите расширенный клиент для chrome и выполните следующие POST:

URL: http://localhost:9200/_search

Сырая полезная нагрузка:

{
  "query": { "wildcard": {"file.filename":"aFileNameToSearchFor*"} }
}

Вы получите список совпадающих файлов. Примечание: fscrawler индексирует имена файлов под ключ: file.filename.

5

Теперь вместо использования расширенного клиента для отдыха вы можете использовать PHP для выполнения этого запроса. Либо вызовом REST на url выше, либо с использованием php-client api: https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/_search_operations.html

То же самое относится к индексированию: https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/_indexing_documents.html

Ответ 3

Если вы хотите сохранить всю информацию о файлах в базе данных:

<?php 
function deep_scandir( $dir, &$query, &$files) {

    $count = 0;

    if(is_dir($dir)) {
        if ($dh = opendir($dir)) {
            while (($item = readdir($dh)) !== false) {
                if($item != '.' && $item != '..') {
                    if(is_dir($dir.'/'.$item)){
                        deep_scandir($dir.'/'.$item, $query, $files);
                    }else{
                        $count++;
                        preg_match("/(\d\_\d+)\_(.*)\.txt/i", $item, $matches);
                        if(!empty($matches)){
                            $no = $matches[1];
                            $str = $matches[2];
                            $files[$dir][$no] = $str;
                            $content = addcslashes( htmlspecialchars( file_get_contents($dir.'/'.$item) ), "\\\'\"" );
                            $query[] =  "INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES\n(NULL, '$no', '$str', '$dir/$item', '$content');";
                        }
                    }
                }
            }
            closedir($dh);
        }
    }
}

echo '<pre>';
$dir = 'notes_docs/files_txt';
$query = [];
$files = [];
deep_scandir($dir, $query, $files);
print_r($files);
echo '<br>';
print_r($query);

Теперь вы можете выполнить каждую строку в массиве

foreach($query as $no=>$line){
    mysql_query($line) or trigger_error("Couldn't execute query no: '$no' [$line]");
}

Вывод:

Array
(
    [notes_docs/files_txt/20170831] => Array
        (
            [1_291838733] => uridjdh
            [1_482737439] => a8weele
            [1_579374743] => abc2_file
            [1_733839474] => dejsde
            [1_837837472] => abc_file
        )

)

Array
(
    [0] => INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES
(NULL, '1_291838733', 'uridjdh', 'notes_docs/files_txt/20170831/1_291838733_uridjdh.txt', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus in nisl quis lectus sagittis ullamcorper at faucibus urna. Suspendisse tristique arcu sit amet ligula cursus pretium vitae eu elit. Nullam sed dolor ornare ex lobortis posuere. Quisque venenatis laoreet diam, in imperdiet arcu fermentum eu. Aenean molestie ligula id sem ultricies aliquet non a velit. Proin suscipit interdum vulputate. Nullam finibus gravida est, et fermentum est cursus eu. Integer sed metus ac urna molestie finibus. Aenean hendrerit ante quis diam ultrices pellentesque. Duis luctus turpis id ipsum dictum accumsan. Curabitur ornare nisi ligula, non pretium nulla venenatis sed. Aenean pharetra odio nec mi aliquam molestie. Fusce a condimentum nisl. Quisque mattis, nulla suscipit condimentum finibus, leo ex eleifend felis, vel efficitur eros turpis nec sem. ');
    [1] => INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES
(NULL, '1_482737439', 'a8weele', 'notes_docs/files_txt/20170831/1_482737439_a8weele.txt', 'Nunc et odio sed odio rhoncus venenatis congue non nulla. Aliquam dictum, felis ac aliquam luctus, purus mi dignissim magna, vitae pharetra risus elit ac mi. Sed sodales dui semper commodo iaculis. Nunc vitae neque ut arcu gravida commodo. Fusce feugiat velit et felis pharetra posuere sit amet sit amet neque. Phasellus iaculis turpis odio, non consequat nunc consectetur a. Praesent ornare nisi non accumsan bibendum. Nunc vel ultricies enim, consectetur fermentum nisl. Sed eu augue ac massa efficitur ullamcorper. Ut hendrerit nisi arcu, a sagittis velit viverra ac. Quisque cursus nunc ac tincidunt sollicitudin. Cras eu rhoncus ante, ac varius velit. Mauris nibh lorem, viverra in porttitor at, interdum vel elit. Aliquam imperdiet lacus eu mi tincidunt volutpat. Vestibulum ut dolor risus. ');
    [2] => INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES
(NULL, '1_579374743', 'abc2_file', 'notes_docs/files_txt/20170831/1_579374743_abc2_file.txt', 'Vivamus aliquet id elit vitae blandit. Proin laoreet ipsum sed tincidunt commodo. Fusce faucibus quam quam, in ornare ex fermentum et. Suspendisse dignissim, tortor at fringilla tempus, nibh lacus pretium metus, vel tempus dolor tellus ac orci. Vestibulum in congue dolor, nec porta elit. Donec pellentesque, neque sed commodo blandit, augue sapien dapibus arcu, sit amet hendrerit felis libero id ante. Praesent vitae elit at eros faucibus volutpat. Integer rutrum augue laoreet ex porta, ut faucibus elit accumsan. Donec in neque sagittis, auctor diam ac, viverra diam. Phasellus vel quam dolor. Nullam nisi tellus, faucibus a finibus et, blandit ac nisl. Vestibulum interdum malesuada sem, nec semper mi placerat quis. Nullam non bibendum sem, vitae elementum metus. Donec non ipsum quis turpis semper lobortis.');
    [3] => INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES
(NULL, '1_733839474', 'dejsde', 'notes_docs/files_txt/20170831/1_733839474_dejsde.txt', 'Nunc faucibus, enim non luctus rutrum, lorem urna finibus turpis, sit amet dictum turpis ipsum pharetra ex. Donec at leo vitae massa consectetur viverra eget vel diam. Sed in neque tempor, vulputate quam sed, ullamcorper nisl. Fusce mollis libero in metus tincidunt interdum. Cras tempus porttitor nunc nec dapibus. Vestibulum condimentum, nisl eget venenatis tincidunt, nunc sem placerat dui, quis luctus nisl erat sed orci. Maecenas maximus finibus magna in facilisis. Maecenas maximus turpis eget dignissim fermentum. ');
    [4] => INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES
(NULL, '1_837837472', 'abc_file', 'notes_docs/files_txt/20170831/1_837837472_abc_file.txt', 'Integer non ex condimentum, aliquet lectus id, accumsan nibh. Quisque aliquet, ante vitae convallis ullamcorper, velit diam tempus diam, et accumsan metus eros at tellus. Sed lacinia mauris sem, scelerisque efficitur mauris aliquam a. Nullam non auctor leo. In mattis mauris eu blandit varius. Phasellus interdum mi nec enim imperdiet tristique. In nec porttitor erat, tempor malesuada ante. Etiam scelerisque ligula et ex maximus, placerat consequat nunc consectetur. Phasellus suscipit ligula sed elit hendrerit laoreet. Suspendisse ex sem, placerat pharetra ligula eu, accumsan rhoncus ex. Sed luctus nisi vitae metus maximus scelerisque. Suspendisse porta nibh eget placerat tempus. Nunc efficitur gravida sagittis. ');
)

Ответ 4

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

Это было бы самым простым решением, и это могло бы вырасти, добавив дополнительные таблицы, содержащие слова с именами файлов (это может быть что-то сделано динамически, например, при наиболее частых поисках)

Ответ 5

Файловая система или база данных
Чтобы работать с большим количеством данных, вам нужно использовать какую-то БД. Как google делает!

Так что я должен использовать реляционные базы данных, такие как Mysql?
Во многих случаях (например, в вашем случае) реляционные базы данных и SQL не являются правильным выбором. Сегодня большинство поисковых систем и больших анализаторов данных используют базы данных NOSQL.
NOSQL DB намного быстрее, чем SQL DB. Но они обычно используют много аппаратных ресурсов (RAM,...).

Ваше решение

  • Найдите информацию о базах данных nosql и проверьте их отличия. Также вы должны выполнить полный текстовый поиск в своем db, поэтому выполните поиск по реализации полного текстового поиска в каждом из них. Выберите NOSQL DB: Aerospike (самый быстрый) - Cassandra - MongoDB - ...
  • Импортируйте все данные ваших файлов в свою БД.
  • Выберите правильный и быстрый язык программирования (я не рекомендую использовать PHP).
  • Создайте и создайте свою поисковую систему как услугу.
  • Подключите свою основную программу к своей поисковой системе.