ОБЛАКА: AWS Serverless

Обязательно

Serverless в AWS: Lambda, события и cold start

Lambda · API Gateway · Step Functions

Событийная модель, холодный старт, синхронные и асинхронные вызовы и оркестрация.

1. Lambda выполняет функцию по событию

1 / 5

HTTP, SQS, EventBridge или расписание вызывает код. AWS выделяет среду и масштабирует выполнение.

  • Нет управления серверами.
  • Плата за запуск и время.
  • Состояние хранится вне функции.
EventLambdaResult + billingСерверы не видны приложению

2. Холодный старт создаёт среду заранее не готовую

2 / 5

Новый экземпляр требует инициализации runtime, зависимостей и клиентов. Тёплый экземпляр пропускает её.

  • Provisioned concurrency держит готовые.
  • Маленький пакет ускоряет запуск.
  • Общие клиенты инициализируются один раз.
Cold startinit + invokeWarm startinvoke onlyProvisioned concurrency убирает холод

3. API Gateway создаёт HTTP-контракт

3 / 5

Маршрут связывает метод и путь с Lambda. Авторизация, лимиты и ошибки описываются на границе.

  • Синхронный ответ имеет дедлайн.
  • ProblemDetails един.
  • Rate limit защищает функцию.
HTTP clientAPI GatewayLambdaResponse

4. Асинхронное событие повторяется

4 / 5

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

  • Consumer дедуплицирует eventID.
  • Visibility timeout соответствует работе.
  • Долгая задача идёт async.
QueueLambda retryDLQDedup

5. Step Functions управляют шагами

5 / 5

Оркестратор хранит состояние, повторяет шаги и передаёт результат. Долгий workflow не живёт в одном вызове.

  • Шаги имеют вход и выход.
  • Ошибка выбирает retry или catch.
  • Состояние видно эксплуатации.
ValidateChargeReserveNotifyОркестратор хранит состояние

Слово serverless («бессерверный») звучит так, будто серверов нет вообще. Это не так — серверы есть, просто вы их не видите и не управляете ими. AWS сам поднимает машину, когда нужно выполнить ваш код, и гасит её, когда работа закончена. Вы не патчите операционную систему, не настраиваете масштабирование, не платите за простаивающие инстансы — платите только за фактическое время, пока код работал.

Главный кирпич serverless в AWS — это Lambda: сервис, в который вы загружаете функцию (кусок кода), а AWS запускает её в ответ на событие. Такой подход часто называют FaaS — Function as a Service, «функция как сервис». Если вы ещё не разбирались с тем, какие вообще бывают варианты запуска кода в облаке, полезно сперва заглянуть в обзор вычислений AWS — там Lambda стоит рядом с виртуальными машинами и контейнерами. Здесь же разберём, как serverless устроен изнутри и когда его стоит брать.

синхронно шлюз API функция ошибка вызывающему асинхронно файл в S3 внутренняя очередь функция до трёх попыток адресат отказа

Два пути вызова ведут себя по-разному при отказе: синхронный вызов отдаёт ошибку тому, кто звал, и повторы его забота; асинхронное событие AWS ставит в свою очередь и повторяет сама, а после исчерпания попыток отправляет в очередь недоставленных или адресат отказа, если его настроили.

Событийная модель

Обычный сервер — это программа, которая постоянно запущена и ждёт запросов. Lambda устроена наоборот: код «спит», пока не случилось событие (event) — что-то, на что функция должна отреагировать. Произошло событие — AWS запускает функцию, она отрабатывает и завершается. Нет событий — нет запущенного кода и нет счёта за работу.

Источников событий много, и в этом сила модели. Несколько типичных:

  • HTTP-запрос от пользователя — приходит через API Gateway (о нём ниже);
  • новое сообщение в очереди SQS;
  • загрузка файла в хранилище S3 (например, картинку залили — функция сделала превью);
  • запись изменилась в базе DynamoDB (через DynamoDB Streams);
  • наступило время по расписанию (раз в час, каждую ночь);
  • событие пришло на шину EventBridge от другого сервиса.

Из этой модели следует ключевое свойство — автоматическое масштабирование. Пришёл один запрос — AWS запустил одну копию функции. Пришла тысяча запросов одновременно — он запустит тысячу копий параллельно. Тысяча тут не случайная цифра, а ровно потолок по умолчанию: столько одновременных выполнений даётся на аккаунт в регионе — сразу на все ваши функции. Потолок поднимают по заявке, но знать про него надо заранее, потому что упираются в него первым делом. Вам не нужно настраивать группы автомасштабирования и балансировщики, как для обычных серверов (это подробно разобрано в статье про масштабирование и доступность). Поэтому неровная, «скачущая» нагрузка — когда то пусто, то густо — это родная стихия Lambda.

Что происходит, когда функция упала

Половина эксплуатации функций — это обработка отказов, и правила здесь разные для синхронного и асинхронного вызова. Это первое, что нужно развести.

Синхронный вызов (шлюз API, прямой вызов, балансировщик): ошибка возвращается вызывающему, и повторы — его забота. Сама AWS ничего не повторяет.

Асинхронный вызов (событие из хранилища объектов, уведомление, шина событий, прямой вызов с признаком «не жди ответа»): AWS ставит событие во внутреннюю очередь и повторяет сама. По умолчанию — два дополнительных раза, то есть до трёх попыток всего, с паузой между ними (минута, потом две). Настраивается это двумя параметрами: число повторов (от 0 до 2) и максимальный возраст события (до 6 часов — старше не повторяется вовсе).

Отсюда сразу два следствия, которые дорого узнавать в проде.

Обработчик обязан быть идемпотентным. Повтор — это штатное поведение, а не аварийный случай. Хуже того: событие может прийти дважды и без всякой ошибки — гарантия доставки «хотя бы один раз». Значит, «создать заказ» или «списать деньги» в функции защищают ключом операции или уникальным индексом, как разобрано в статье про идемпотентность (Java, Go, Node, Python).

Неудавшееся событие надо куда-то девать. Если все попытки исчерпаны, событие по умолчанию просто исчезает. Чтобы этого не было, настраивают один из двух механизмов:

  • Очередь недоставленных (DLQ) — старый механизм: указываете очередь или тему, и туда попадает само событие (без причины отказа).
  • Адресаты (Lambda Destinations) — современный вариант: отдельно задаётся, куда отправить результат при успехе и куда при отказе, а в сообщение попадает и событие, и причина, и число попыток. Для разбора это несравнимо полезнее.
aws lambda put-function-event-invoke-config --function-name orders-import \
  --maximum-retry-attempts 2 --maximum-event-age-in-seconds 3600 \
  --destination-config '{"OnFailure":{"Destination":"arn:aws:sqs:eu-central-1:123456789012:orders-import-failed"}}'

И главное правило, которое из этого вытекает: у очереди недоставленных должен быть читатель и сигнал. Пустая очередь — это норма; сообщение в ней — событие, на которое кто-то должен посмотреть в тот же день, иначе через месяц там будет двести сообщений и никто не знает, какие из них важны.

Очередь как источник: время видимости

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

Когда функция берёт сообщение, очередь делает его невидимым для других читателей на заданное время (visibility timeout). Если функция успела обработать и подтвердить — сообщение удаляется. Если не успела — сообщение снова становится видимым, и его получит ещё один экземпляр функции.

Поэтому время видимости обязано быть не меньше таймаута функции. Иначе картина такая: функция работает 40 секунд, время видимости 30 — на тридцатой секунде сообщение выдаётся второму экземпляру, и оба делают одну работу. Ошибок в журнале нет, всё «работает», а письма уходят дважды.

Рабочее правило — время видимости в шесть раз больше таймаута функции (так рекомендует и сама документация): запас на повторные попытки внутри пачки и на задержку самой платформы. И у очереди тоже настраивают очередь недоставленных с числом попыток: без неё сообщение, которое всегда ломает обработчик, будет ходить по кругу до истечения срока хранения.

Ещё три детали про этот источник. Функция получает сообщения пачкой (до десяти по умолчанию, настраивается), и отказ на одном сообщении по умолчанию возвращает в очередь всю пачку — это лечится ответом с частичным отказом (ReportBatchItemFailures), где функция перечисляет, какие именно сообщения не удались. Одновременность растёт сама по мере роста очереди, и её ограничивают, чтобы не убить базу за очередью. А FIFO-очередь обрабатывается по группам: порядок внутри группы сохраняется, и параллелизм равен числу групп, а не числу сообщений.

Cold start

У событийной модели есть обратная сторона, и про неё спрашивают чаще всего — это cold start («холодный старт»).

Разберём механику. Когда функция отработала, AWS не убивает её мгновенно — он держит «тёплую» среду выполнения какое-то время, чтобы следующий вызов был быстрым (это называется warm start, «тёплый старт»). Но если вызовов долго нет, среда «остывает» и удаляется. И тогда следующий вызов требует поднять всё заново: выделить машину, скачать ваш код, запустить рантайм (среду исполнения языка), выполнить инициализацию — подключения к базе, чтение настроек. Вся эта подготовка добавляется к времени ответа первого запроса. Вот эта добавка и есть cold start.

Аналогия: тёплый старт — это машина, которая уже заведена и едет. Холодный старт — это когда машина стояла всю ночь на морозе, и сперва её нужно завести и прогреть. Едет она потом одинаково, но первая поездка после простоя дольше.

Как с этим борются:

  • Provisioned concurrency — заранее держать заданное число «тёплых» функций наготове. Они не остывают, поэтому cold start пропадает — но за эту готовность приходится платить, даже когда вызовов нет.
  • Меньше зависимостей и легче пакет — чем меньше кода и библиотек нужно загрузить и проинициализировать, тем быстрее старт.
  • SnapStart — функция инициализируется один раз, AWS делает «снимок» (snapshot) готовой среды и потом запускает копии из него, минуя долгую инициализацию. Доступно это для Java, Python и .NET. Для Java денег не берут вовсе; за Python и .NET AWS просит плату за хранение снимка и за каждое восстановление из него — но это всё равно дешевле, чем круглосуточно держать прогретые экземпляры. И учтите: вместе с provisioned concurrency SnapStart не включается, средства взаимоисключающие.
  • Лёгкий рантайм — у одних языков среда исполнения стартует за миллисекунды, у других «тяжёлых» рантаймов старт самого окружения заметно дольше. Для синхронных API с жёсткими требованиями к скорости ответа это учитывают при выборе языка.

Важный практический вывод: cold start вполне терпим для фоновой и событийной обработки (никто не заметит, если разбор файла начнётся на полсекунды позже), но неприятен для синхронного API, где пользователь ждёт ответа прямо сейчас. Это один из главных критериев, брать Lambda под задачу или нет.

Лимиты Lambda

У функции есть жёсткие ограничения, которые важно знать заранее, чтобы не упереться в них в самый неподходящий момент:

  • Время выполнения. Задача обрывается на пятнадцатой минуте, а повтор начинает её заново с нуля: потолок — 15 минут (900 секунд) на один вызов, и поднять его нельзя. Долгие задачи на Lambda просто не помещаются.
  • Память — до 10 240 МБ на функцию. Причём процессорная мощность привязана к памяти: чем больше памяти выделили, тем больше vCPU функция получит. Иногда добавляют памяти не ради неё самой, а чтобы ускорить вычисления.
  • Одновременные выполнения — 1000 на аккаунт в регионе по умолчанию, на все функции сразу. Наращивается тоже не мгновенно: каждая функция прибавляет до тысячи экземпляров за десять секунд.
  • Размер ответа — 6 МБ при синхронном вызове и 1 МБ при асинхронном. Отдать из функции большой файл не выйдет: его кладут в хранилище и возвращают ссылку.
  • Размер пакета с кодом — 50 МБ архивом и 250 МБ в распакованном виде. Если библиотеки не влезают, функцию собирают образом контейнера — там потолок 10 ГБ.
  • Временный диск /tmp — от 512 МБ (по умолчанию) до 10 ГБ. Это единственное место, куда функция может что-то записать, и живёт оно ровно столько, сколько живёт экземпляр.

Эти лимиты — ещё одна причина не пытаться засунуть в Lambda всё подряд. Если задача идёт дольше 15 минут или требует постоянной тяжёлой работы — это сигнал смотреть на контейнеры или виртуальные машины.

API Gateway и Step Functions

Lambda редко живёт в одиночку. Два сервиса дополняют её чаще всего.

API Gateway — это управляемый «вход» для HTTP-запросов перед вашими функциями. Своего веб-сервера у Lambda нет, но собственный адрес в интернете она получить умеет: это называется Lambda Function URL — включается одной настройкой, доступ закрывается правами IAM или остаётся публичным. Для простого вебхука или внутренней ручки этого хватает, и шлюз не нужен вовсе.

API Gateway берут, когда нужно больше: свой домен вроде https://api.example.com/orders, проверка авторизации, ограничение частоты обращений (rate limiting), разные маршруты на разные функции. Он принимает HTTP-запрос, направляет его нужной функции и отдаёт ответ обратно клиенту. Так из набора функций получается полноценный REST API.

Простейший вызов функции через AWS CLI выглядит так:

aws lambda invoke --function-name create-order --cli-binary-format raw-in-base64-out --payload '{"item":"book"}' response.json

Флаг --cli-binary-format raw-in-base64-out здесь обязателен. В AWS CLI версии 2 (она ставится по умолчанию) параметр --payload ожидает данные в base64, и если передать сырой JSON без этого флага, команда упадёт с ошибкой Invalid base64. Флаг говорит CLI принять JSON как есть. Чтобы не дописывать его каждый раз, можно один раз выполнить aws configure set cli-binary-format raw-in-base64-out. В старой версии 1 команда работала и без флага — отсюда и старые примеры, которые на CLI 2 уже не запускаются.

Step Functions — это оркестратор многошаговых процессов. Представьте сценарий: «принять заказ, списать деньги, зарезервировать товар, отправить письмо, а если на каком-то шаге ошибка — откатить назад». Можно было бы написать это как цепочку функций, вызывающих друг друга, но такая самодельная логика быстро превращается в запутанный клубок, где непонятно, на каком шаге всё застряло.

Step Functions описывают процесс как машину состояний (state machine): явная последовательность шагов, ветвления («если успех — туда, если ошибка — сюда»), повторные попытки и паузы между шагами. Логика становится наглядной, а в консоли AWS видно, какой шаг сейчас выполняется и где что пошло не так. Описывается машина состояний декларативно, в формате JSON:

{
  "StartAt": "ChargePayment",
  "States": {
    "ChargePayment": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:123456789012:function:charge",
      "Retry": [
        {
          "ErrorEquals": ["States.TaskFailed"],
          "IntervalSeconds": 2,
          "MaxAttempts": 3,
          "BackoffRate": 2
        }
      ],
      "Next": "ReserveItem"
    },
    "ReserveItem": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:123456789012:function:reserve",
      "Catch": [
        {
          "ErrorEquals": ["States.ALL"],
          "Next": "RefundPayment"
        }
      ],
      "End": true
    },
    "RefundPayment": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:123456789012:function:refund",
      "End": true
    }
  }
}

Блок Retry — это те самые повторные попытки: три раза, с паузой, которая удваивается. Блок Catch — куда уходить, если шаг всё-таки упал. И сразу важное: «откатить назад» само по себе не происходит. Шаг RefundPayment — обычная функция, которая возвращает деньги; такое возмещающее действие на каждый шаг пишут руками, а Step Functions лишь гарантирует, что до него дойдут.

Таймаут шлюза: 29 секунд, а не 15 минут

Тонкость, из-за которой строят неработающие схемы. У функции потолок 15 минут — это правда. Но если её вызывают через шлюз API, действует потолок шлюза: 29 секунд на интеграцию. Дольше — клиент получает ошибку шлюза, хотя функция продолжает работать и досчитает задачу впустую.

Поднять это значение можно только запросом квоты и не во всех типах API, поэтому проектируют иначе: долгую работу принимают и отвечают сразу.

POST /reports              → 202 Accepted, { "id": "r-42", "status": "PENDING" }
GET  /reports/r-42         → { "status": "PENDING" | "DONE", "url": ... }

Функция за шлюзом только регистрирует задание и кладёт сообщение в очередь; вторая функция (уже без шлюза, а значит без 29 секунд) делает работу и сохраняет результат. Клиент опрашивает статус или получает уведомление. Та же схема, что и в статье про дренаж HTTP (Java, Go, Node, Python), и по той же причине: долгий синхронный ответ — источник проблем и без всяких таймаутов.

Заодно два соседних предела шлюза, которые стоит помнить: размер тела запроса (10 МБ у REST-типа) и размер ответа функции (6 МБ при синхронном вызове). Для больших данных в обе стороны используют подписанные ссылки прямо в хранилище объектов — минуя и шлюз, и функцию.

Step Functions: два разных вида процессов

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

Standard — «надёжный и долгий». Живёт до года, каждое состояние выполняется ровно один раз (платформа гарантирует это сама), вся история выполнения хранится и видна в консоли по шагам. Платят за переходы между состояниями — то есть цена зависит от числа шагов, а не от времени. Это то, что нужно для бизнес-процессов: оформление заказа с ожиданием оплаты, согласование, длинная обработка с ожиданием внешнего события.

Express — «быстрый и дешёвый». Живёт до 5 минут, семантика «хотя бы один раз» (шаг может выполниться повторно, значит нужна идемпотентность), история пишется в журнал, а не хранится в сервисе. Платят за число запусков и потреблённые ресурсы, и на потоке это в разы дешевле. Берут для обработки событий в больших объёмах: разобрать сообщение, вызвать три функции, записать результат.

Отсюда ответ на исходный вопрос: потолок 15 минут обходит именно Standard — он разбивает работу на шаги, каждый из которых укладывается в лимит функции, и умеет ждать между ними (в том числе ждать внешнего события неделями, не тратя ничего). Express в этой роли бесполезен: у него собственный потолок в 5 минут.

И приятная возможность, о которой узнают позже: шаги умеют вызывать сервисы AWS напрямую, без функции-посредника — положить запись в базу, отправить сообщение, запустить задачу контейнера. Меньше кода, меньше холодных стартов, меньше цены.

Когда serverless оправдан, а когда нет

Главный навык — не «уметь писать Lambda», а понимать, где она к месту, а где навредит.

Serverless хорош, когда:

  • нагрузка событийная или «скачущая» — то пусто, то наплыв; платить за простой обидно, а пиков не предугадать;
  • задачи нерегулярные — ночная выгрузка отчёта, реакция на загрузку файла, обработка сообщений из очереди;
  • нужно склеить несколько сервисов между собой небольшим кусочком кода;
  • работа фоновая, и доли секунды задержки на cold start никого не смущают.

Serverless проигрывает, когда:

  • нагрузка постоянная и высокая — тогда предсказуемее и дешевле держать контейнеры или инстансы, которые работают всё время;
  • задачи долгие — упираются в лимит 15 минут;
  • нужен очень быстрый и стабильный ответ синхронного API, а cold start этому мешает;
  • у задачи сложное состояние, которое неудобно держать в эфемерной функции, живущей лишь время вызова.

Главная мысль: serverless — инструмент под определённый класс задач, а не замена всему остальному. «Всё на Lambda» — такая же крайность, как «ничего на Lambda». Выбирают осознанно, по характеру нагрузки.

Где это применяется

Serverless встречается повсюду, где есть событийная или нерегулярная работа: обработка загруженных пользователями файлов, реакция на сообщения в очередях, ночные задания по расписанию, склейка микросервисов, чат-боты, webhook-приёмники. Многие проекты используют Lambda не как фундамент всей системы, а точечно — там, где постоянный сервер был бы пустой тратой денег.

Типичные ошибки новичков:

  • Игнорировать cold start до того, как пользователи пожалуются на медленный первый ответ. Если строите синхронный API с требованием к скорости — закладывайте provisioned concurrency или SnapStart с самого начала, а не как пожарную меру.

  • Пытаться засунуть в Lambda долгую задачу и упереться в лимит 15 минут на середине обработки. Длинные процессы — это Step Functions, контейнеры или специальные сервисы пакетной обработки.

  • Складывать всю логику в одну гигантскую функцию. Lambda хороша маленькими, сфокусированными функциями — это и стартует быстрее, и проще тестировать.

  • Забывать про оплату за provisioned concurrency — её держат запущенной всегда, и счёт идёт даже без вызовов. Это бьёт по стоимости, если включить «на всякий случай» и забыть.

  • Обзор вычислений и масштабирование и доступность — чем Lambda отличается от серверов и контейнеров.

  • DynamoDB — естественная пара для serverless-функций: база, которая так же масштабируется без управления серверами.

  • Управление доступом через IAM — функции нужны права, чтобы читать очередь или писать в базу.

  • Основы IaC и Terraform или CloudFormation — чтобы не описывать функции, шлюзы и машины состояний кликами в консоли.

Дополнительно: при первом чтении можно пропустить

Глубже: где смотреть, что происходитрасширенное

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

Журнал. Каждый вызов оставляет в журнале строку отчёта:

REPORT RequestId: 8f3e... Duration: 412.55 ms  Billed Duration: 413 ms
  Memory Size: 512 MB  Max Memory Used: 187 MB  Init Duration: 1204.11 ms

Читается она так: Duration — время самого обработчика, Init Duration — время инициализации, то есть ровно тот холодный старт, о котором идёт речь (эта часть присутствует только в отчётах холодных вызовов). Max Memory Used против Memory Size показывает, не переплачиваете ли вы за память — и одновременно, не слишком ли близко к потолку. Отсюда простое правило измерения: доля вызовов со строкой Init Duration — это доля холодных стартов, а её значение — их цена.

Метрики. Без всякой настройки доступны число вызовов, число ошибок, длительность (среднее и процентили), число одновременных выполнений, число ограничений по одновременности (Throttles) и число попавших во внутреннюю очередь асинхронных событий. Первое, на что ставят сигналы: ошибки, ограничения и длительность около таймаута.

Трассировка. Для функции, которая вызывает базу и соседние сервисы, включают трассировку (X-Ray или совместимый сборщик) — она показывает, где именно ушли миллисекунды, и отдельно выделяет инициализацию.

Практический минимум наблюдаемости для функции: сигнал на ошибки, сигнал на ограничения по одновременности, сигнал на непустую очередь недоставленных и график доли холодных стартов. Четыре вещи, которые настраиваются за полчаса и отвечают на большинство вопросов «почему медленно» и «почему не обработалось».

Глубже: EventBridge: шина событийрасширенное

В списке источников событий шина упомянута одной строкой, а на практике это основной способ связывать сервисы событиями — и стоит понимать, чем она отличается от уведомлений и очередей.

Что она делает. Принимает события (ваши собственные, от сервисов AWS, от внешних поставщиков) и раздаёт их подписчикам по правилам: правило описывает, какие события ему интересны — по источнику, типу и любому полю внутри содержимого, — и куда их отправлять (функция, очередь, процесс Step Functions, другой аккаунт, внешний адрес).

{
  "source": ["orders"],
  "detail-type": ["OrderPaid"],
  "detail": { "amount": [{ "numeric": [">", 10000] }] }
}

Чем отличается от уведомлений (SNS). Уведомления просто рассылают сообщение всем подписчикам темы; фильтрация есть, но простая, по атрибутам. Шина фильтрует по содержимому события, умеет преобразовывать его перед отправкой, хранит схемы событий и умеет повторять доставку и складывать неудачное в очередь недоставленных. Зато уведомления дешевле и быстрее, а ещё умеют отправлять сообщения на почту и в мобильные приложения, чего шина не делает.

Чем отличается от очереди (SQS). Очередь — это буфер с одним читателем, который сам забирает сообщения в своём темпе; шина — раздатчик, который отправляет события подписчикам. Нормальная схема использует оба: шина отправляет событие в очередь, а сервис читает свою очередь — тогда медленный подписчик не теряет события и разгребает их своим темпом.

Что берут в каких случаях. Одно событие нужно трём разным подсистемам, и список подсистем меняется — шина. Событие нужно одному получателю, важна очередь и повторы — очередь напрямую. Нужна рассылка людям (почта, сообщения) — уведомления. Нужно расписание вместо события — та же шина умеет запускать по расписанию (cron), и это заменяет отдельный планировщик.

Глубже: сколько это стоитрасширенное

«Платите только за использование» — лозунг, у которого есть прайс, и порядок величин стоит держать в голове, чтобы понимать, когда функции дешевле машин, а когда нет.

Вызовы. Порядка 20 центов за миллион вызовов. Сам факт вызова стоит почти ничего.

Время работы. Считается в гигабайт-секундах: выделенная память, умноженная на время. Цена — около 1,7 цента за 100 гигабайт-секунд (то есть примерно 0,0000167 доллара за гигабайт-секунду). Пример для ощущения масштаба: функция на 512 МБ, работающая 200 миллисекунд, стоит 0,1 гигабайт-секунды; миллион таких вызовов — это 100 000 гигабайт-секунд, около 1,7 доллара плюс 20 центов за вызовы. Два доллара за миллион запросов — вот откуда берётся репутация дешёвого варианта.

Бесплатный уровень у функций постоянный, а не только для новых аккаунтов: миллион вызовов и 400 000 гигабайт-секунд в месяц. Мелкие фоновые задачи в него укладываются целиком.

Заранее прогретые экземпляры (provisioned concurrency). Здесь платят за готовность, а не за работу: около 1,5 цента за гигабайт-час. Десять экземпляров по 512 МБ круглосуточно — это примерно 55 долларов в месяц дополнительно к оплате самих вызовов. Отсюда практический вывод: держать прогретые экземпляры имеет смысл не «на всякий случай», а по расписанию — например, только в рабочие часы, когда важна задержка ответа.

Где функции становятся дороже машин. Правило грубое, но рабочее: если функция работает непрерывно, её стоимость сравнивается с ценой постоянно включённой машины той же мощности, а потом обгоняет её. Сервис, у которого функция занята больше половины времени, обычно дешевле переселить в контейнер. И наоборот: неровная нагрузка с простоями и редкие фоновые задачи на машинах стоят дороже, потому что за простой платят.

Что легко забыть в подсчётах. Шлюз API берёт свою плату за миллион запросов (сопоставимую с ценой самих вызовов функции), журналы платятся за объём записи и хранения (у болтливой функции это заметная строка), трафик наружу считается отдельно, а шаги Step Functions — сверх всего этого. Считая «серверлесс против контейнера», складывают всю цепочку, а не только функцию.

Коротко

  • Функция спит до события и масштабируется копиями на каждый запрос; источники это HTTP через шлюз, очередь, файл в хранилище, поток изменений базы, расписание и шина событий.
  • Отказы разные для двух видов вызова: синхронный возвращает ошибку вызывающему, асинхронный AWS повторяет сама до трёх раз, поэтому обработчик обязан быть идемпотентным, а неудавшееся событие уходит в очередь недоставленных или адресат с причиной.
  • Очередь как источник: время видимости не меньше таймаута функции, лучше в шесть раз больше, иначе одно сообщение делают два экземпляра; отказ на одном сообщении возвращает всю пачку без ответа о частичном отказе.
  • Холодный старт это инициализация среды после простоя: лечат provisioned concurrency за деньги, SnapStart для Java, лёгким пакетом и рантаймом; терпим для фоновой работы, вреден для синхронного API.
  • Числа берут из журнала: Init Duration и есть холодный старт, Max Memory Used против выделенной памяти; минимум наблюдаемости это сигналы на ошибки, ограничения по одновременности и непустую очередь недоставленных.
  • Лимиты: 15 минут на вызов, до 10 ГБ памяти и процессор пропорционально ей, 1000 одновременных выполнений на регион, ответ 6 МБ, пакет 250 МБ или образ до 10 ГБ, диск /tmp до 10 ГБ.
  • За шлюзом API действует его потолок в 29 секунд: долгую работу принимают, кладут в очередь и отвечают сразу; Step Functions Standard живёт до года и обходит 15 минут шагами, Express до 5 минут с семантикой «хотя бы раз».
  • EventBridge раздаёт события по содержимому многим подписчикам, SNS рассылает всем, SQS буферизует для одного читателя; обычно шина кладёт событие в очередь получателя.
  • Платят за вызовы и гигабайт-секунды, бесплатный уровень постоянный; непрерывно работающая функция дороже машины той же мощности, а provisioned concurrency, шлюз и журналы считают отдельно.

Что почитать дальше