Оптимизация времени отклика 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. Однако в этой реализации мы видим следующие проблемы:
- Такие столбцы, как display_name и description, относятся к типу
VARCHAR
. - Использование оператора
LIKE
с предложением OR для столбцов типаVARCHAR
. ORDER BY
используется.- Составной индекс отсутствовал для всех столбцов, используемых в предложении
WHERE
. - Таблица содержала 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. Но поскольку данные в основном статичны, в существующую запись вносится очень мало обновлений, поэтому затраты на обслуживание очень низкие.
В этом посте подчеркивается важность выбора правильного ядра базы данных для реализации бизнес-варианта.
Удачного кодирования.