В Loopio мы в значительной степени полагаемся на офлайн-эксперименты для проверки новых гипотез, которые могут помочь нам улучшить поиск для наших пользователей. Когда эксперимент оказывается успешным, наша цель состоит в том, чтобы как можно скорее запустить его в производство, чтобы мы могли собрать информацию с помощью A/B-тестирования на подмножестве наших пользователей.

Узнайте все подробности о нашей автономной экспериментальной среде и о том, как мы используем машинное обучение (ML) для улучшения понимания запросов, в Часть 1 и Часть 2 нашей серии Поиск.

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

Мы решили инвестировать в новый поисковый микросервис, поскольку решения машинного обучения обычно разрабатываются на Python, а наша устаревшая система была на PHP. Целью этих инвестиций было создание более производительного сервиса, который значительно упрощает интеграцию с машинным обучением. В этом сообщении блога мы поделимся тем, как выглядит эта новая архитектура, и некоторыми нашими знаниями.

Ограничения устаревшей архитектуры

В устаревшей архитектуре наше основное веб-приложение связано с Elasticsearch для выполнения функций поиска. Elasticsearch — невероятно мощный инструмент для индексации и поиска данных, но мы обнаружили, что по мере роста объемов данных производительность индексирования стала становиться узким местом. Всякий раз, когда происходили изменения в настройках индекса, все документы нужно было переиндексировать, охватывая несколько миллионов документов и занимая часы.

Поскольку существующее веб-приложение построено на PHP, который ограничен решениями и поддержкой ML, добавление моделей ML в процесс было громоздким:

  • Применять модели машинного обучения в рабочем процессе поиска будет сложно: необходимо будет извлечь результаты запроса Elasticsearch и передать их во внешний API, содержащий интегрированные модели машинного обучения. Результаты, которые возвращаются из API, затем будут отображаться в пользовательском интерфейсе.
  • Также было бы сложно обрабатывать индексирование: потребуется доступ к внешнему API, чтобы применить модели машинного обучения к данным перед повторным индексированием.

Определение требований к новому решению

Как описано в нашей предыдущей статье, мы в значительной степени полагаемся на эксперименты ML для поиска. Таким образом, основным требованием к нашей новой инфраструктуре была просто возможность быстро интегрировать машинное обучение в жизненный цикл поиска. Мы хотели иметь возможность использовать машинное обучение в три этапа:

  • Индексирование: когда документ индексируется, мы хотим извлечь ключевые фразы, идентифицировать объекты или часть речи, а также создать вложения, которые открывают возможность использования семантического поиска или возможность сопоставления документов. помимо ключевых слов
  • Запрос. Мы могли бы использовать понимание запроса или возможность расширить запрос с помощью синонимов, исправить орфографические ошибки или просто предсказать, где будет наиболее релевантный документ на основе поисковых запросов.
  • Пост-поиск. После извлечения набора документов мы можем повторно ранжировать их или даже извлечь точный ответ, который искал пользователь.

На рисунке ниже показано, как можно внедрить новую функцию на разных этапах поисковой системы.

Из-за наших вариантов использования индексирования нам также необходимо было поддерживать быстрое и высокопроизводительное индексирование, независимо от того, обрабатывается ли один документ или миллионы.

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

На основе этой работы мы решили построить наш сервис на основе следующего стека:

  • Elasticsearch по-прежнему будет нашей основной поисковой системой.
  • Поисковые запросы будут обслуживаться в новом API Python с использованием платформы FastAPI, которая считывает документы из Elasticsearch.
  • Индексирование документов в Elasticsearch будет выполняться с использованием событийно-ориентированного подхода на основе AWS Kinesis и Lambda.

API поиска: на основе FastAPI

FastAPI — это современный, быстрый и высокопроизводительный веб-фреймворк для создания API на Python. Мы решили использовать FastAPI в первую очередь из-за его производительности, параллелизма и простоты использования. API поиска работает следующим образом:

  • Основной конечной точкой этого API является конечная точка /search, которая принимает поисковый запрос и набор фильтров, выполняет запрос в эластичном поиске и возвращает результаты.
  • В настоящее время API использует модель машинного обучения для понимания запросов. Когда запрос получен, поисковый запрос сначала передается в модель для создания некоторых метаданных. Затем они используются в финальном запросе Elasticsearch DSL.
  • API также поддерживает пометку функций, что позволяет нам легко запускать эксперименты и выполнять A/B-тесты. Эксперименты можно легко включить или отключить, что позволяет нам проводить эксперименты в безопасной и контролируемой среде.
  • Новый API также упрощает использование моделей машинного обучения на этапе после извлечения данных.

Конвейер индексации поиска: на базе AWS Kinesis и Lambda

Наша загрузка данных индексирования является переменной, и мы не хотели, чтобы это влияло на поисковые запросы. Поэтому мы решили отделить функции индексирования от API поиска. Мы выбрали событийный подход. Когда документ изменяется на нашей платформе, он просто отправляется в очередь сообщений и немедленно обрабатывается.

Мы решили использовать Kinesis вместо других очередей, таких как SQS и Kafka, потому что он управляется AWS, может поддерживать больший объем данных и является экономически выгодным. SQS был нашим первоначальным выбором, но из-за ограничения размера сообщения в 256 КБ мы переключились на Kinesis.

При использовании Kinesis следует помнить о ключе раздела. Kinesis использует сегментирование для параллельной обработки событий, и в нашем случае нам нужно поддерживать случай, когда один и тот же документ обновлялся одновременно. Здесь помогает использование хорошего ключа раздела. Мы использовали идентификатор документа в качестве ключа раздела, что означает, что порядок обновлений будет поддерживаться в зависимости от того, какое событие было отправлено/принято первым.

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

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

Ниже представлена ​​упрощенная версия того, как выглядит новая архитектура.

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

Основные выводы из создания поискового микросервиса

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

  • Установка соглашений об уровне обслуживания (SLA) и тестирование.Перед развертыванием службы в первый раз запустите несколько тестов производительности и определите SLA, определяющее, как долго должен выполняться поисковый запрос или сколько времени требуется для полной индексации обновлений. . Это важно, потому что мы не хотим, чтобы добавление новых моделей ухудшало качество поиска. Каждое изменение в индексе Elasticsearch или введение новой модели следует сравнивать с текущим соглашением об уровне обслуживания.
  • Используйте флажки функций. Флаги функций — отличный способ снизить риск, поскольку они позволяют легко включать и выключать функции. Флаги функций также можно использовать для постепенного развертывания изменений для клиентов или проведения онлайн-тестов A/B.
  • Учитывайте взаимодействие с пользователем. Переход от синхронных обновлений к асинхронным может повлиять на взаимодействие с пользователем, особенно в существующей системе, поскольку изменения могут быть видны не сразу. Очень важно помнить об этом при переходе на событийно-ориентированный подход.

Заключение

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

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

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