Быстрый, построчный эквивалент «grep -n» для структуры каталогов Unix

#python #search #lucene #indexing #sphinx

#python #Поиск #lucene #индексирование #sphinx

Вопрос:

Я пытаюсь создать веб-интерфейс для поиска по большому количеству огромных файлов конфигурации (приблизительно 60000 файлов, каждый размером от 20 КБайт до 50 МБайт). Эти файлы также часто обновляются (~ 3 раза в день).

Требования:

  • Параллелизм
  • Необходимо определить номера строк для каждой соответствующей строки
  • Хорошая производительность обновления

Что я изучил:

  • Lucene: Для определения номера строки каждая строка должна храниться в отдельном документе Lucene, каждое из которых содержит два поля (номер строки и строка). Это затрудняет обновление.
  • SOLR и Sphinx: оба основаны на Lucene, у них одинаковая проблема и они не позволяют идентифицировать номер строки.
  • Таблица SQL с полнотекстовым индексом: опять же, нет способа показать номер строки.
  • Таблица SQL с каждой строкой в отдельной строке: тестировал это с помощью SQLite или MySQL, и производительность обновления была худшей из всех вариантов. Обновление документа объемом 50 МБ заняло более часа.
  • eXist-db: мы преобразовали каждый текстовый файл в XML следующим образом: <xml><line number="1">test</line>...</xml> . Обновления занимают ~ 5 минут, что в некоторой степени работает, но мы по-прежнему недовольны этим.
  • Whoosh для Python: очень похоже на Lucene. Я реализовал прототип, который работает путем удаления / повторного импорта всех строк данного файла. При использовании этого метода обновление документа размером 50 МБ занимает около 2-3 минут.
  • Утилиты GNU id: предложенный sarnold, это невероятно быстрый (документ размером 50 МБ обновляется менее чем за 10 секунд на моей тестовой машине) и был бы идеальным, если бы у него была разбивка на страницы и API.

Как бы вы реализовали альтернативу?

Комментарии:

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

2. Lucene не работает медленно, идентифицируя / обновляя все строки каждого документа, если они хранятся таким образом.

3. Почему бы вам не взглянуть на OpenGrok и не попробовать его.

4. При обновлении ваших файлов вы точно знаете, какие номера строк обновляются? Также я почти уверен, что Sphinx не основан на lucene.

Ответ №1:

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

 $ gid ugly
include/linux/hil_mlc.h:66:  * a positive return value causes the "ugly" branch to be taken.
include/linux/hil_mlc.h:101:    int         ugly;   /* Node to jump to on timeout       */
  

Восстановление индекса из холодного кэша выполняется достаточно быстро:

 $ time mkid

real    1m33.022s
user    0m17.360s
sys     0m2.730s
  

Восстановление индекса из «теплого» кэша происходит намного быстрее:

 $ time mkid

real    0m15.692s
user    0m15.070s
sys     0m0.520s
  

Индекс занимает всего 46 мегабайт для моих 2,1 гигабайт данных — что немного по сравнению с вашим, но соотношение кажется хорошим.

Поиск 399 вхождений foo занял всего 0.039 секунды:

 $ time gid foo > /dev/null

real    0m0.038s
user    0m0.030s
sys     0m0.000s
  

Обновить

Ларсмансу было любопытно узнать о производительности git grep в исходных текстах ядра — это отличный способ показать, какой прирост производительности gid(1) обеспечивает.

В холодном кэше git grep foo (который вернул 1656 записей, намного больше, чем idutils):

 $ time git grep foo > /dev/null

real    0m19.231s
user    0m1.480s
sys     0m0.680s
  

Как только кэш был прогрет, git grep foo работает намного быстрее:

 $ time git grep foo > /dev/null

real    0m0.264s
user    0m1.320s
sys     0m0.330s
  

Поскольку мой набор данных полностью помещается в оперативную память, как только кэш прогреется, git grep это довольно удивительно: это всего в семь раз медленнее, чем gid(1) утилита, и, конечно, было бы более чем достаточно быстро для интерактивного использования. Если рассматриваемый набор данных не может быть полностью кэширован (что, вероятно, действительно становится интересным), то преимущество индекса в производительности безошибочно.

Две жалобы на idutils:

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

  2. Нет API: действительно, API нет. Но исходный код доступен; src/lid.c функция report_grep() принимает связанный список файлов, которые соответствуют выходным данным. Небольшая работа с этой функцией должна даже предлагать разбивку на страницы. (Это потребует некоторых действий.) В конце концов, у вас будет C API, который все еще может быть не идеальным. Но его настройка не выглядит ужасно.

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

Комментарии:

1. 1. Мне любопытно, как это соотносится с git grep производительностью?

2. Только что протестирован, и id-utils, безусловно, достаточно быстр. Однако отсутствие API и разбивки на страницы делают его невыполнимым для веб-приложения.

3. @larsmans, я включил git grep тайминги. git grep невероятно работает с теплым кэшем.

Ответ №2:

На случай, если кому-то это понадобится, я создал Whoosh Store, который по сути представляет собой основанный на Whoosh чистый Python-клон утилит GNU id utils, предоставляющий инкрементные обновления, разбивку на страницы и Python API.

Клиент командной строки работает следующим образом:

 ws-update -b --index my.idx datadir  # build the index
ws-update -b --append --index my.idx datadir  # incremental update
ws --index my.idx hello world     # query the index
  

( -b предназначен для пакетного обновления, которое выполняется быстрее, но требует больше памяти. Для полного использования синтаксиса CLI --help .)

Это и близко не соответствует скорости утилит GNU id, но за счет обновления индекса с помощью нескольких инкрементных пакетных обновлений (в памяти) для нас это достаточно быстро.