Перейти к основному содержимому

Код ответа 499 в Serverless-функциях

· 6 мин. чтения

В логах Serverless-функций вы можете увидеть код ответа 499. Что это значит и что делать, если вы видите его в логах?

Что означает код ответа 499?

Это не стандартный код ответа HTTP. Вы не найдете его в спецификации HTTP/1.1 в секции 6.5, где перечислены 4xx коды ответа. Потому что он был введен в Nginx и используется для обозначения таких случаев.

Код ответа 499 означает, что клиент закрыл соединение до того, как сервер успел отправить ответ. Это может произойти, например, если клиент отключился по таймауту или из-за ошибки. При этом клиентом может быть как браузер, так и другой сервер, который делает запрос к вашей функции, например, Яндекс.Диалоги или Telegram.

Отдельно стоит отметить, что код ответа 499 может появляться и в случае, когда одна serverless-функция вызывает другую. В таком случае, если вызывающая функция закрывает соединение, то вызываемая функция завершится с кодом ответа 499.

Как исправить код ответа 499?

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

Тут может быть две стратегии исправления:

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

И если у вас нет возможности изменить клиентский код, то вам остается только модифицировать серверный код.

Стратегии исправления

И тут есть несколько вариантов:

  1. Уменьшить время обработки запроса. Например, оптимизировать код, уменьшить количество запросов к базе данных или кэшировать результаты.
  2. Использовать очереди. Если обработка запроса занимает много времени, то можно отправить его в очередь и обработать асинхронно.
  3. Использовать асинхронные запросы.
warning

Увеличивать время работы функции, где вы видите код ответа 499, бессмысленно.

Уменьшить время обработки запроса

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

При выполнении оптимизации времени ответа всё зависит от того где именно у вас тратится наибольшее количество времени. Поэтому исправления необходимо начать с профилирования кода и выявления узких мест.

Далее, в зависимости от того, что именно у вас тормозит, можно применить разные методы оптимизации:

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

В принципе, кроме нюанса с выделением ресурсов, перечисленные методы оптимизации не являются специфичными для Serverless-функций.

Отправка запроса в очередь или стрим

Этот метод исторически появился в Облаке раньше, чем асинхронные запросы, поэтому я расскажу о нем первым.

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

Преимущества:

  • Уменьшение времени ответа. Поскольку запрос обрабатывается асинхронно, клиенту не нужно ждать ответа.
  • Увеличение отказоустойчивости. Если функция упала, то запрос не потеряется, а будет обработан позже, так как он будет возвращен в очередь. Если же запрос не может быть обработан, например, из-за ошибки в коде, то и в этом случае он не будет потерян, а будет помещен в очередь ошибочных запросов (DLQ — dead-letter queue).

Недостатки:

  • Невозможность вернуть ответ сразу. Если клиенту нужен ответ сразу, то этот метод не подойдет. Клиенту нужно будет либо самому обращаться за результатом выполнения операции, либо вам придется использовать WebSockets, чтобы нотифицировать клиента о завершении операции.
  • Сложность отладки и тестирования. Поскольку запрос обрабатывается асинхронно, то отладка может быть сложнее, чем при синхронной обработке.
  • Не контроля над порядком обработки. Поскольку запросы обрабатываются асинхронно, то порядок обработки не гарантирован. Если вам важен порядок обработки, то вам придется использовать Yandex Data Streams, который гарантирует порядок обработки. Важно, что порядок гарантируется в рамках одной партиции, поэтому важно, правильно формировать ключи партиционирования.
  • Нет контроля над кодом и телом ответа. API Gateway в ответ на запрос в очередь возвращает 200 OK с пустым телом. Вы можете обойти это ограничение, если реализуете свою функцию обработчик, которая будет класть сообщения в очередь.

Асинхронные запросы

Это самое свежее решение, которое появилось в Облаке.

Не нужно путать это с асинхронными обработчиками в код функции. Асинхронные обработчики, как например async function handler(event, context) { ... }, позволяют вам пользоваться средствами асинхронности предоставляемыми языком программирования. Однако, если запрос приходит синхронно, то выполнение функции будет прервано в момент когда будет потеряно соединение с клиентом и в логах вы увидите ту самую 499.

Асинхронные запросы же позволяют вам отложить выполнение функции на потом. Ответ на запрос будет возвращен сразу с кодом 202 Accepted. Тело запроса помещается в очередь и при вызове функции передается в качестве аргумента.

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

Минусом можно назвать то, что на текущий момент API Gateway не может вызвать функцию асинхронно. Для вызова функции API Gateway использует запрос с параметром ?integration=raw, а для асинхронного вызова функции нужно использовать ? integration=async. Выходом может быть вызов асинхронной функции из другой функции, которая будет вызываться синхронно API Gateway'ем. Такой подход даст вам большую гибкость в управлении кодами ответа и телами ответов.

Заключение

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

Еще по теме: