Оптимизация времени отклика API

Соавтор — Мохит Сегал, ГАРИМА МАХАДЖАН

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

Свет, камера…

Итак, мы начали с понимания того, что делает API и как он это делает.

Приложение хранит каталог книг и их авторов в базе данных MySQL. Насчитывается около 58 миллионов книг, и на одну книгу приходится 1 издательство.

Ниже приведены структуры таблиц для книги и автора.

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

Запрос MySQL, который использовался в API, выглядел следующим образом:

select book_uuid_bin, 
       display_name, 
       normalized_name, 
       description,       
       author_uuid_bin 
from book
where 
     ((lower(display_name) like lower("%Software E%") or  lower(description) like lower("%Software E%")) 
and publishing_house_uuid_bin = UUID_TO_BIN("d2230981-e570-5ba4-9a3a-16028c51d54f"))
order by display_name asc limit 100;

SQL-запрос требовал ~70 seconds для запроса таблицы базы данных, даже если запрос был выполнен для одной таблицы, а соединение с таблицей автора не выполнялось.

У нас были индексы для столбцов, которые использовались в предложении where. Однако в этой реализации мы видим следующие проблемы:

  1. Такие столбцы, как display_name и description, относятся к типу VARCHAR.
  2. Использование оператора LIKE с предложением OR для столбцов типа VARCHAR.
  3. ORDER BY используется.
  4. Составной индекс отсутствовал для всех столбцов, используемых в предложении WHERE.
  5. Таблица содержала 58 миллионов записей.

Мы попытались создать составной индекс для столбцов, используемых в запросе, но знали, что это не поможет, поскольку поиск текста в столбце VARCHAR неэффективен для огромной таблицы в базе данных СУБД.

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

Amazon OpenSearch Service — это управляемый сервис, упрощающий развертывание, эксплуатацию и масштабирование кластеров OpenSearch в облаке AWS. Amazon OpenSearch Service является преемником Amazon Elasticsearch Service.

И действие…

Мы использовали скрипт для загрузки данных таблицы из MySQL в кластер AWS OpenSearch. Миграция данных была завершена за пару часов.

Мы сохранили 5 осколков для нашего индекса и 1 фактор репликации.

Мы написали эквивалентный запрос OpenSearch для нашего варианта использования, как показано ниже:

API — POST /books-catalog/_search

Результат -

Наш API работал менее 70 мс.

Это было 1000-кратное улучшение времени отклика API!

Некоторые подробности о полнотекстовом поиске OpenSearch

AWS OpenSearch использует анализатор текста для полей типа String, когда документы индексируются (создаются) в Elastic Search.

Анализатор текста разбивает поле String на токены, строит внутренние индексы для токенов, а затем применяет сопоставление на основе токенов, предоставленных в запросе.

Компромисс

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

Компромисс с этой скоростью - дополнительное обслуживание. Теперь нам нужно сохранить копию данных в OpenSearch. Но поскольку данные в основном статичны, в существующую запись вносится очень мало обновлений, поэтому затраты на обслуживание очень низкие.

В этом посте подчеркивается важность выбора правильного ядра базы данных для реализации бизнес-варианта.

Удачного кодирования.