Внешний API тормозит сайт: таймауты, повторы и очереди
Внешний API может быть полезной частью сайта: отправка заявки в CRM, проверка оплаты, получение остатков, расчёт доставки. Но если запрос к API выполняется прямо во время открытия страницы или отправки формы, медленный внешний сервис начинает тормозить весь сайт.
На практике интеграцию нужно строить так, чтобы сайт не зависел полностью от чужой скорости. Даже хороший API иногда отвечает долго, отдаёт 500 или временно недоступен.
Сначала измерить время
Перед изменениями нужно понять, сколько реально занимает внешний запрос. Не по ощущениям, а по логам.
$startedAt = microtime(true);
try {
$response = $client->send($request);
} finally {
Yii::info([
'service' => 'crm',
'duration' => round(microtime(true) - $startedAt, 3),
], 'external-api');
}
Если запрос занимает 3-5 секунд в форме, пользователь уже воспринимает сайт как зависший. Если запросов несколько, задержка складывается.
Настроить timeout
Запрос без таймаута опасен. Он может подвесить PHP-процесс надолго и постепенно занять все воркеры.
$client = new \yii\httpclient\Client([
'transport' => 'yii\httpclient\CurlTransport',
]);
$response = $client->createRequest()
->setMethod('POST')
->setUrl('https://api.example.ru/orders')
->setData($data)
->setOptions([
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_TIMEOUT => 10,
])
->send();
connect timeout отвечает за время подключения, общий timeout — за весь запрос. Значения зависят от задачи, но бесконечное ожидание почти всегда плохо.
Не делать тяжёлый API-запрос в пользовательском запросе
Если действие не обязано завершиться прямо сейчас, его лучше вынести в очередь. Например, форму можно принять быстро, сохранить заявку в базу и отправить её во внешний сервис в фоне.
$order = new Order();
$order->load(Yii::$app->request->post());
$order->status = Order::STATUS_NEW;
$order->save();
Yii::$app->queue->push(new SendOrderToCrmJob([
'orderId' => $order->id,
]));
Пользователь получает быстрый ответ, а интеграция обрабатывается отдельно. Если API временно недоступен, заявку можно повторить позже.
Повторные попытки
Retry полезен, но его нельзя делать без ограничений. Три быстрых повтора прямо в форме могут только увеличить задержку. Лучше повторять в фоне с паузами.
for ($attempt = 1; $attempt <= 3; $attempt++) {
try {
// запрос к API
break;
} catch (\Throwable $e) {
Yii::warning([
'attempt' => $attempt,
'error' => $e->getMessage(),
], 'external-api');
sleep($attempt * 2);
}
}
Для очередей лучше хранить количество попыток и последнюю ошибку, чтобы потом было понятно, что происходило.
Кэшировать справочные данные
Если API отдаёт справочник городов, тарифы или статусы, не нужно запрашивать это при каждом открытии страницы. Такие данные часто можно кэшировать на несколько минут или часов.
$key = ['delivery-tariffs'];
$tariffs = Yii::$app->cache->get($key);
if ($tariffs === false) {
$tariffs = $service->loadTariffs();
Yii::$app->cache->set($key, $tariffs, 3600);
}
Важно понимать срок жизни данных. Остатки товара могут устаревать быстро, а список регионов — редко.
Безопасная деградация
Если API недоступен, сайт должен вести себя предсказуемо. Например, можно сохранить заявку со статусом “ожидает отправки”, показать пользователю обычное сообщение и не терять данные.
try {
$crm->sendOrder($order);
$order->crm_status = 'sent';
} catch (\Throwable $e) {
$order->crm_status = 'pending';
Yii::error($e->getMessage(), 'crm');
}
$order->save(false);
Главное — не делать так, чтобы временная ошибка внешнего сервиса приводила к потере заявки.
Логи и идентификаторы
В логах нужно хранить ID локальной сущности и краткую ошибку. Не стоит писать токены, пароли и полные персональные данные.
Yii::info([
'order_id' => $order->id,
'external_id' => $externalId,
'status_code' => $response->statusCode,
], 'external-api');
Чек-лист
- Измерить время внешнего запроса.
- Настроить connect timeout и общий timeout.
- Не выполнять необязательный API-запрос в пользовательском ожидании.
- Вынести отправку в очередь, если это возможно.
- Настроить ограниченные повторные попытки.
- Кэшировать справочные данные.
- Сохранять локальные данные до внешней отправки.
- Логировать ID, статус и ошибку без секретов.
Внешний API всегда остаётся внешним. Его нельзя полностью контролировать, но можно сделать так, чтобы его задержки и сбои не останавливали сайт и не приводили к потере данных.
Комментарии (0)
Пока нет комментариев. Будьте первым!