Прим. переводчика: автор статьи рассказывает, как его команде удалось оптимизировать временные и ресурсные затраты при токенизации текстов в Elasticsearch путем внедрения нормализации похожих символов.
Это четвертая часть серии статей об обновлении кластера Elasticsearch без простоев и с минимальным воздействием на пользователей.
![](https://habrastorage.org/getpro/habr/upload_files/879/6be/969/8796be9692555ab237ecf708c343445d.png)
Во второй части было рассказано о решении провести полную переиндексацию всего датасета в процессе обновления Elasticsearch. В этой части пойдет речь о некоторых изменениях, которые были внесены в документы во время переиндексации.
Новый взгляд на старые истины
Решимость переработать все данные вкупе с запросом от остальных сотрудников на реальные улучшения открыли путь к полному переосмыслению процесса индексации данных.
Самое лучшее в этой свободе — возможность выйти за ограничения существующей системы и заняться вопросами, которые в другое время были бы попросту нерешаемыми.
Увы, была у нее и обратная сторона: решение отбросить существующие рамки автоматически влекло за собой потребность решать, что изменить, а что оставить. Приходилось лавировать между долгожданными улучшениями в системе и дополнительными рисками для проекта из-за слишком глобальных перемен.
Наш поисковый движок нельзя назвать рядовым внутренним инструментом, поскольку он влияет на всю организацию. Десяткам тысяч клиентов, очевидно, необходимо, чтобы все сохраненные поисковые запросы (коих более 800 тыс.) продолжали работать до, во время и после обновления.
Подобная потребность в «обратной совместимости» прямо противоречила нашим стремлениям: улучшить способы анализа, токенизации и нормализации контента.
Токенизация в Elasticsearch
Токенизация — один из важнейших внутренних механизмов Elasticsearch, и нам в Meltwater крайне важно иметь возможность контролировать его работу во всех аспектах (в частности, чтобы понимать, почему тот или иной документ попал или не попал в результаты запроса и объяснять это пользователям).
Так что это за хитрый процесс такой? Итак:
Токенизация — это превращение текстов и запросов в токены.
Elasticsearch поддерживает только низкоуровневое точное сопоставление токенов.
При добавлении документа в Elasticsearch создается инвертированный индекс. В некотором смысле это можно сравнить с таблицей, где каждый токен в документе выступает идентификатором, а ID документа — значением. То есть каждое слово соответствует своему собственному токену.
Например, после обработки документов:
Id 1: “meltwater use elasticsearch”
Id 2: “elasticsearch is used for search”
Id 3: “search is fast”
получится инвертированный индекс следующего вида:
meltwater | [1] |
use | [1] |
elasticsearch | [1,2] |
is | [2,3] |
used | [2] |
for | [2] |
search | [2,3] |
fast | [3] |
Поиск слова elasticsearch
запустит поиск в этой таблице токена elasticsearch
, который вернет [1,2]
— индексы документов 1 и 2. Поиск по слову elastic
не вернет ни одного результата, поскольку такого токена в таблице не существует. Аналогичным образом запрос по use
не выдаст ни одного документа с токеном used
, поскольку это разные токены.
Токенизация — это часть процесса, называемого анализом, который Elasticsearch выполняет над текстом, создавая токены, подходящие под конкретный сценарий использования. Токенизатор разбивает текст на токены, а ряд фильтров изменяют текст и токены различными способами.
Пример простого токенизатора — разбивка текста по пробелам.
Добавление синонимов, стемминг или лемматизация (приведение слова к его нормальной форме), изменение или полное удаление определенных символов — примеры фильтров.
Наша цель — дать клиентам возможность ставить максимально подробные запросы, которые работают независимо от языка. Алгоритм определения на основе ИИ поддерживает 241 язык, но даже столь богатый перечень не избавляет нас от необходимости периодически добавлять новые языки в кластер. Все это возможно благодаря кастомному icu-токенизатору, который создает гораздо больше токенов, нежели стандартный. Одним из примеров поведения стандартного токенизатора является удаление всех знаков препинания и символов валюты, в то время как нам нужно сохранить их как отдельные токены, чтобы их можно было включать в запросы.
У токенизации есть одна особенность: после того, как документ токенизирован и сохранен на диске, его нелегко изменить. Любое нововведение потребует полной переиндексации всех данных, а учитывая огромное количество документов, это может занять месяцы!
Главный вопрос, который заботил нас во время обновления — любые изменения не должны иметь побочных эффектов. Для этого сначала были разработаны глобальные автоматизированные параметрические тесты для сравнения старой и новой реализации. Затем, с появлением второго кластера, работающего параллельно, реальные запросы клиентов начали перебрасываться на новую конфигурацию. Это позволило убедиться, что все изменения ведут к желаемому результату.
Нормализация символов
Старый кластер появился в 2013 году. С тех пор и Meltwater, и окружающий мир значительно изменились. Датасет не только значительно вырос и включает гораздо больше языков, но и сами тексты теперь содержат гораздо больше различных символов. Одна из причин этого — индексация значительных объемов информации из социальных сетей с их более короткими и в то же время более «творческими» и выразительными посланиями.
Как это обычно бывает, клиенты мечтают о двух взаимоисключающих вещах: хотят получать вообще ВСЕ, что соответствует их запросу (идеальный охват), но в то же время требуют ТОЛЬКО тех совпадений, которые точно соответствуют их запросу (идеальная точность).
Для этого им приходится писать громадные и максимально подробные запросы в надежде получить все совпадения независимо от языка, кодировки, окончаний, орфографических ошибок и способов написания.
Совершенствование нормализации символов — одна из областей, где, по нашему мнению, пользователям можно реально помочь. Для этого в пайплайн индексирования было решено внедрить нормализаторы. Ожидалось, что они повысят охват, при этом сохраняя высокую избирательность там, где это необходимо.
Здесь, естественно, требовалось определенная осторожность, поскольку нормализация символа фактически исключает возможность поиска по нему (также его не получится исключить из поиска). Поскольку приходится работать со всеми языками мира, становится практически нереально узнать значение определенного символа на определенном языке. Например, швед не увидит ничего страшного в нормализации é
в e
, в то время как француз, скорее всего, с такой идеей не согласится!
Нормализация апострофов
Были опрошены пользователи, сотрудники службы поддержки и отдела продаж и собраны их пожелания. Затем был составлен конкретный список известных проблем, которые необходимо решить. Одной из самых распространенных были апострофы. Например:
John’s
и
John´s
считаются разными терминами, поскольку символы апострофа разные. Поиск по одному из них не найдет фразы с другим. Пользователям это доставляло массу хлопот и означало кучу пропущенных результатов. Попытки бороться с этим с помощью подстановочных знаков делали поиск медленным и непредсказуемым.
Конечно, одним из решений стало бы полное удаление всех апострофов в индексе и в запросах, но это привело бы к другим проблемам. Иногда поиск должен вестись с учетом апострофов; John’s
и Johns
— разные термины, и в некоторых случаях (например, для названий брендов) эта разница носит решающий характер.
Поэтому вместо этого были выделены все варианты апострофов, встречающиеся в документах:
[ ʹ ʻ ʼ ʽ ʾ ʿ ΄ ᾿ Ꞌ ꞌ ' ‘ ’ ‛ ′ ´ ` ` ']
и добавлен фильтр, который заменяет их на ‘
.
Нормализация полноширинных символов
Нормализация также коснулась так называемых полноширинных (full-width) латинских символов. Эти символы — особенность Юникода. Они специально разработаны, чтобы лучше сочетаться с символами из других языков, таких как китайский, японский или корейский, и имеют приблизительно квадратную форму.
Другими словами, для полного охвата приходилось бы писать поисковый запрос с включением сразу обеих версий, например:
Meltwater OR Meltwater
И наоборот, некоторые японские символы специально уменьшаются, чтобы соответствовать латинским буквам. Они, соответственно, называются символами «половинной ширины» (half-width).
Фильтр, настроенный на нормализацию двух этих диапазонов символов Unicode, позволяет осуществлять поиск без необходимости добавлять дополнительное OR-выражение.
Пользователи положительно оценили оба этих новшества! Теперь можно было значительно сократить длину запросов и время, необходимое для их построения. Кроме того, сократилось время поиска.
Вообще говоря, двух этих новшеств хватило, чтобы вызвать интерес к обновлению в отделах продаж и поддержки.
Символы в CJK-языках
Воодушевленные успехом нормализации, мы обратились к обработке языков, основанных на логограммах. А именно, CJK-языков (CJK — собирательный термин для китайского, японского и корейского языков).
Прежде всего необходимо пояснить, что подразумевается под термином «логограмма» (и его производными) в лингвистическом контексте. Логография — форма письма, в которой каждый символ представляет собой полное слово, в отличие от фонографии, где каждый символ представляет собой звук. Английский и другие языки, основанные на латинских символах, являются фонографическими, в то время как CJK-языки — видные представители логографического семейства.
Когда дело доходит до токенизации — задачи по превращению текста в доступные для поиска токены — у фонографических языков есть преимущество. Слова, как правило, легко выделяются благодаря разделителям, таким как пробелы. С другой стороны, в CJK-языках любой символ может быть словом, но и комбинация символов также может означать слово.
Например, «сова» в китайском языке пишется как 貓頭鷹
и состоит из иероглифов «кошка» + «голова» + «орел».
Старый CJK-алгоритм индексировал каждый иероглиф отдельно, то есть поиск на китайском языке по термину «кошка» выдавал в том числе результаты для «сова». То есть чтобы получить результаты для «кошки», приходилось добавлять несколько фильтров для удаления нерелевантных совпадений.
Алгоритм с полным словарем позволил упростить процесс — комбинации теперь индексировались не как отдельные слова, а по смыслу.
Однако после выката этого решения и его тестирования на реальных данных выяснилось, что клиенты предпочитают старый способ работы с CJK-текстами. Они уже набили руку в создании фильтров данного типа, и более обширный список результатов, возвращаемый старой реализацией, оказался ценнее, нежели возможность писать более короткие запросы. От этой оптимизации было решено отказаться.
Выводы по итогам проведенных оптимизаций
Вот несколько важных уроков, которые мы вынесли из опыта:
Чтобы удовлетворить требования клиентов предсказуемым и понятным образом, важно иметь полное представление о процессе анализа и токенизации.
Запускайте решение в работу как можно раньше и учитывайте отзывы пользователей.
Иногда даже самое технически элегантное решение может не соответствовать потребностям конечного пользователя.
Большое количество автоматизированных тестов помогает убедиться в обратной совместимости.
Как команда бэкенд-разработчиков, мы редко взаимодействуем с реальными пользователями. К счастью, у нас есть палочка-выручалочка — глобальная служба поддержки, которая помогает в тестировании и дает ценные советы. Без их помощи мы бы наделали куда больше ошибок по типу той, о которой шла речь выше.
На этом заканчивается четвертая часть серии публикаций об обновлении кластера Elasticsearch. Следите за обновлениями: очередная статья будет опубликована на следующей неделе.
Чтобы оставаться в курсе событий, подписывайтесь на нас в Twitter или Instagram.
P.S.
Читайте также в нашем блоге: