Приведите конкретный пример из практики, когда вы значительно улучшили производительность REST API.

Ответ

Конечно. Вот пример оптимизации эндпоинта в проекте, похожем на маркетплейс.

Задача: Оптимизировать эндпоинт GET /users/{id}/orders, который возвращал список заказов для конкретного пользователя.

Проблема: Изначальная реализация страдала от проблемы "N+1 запросов". На один запрос к API происходило:

  1. Один запрос к БД для получения данных пользователя.
  2. N запросов к БД в цикле для получения каждого из его заказов.

Это приводило к огромному количеству обращений к базе данных и высокому времени ответа (~1200ms для пользователя со 100 заказами).

Решение (в 3 шага):

  1. Устранение N+1 с помощью JOIN: Запрос был переписан для получения всех необходимых данных за одно обращение к БД. Это позволило избежать цикла и множества запросов.

    -- Вместо множества запросов теперь один
    SELECT o.*, p.name, p.price -- и другие поля заказа и товаров
    FROM orders o
    JOIN order_items oi ON o.id = oi.order_id
    JOIN products p ON oi.product_id = p.id
    WHERE o.user_id = $1;
  2. Добавление пагинации: Чтобы не загружать тысячи заказов одновременно, была добавлена серверная пагинация (LIMIT/OFFSET). Клиент стал получать данные порциями.

    -- Добавляем к запросу выше
    LIMIT $2 OFFSET $3;
  3. Кеширование: Для часто запрашиваемых данных было внедрено кеширование на уровне приложения с использованием Redis. Ответы для активных пользователей кешировались на 1-2 минуты, что еще больше снизило нагрузку на БД.

Результат:

  • Время ответа эндпоинта сократилось с ~1200ms до ~80ms.
  • Нагрузка на базу данных значительно снизилась, что повысило общую стабильность сервиса.

Ответ 18+ 🔞

Ну, смотри, как мы тут один эндпоинт на маркетплейсе из говна и палок делали. Задача была простая, казалось бы: GET /users/{id}/orders. Ну, отдай заказы пользователя, че тут думать-то?

А оказалось, блядь, там под капотом такая дичь творилась, что волосы дыбом встают. Классический "N+1 запрос", ёпта! Представь: приходит запрос, мы лезем в базу за юзером. Окей. А потом, сука, в цикле начинаем для КАЖДОГО его заказа по отдельности лезть в базу за данными! Это ж пиздец какой-то! Если у чувака 100 заказов, то это 1 + 100 = 101 запрос к базе. И эндпоинт тупит, как пень, 1200 миллисекунд отвечает. Пользователь уже успевает сходить, кофе сварить, вернуться, а у него ещё спиннер крутится.

Ну, мы не дураки, сели и подумали. Решили по-взрослому.

Шаг первый, самый главный: выпиливаем этот N+1 нахуй. Вместо этой ебалы с циклами, пишем один нормальный, адекватный запрос с JOIN. Пусть база сразу всё отдаёт, одним махом, как швейцарские часы.

SELECT o.*, p.name, p.price
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.user_id = $1;

Вот так, блядь. Из 101 запроса сделали один. Уже хорошо.

Шаг второй: пагинация. А че, собственно, мы 10 000 заказов разом-то грузим? А если у него их миллион? Сервер сдохнет, а пользователю первые 10 всё равно. Добавили LIMIT и OFFSET. Клиент теперь порциями данные жрёт, всё цивилизованно.

LIMIT $2 OFFSET $3;

Шаг третий, для полного счастья: кеширование. Ну а че? Если какой-то активный пользователь каждые 10 секунд свои заказы дергает, зачем нам каждый раз в базу лезть? Поставили Redis, ответ на пару минут в кеш пихаем. Следующий запрос — бац, и данные уже из памяти, моментально.

И что в итоге, спросишь ты? А в итоге мы с 1200ms спустились до ~80ms. Представляешь? В 15 раз быстрее, блядь! Нагрузка на базу упала, сервис перестал хрипеть, все довольны. Вот так вот, казалось бы, мелочь, а из-за кривых рук и непонимания основ может всю систему в тапки запереть.