Serverless і подійна архітектура
Навіщо це
Section titled “Навіщо це”Користувач завантажує фото, і треба зробити мініатюру. Нове замовлення має потрапити на склад, в аналітику й на пошту. Навантаження нерівне: вночі нуль, у розпродаж сотні запитів за секунду. Тримати під це віртуальні машини значить платити за простій. Serverless (буквально «безсерверний»: сервери є, але ви їх не бачите й не оплачуєте, поки код не працює) пропонує інший договір: провайдер запускає ваш код у відповідь на подію, масштабує від нуля до тисяч копій і рахує гроші за фактичне виконання.
Договір має ціну. Код інколи стартує повільно (холодний старт). Повідомлення доставляються щонайменше раз, тож обробник мусить переживати дублікати. А функція, яку викликають мільйони разів, зрештою коштує більше за контейнер. Модуль пояснює механізми, з яких випливають ці наслідки, і показує, де межа.
Передумови. Ролі й федерація (модуль 3), віртуальні машини й образи (модуль 4), контейнерні платформи, зокрема Cloud Run (модуль 8). Холодний старт спирається на курс ОС: віртуальну памʼять і сторінковий кеш (модуль 12), а також microVM (модуль 17).
Функція як одиниця виконання
Section titled “Функція як одиниця виконання”Функція є обробником: провайдер викликає його з подією (HTTP-запит, повідомлення з черги, файл у сховищі), він щось робить і завершується. Стан між викликами не гарантується: наступну подію може обробити інший інстанс, а цей могли знищити.
Важливо, скільки подій обробляє один інстанс одночасно. Lambda запускає одне середовище виконання на один запит: сто одночасних запитів означають сто середовищ. Cloud Run functions дозволяють до 1000 одночасних запитів на інстанс, Azure Functions Flex Consumption теж налаштовує одночасність. Для коду, що більшість часу чекає відповіді бази, це різниця в рахунку: Lambda оплачує кожне очікування окремо, а на інстансі з одночасністю очікування ділиться.
Тривалість виклику обмежена: Lambda до 15 хвилин (довші процеси потребують durable-режиму, про нього нижче), Cloud Run functions до 60 хвилин для HTTP і 9 хвилин для подій.
Холодний старт
Section titled “Холодний старт”Коли для події немає готового інстанса, провайдер створює його. Цю затримку називають холодним стартом (cold start). Складається вона з кількох етапів, і кожен має власну причину.
1. Створення середовища. Провайдер обирає хост і запускає ізольоване середовище. У Lambda це Firecracker, microVM із мінімальним набором пристроїв, що стартує за десятки мілісекунд (модуль 17 курсу ОС). Ізоляцію між клієнтами забезпечує гіпервізор (у контейнерів ядро спільне), тому на одній машині безпечно виконується код різних клієнтів.
2. Завантаження коду або образу. Пакет функції чи шари образу треба доставити на хост. Що більший пакет, то довше. Сторінки файлів потрапляють у памʼять у міру звернення (demand paging, модуль 12), і на свіжому хості сторінковий кеш порожній, тож перше читання бібліотек іде з диска чи мережі.
3. Старт рантайму. Запускається процес Node.js, JVM, інтерпретатор Python. JVM тут найважча: завантаження класів і прогрів JIT займають сотні мілісекунд і більше.
4. Ініціалізація функції. Виконується код поза handler: імпорти, клієнти SDK, з’єднання з базою, читання конфігурації. Цей етап ви контролюєте найбільше.
Документація AWS стверджує, що холодні старти трапляються менш ніж у 1% викликів, а їхня тривалість коливається від менш як 100 мс до понад секунди. Для API, яким користуються рідко, або для першого запиту після паузи цей відсоток вищий, бо готових середовищ нема.
Від 1 серпня 2025 року AWS оплачує фазу ініціалізації (INIT) і для функцій у ZIP-пакетах на керованих рантаймах, тобто холодний старт тепер і додає затримку, і оплачується.
Як його зменшують
Section titled “Як його зменшують”Спершу зменшують те, що залежить від вас: менше залежностей, ліниве імпортування рідкісних модулів, клієнти SDK створюють поза handler, щоб тепле середовище їх перевикористовувало. Більша памʼять у Lambda дає й більше процесора, тож ініціалізація іноді швидшає настільки, що функція коштує не більше.
Lambda SnapStart переносить ініціалізацію на момент публікації версії. Lambda виконує init, робить знімок памʼяті й диска Firecracker-середовища, шифрує його й кешує. Новий інстанс відновлюється зі знімка замість ініціалізації з нуля. SnapStart підтримує Java 11 і новіші, Python 3.12+ і .NET 8+. У нього є обмеження, що випливають з самої ідеї знімка:
- Унікальність. Усі інстанси стартують з однакового стану. Випадкові числа, ідентифікатори й секрети, згенеровані під час init, вийдуть однаковими, тож їх генерують після відновлення.
- З’єднання. Мережеві з’єднання, відкриті до знімка, після відновлення можуть бути недійсними; їх перевіряють і відкривають заново.
- Несумісність. SnapStart не працює з provisioned concurrency, EFS і ефемерним сховищем понад 512 МБ.
Плата: для Java додаткової немає, для інших рантаймів окремо оплачується кешування знімка (мінімум три години на версію) і кожне відновлення.
Provisioned concurrency тримає задану кількість середовищ ініціалізованими заздалегідь; це дає відповідь за двозначну кількість мілісекунд, але оплачується цілодобово. В Azure Functions Flex Consumption аналог називається always ready instances. У Cloud Run є мінімальна кількість інстансів.
Як це в трьох хмарах
Section titled “Як це в трьох хмарах”| AWS Lambda | Azure Functions (Flex Consumption) | Cloud Run functions | |
|---|---|---|---|
| Одиниця | функція | застосунок із функціями | функція на Cloud Run |
| Одночасність на інстанс | 1 запит | налаштовується | до 1000 |
| Макс. тривалість виклику | 15 хв | налаштовується | 60 хв (HTTP), 9 хв (події) |
| Проти холодного старту | SnapStart, provisioned concurrency | always ready | мінімум інстансів |
| Події | event source mapping (SQS, Kinesis), EventBridge, S3 | тригери й прив’язки, Event Grid | Eventarc |
| Довгі процеси | Durable functions (до року) | Durable Functions | Workflows |
Чим відрізняється і що з цього випливає. Lambda й Flex Consumption є функціями в повному сенсі: платформа управляє масштабуванням і ви не бачите контейнера. Cloud Run functions працюють як сервіс Cloud Run із додатковою обгорткою, тож їх можна перенести в контейнер без переписування. У Azure слід спиратися саме на Flex Consumption: він рекомендований, тоді як Linux-план Consumption Microsoft виводить з ужитку 30 вересня 2028 року. Flex дає приватну мережу (VNet), розміри інстансів 512 МБ, 2 ГБ і 4 ГБ, масштабування до 1000 інстансів і незалежне масштабування кожної функції. Крім класичних функцій, AWS пропонує Lambda Managed Instances (функції на ваших EC2 із надбавкою 15%) і durable functions.
variable "role_arn" { type = string}
resource "aws_lambda_function" "thumb" { function_name = "thumb" role = var.role_arn runtime = "java21" handler = "shop.Thumb::handleRequest" filename = "build/thumb.zip" architectures = ["arm64"] memory_size = 1024 timeout = 30 publish = true
snap_start { apply_on = "PublishedVersions" }}variable "storage_name" { type = string}
resource "azurerm_resource_group" "rg" { name = "rg-thumb" location = "eastus"}
resource "azurerm_storage_account" "sa" { name = var.storage_name resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location account_tier = "Standard" account_replication_type = "LRS"}
resource "azurerm_storage_container" "deploy" { name = "deploy" storage_account_id = azurerm_storage_account.sa.id container_access_type = "private"}
resource "azurerm_service_plan" "flex" { name = "plan-thumb" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location sku_name = "FC1" os_type = "Linux"}
resource "azurerm_function_app_flex_consumption" "thumb" { name = "func-thumb" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location service_plan_id = azurerm_service_plan.flex.id
storage_container_type = "blobContainer" storage_container_endpoint = "${azurerm_storage_account.sa.primary_blob_endpoint}${azurerm_storage_container.deploy.name}" storage_authentication_type = "StorageAccountConnectionString" storage_access_key = azurerm_storage_account.sa.primary_access_key runtime_name = "python" runtime_version = "3.12" maximum_instance_count = 50 instance_memory_in_mb = 2048
site_config {}}variable "source_bucket" { type = string}
variable "source_object" { type = string}
resource "google_cloudfunctions2_function" "thumb" { name = "thumb" location = "us-central1"
build_config { runtime = "python313" entry_point = "make_thumbnail"
source { storage_source { bucket = var.source_bucket object = var.source_object } } }
service_config { available_memory = "512Mi" timeout_seconds = 60 min_instance_count = 1 max_instance_count = 20 max_instance_request_concurrency = 20 }}Черги, теми й шини подій
Section titled “Черги, теми й шини подій”Три види посередників розв’язують різні задачі.
Черга (queue). Один вид повідомлень, кілька конкуруючих споживачів, кожне повідомлення обробляє один із них. Черга згладжує сплески: виробник пише стільки, скільки хоче, а споживачі беруть у своєму темпі. Це і є зворотний тиск (backpressure): база з обмеженою пропускною здатністю не падає, коли приходить тисяча замовлень за секунду.
Тема (topic) з підписками. Одне повідомлення отримує кожен підписник: замовлення бачать склад, пошта й аналітика. Виробник не знає споживачів, а новий споживач додається без змін у виробника.
Шина подій (event bus). Маршрутизує події за правилами на вміст: «усі
OrderPlaced з сумою понад 1000 надсилати на перевірку шахрайства». Шина також
приймає події від сервісів хмари і SaaS.
| AWS | Azure | GCP | |
|---|---|---|---|
| Черга | SQS | Service Bus queue | підписка Pub/Sub (pull) |
| Тема з підписками | SNS | Service Bus topic | тема Pub/Sub |
| Шина подій | EventBridge | Event Grid | Eventarc |
| Dead-letter queue | SQS DLQ за maxReceiveCount |
вбудована підчерга $deadletterqueue |
dead letter topic |
Чим відрізняється і що з цього випливає. У Pub/Sub черги й теми окремо немає: тема розсилає повідомлення підпискам, а кожна підписка поводиться як черга для своїх споживачів. У AWS зазвичай ставлять SNS перед кількома SQS (fan-out), у Pub/Sub це одна тема з кількома підписками. Service Bus охоплює і черги, і теми, а Event Grid лише маршрутизує події й не зберігає повідомлень надовго.
Доставка «щонайменше раз», ідемпотентність, visibility timeout
Section titled “Доставка «щонайменше раз», ідемпотентність, visibility timeout”Розподілена система не може одночасно гарантувати, що повідомлення не загубиться і що воно не повториться. Споживач обробив повідомлення, але підтвердження загубилося по дорозі; або впав, не встигнувши підтвердити. Брокер не знає, що сталося, і на вибір має два варіанти: доставити ще раз або втратити. Усі три хмари обирають «ще раз», і це називають доставкою «щонайменше раз» (at-least-once delivery). Повідомлення не губиться, але може прийти двічі.
Ця властивість пронизує все: у документації Lambda з SQS прямо сказано, що event source mapping обробляє кожну подію щонайменше раз і що функція має бути ідемпотентною.
Visibility timeout
Section titled “Visibility timeout”Коли споживач бере повідомлення, воно не зникає з черги, а стає невидимим на обмежений час. Цей інтервал у SQS називається visibility timeout (за замовчуванням 30 секунд, від 0 до 12 годин), у Service Bus це тривалість блокування (1 хвилина за замовчуванням, до 5 хвилин), у Pub/Sub ack deadline (кінцевий строк підтвердження). Поки таймер іде, інші споживачі повідомлення не бачать. Якщо споживач підтвердив (видалив) його вчасно, повідомлення зникає назавжди. Якщо ні, таймер закінчується і повідомлення знову доступне.
Звідси випливає перше правило вибору таймера: він має перевищувати найдовший час обробки з запасом. Для SQS з Lambda AWS радить ставити його не менше ніж у шість разів більше за таймаут функції. Якщо він замалий, повідомлення повертається у видимість, поки перший споживач ще працює, і його бере другий. Так виникають дублікати навіть без жодного збою.
Dead-letter queue
Section titled “Dead-letter queue”Повідомлення, яке стабільно ламає обробник (отруйне, poison message),
без обмежень крутилося б у черзі вічно. Тому після заданої кількості спроб
брокер відкладає його в dead-letter queue (DLQ): у SQS це параметр
maxReceiveCount черги-джерела, у Service Bus MaxDeliveryCount (за
замовчуванням 10), у Pub/Sub max_delivery_attempts dead letter topic
(за замовчуванням 5, від 5 до 100).
DLQ сама нічого не лагодить. Це місце, де повідомлення чекають на людину, тож потрібні три речі: сповіщення, коли в ній щось з’явилося; достатній термін зберігання (повідомлення в SQS DLQ має той самий час життя, що й у черзі-джерелі, відлічений від початкового надходження, тому DLQ налаштовують на довший строк); і спосіб повернути повідомлення в основну чергу після виправлення (redrive).
Ідемпотентність
Section titled “Ідемпотентність”Обробник ідемпотентний (idempotent), якщо повторна обробка тієї самої події не змінює результату. Найнадійніше дає ключ ідемпотентності: у повідомленні є унікальний ідентифікатор події, а обробник спершу записує його в сховище з обмеженням унікальності й виконує роботу лише тоді, коли запис вдався.
-- у тій самій транзакції, що й побічний ефектINSERT INTO processed_events (event_id, processed_at)VALUES ($1, now())ON CONFLICT (event_id) DO NOTHING;-- 0 змінених рядків означає, що подію вже оброблялиТака таблиця однакова в усіх хмарах, бо живе у вашій базі. Якщо побічний ефект відбувається поза базою (лист, платіж через зовнішній API), у запиті до цього API теж передають ключ ідемпотентності: платіжні API його підтримують.
Деякі брокери намагаються зняти проблему самі. SQS FIFO і Service Bus вміють виявляти дублікати за ідентифікатором у часовому вікні. Pub/Sub має exactly-once delivery для підписок pull: гарантія діє лише в межах одного регіону, не поширюється на push і експортні підписки, а підтвердження має повертати відповідь, інакше можливі мовчазні повторні доставки. Це зменшує дублікати від брокера, але не від виробника, що надіслав подію двічі, тож ідемпотентність споживача лишається обовʼязковою.
Ще одне поширене джерело дублікатів: Lambda з SQS обробляє повідомлення
пакетами. Якщо один елемент пакета зламався, а функція повернула помилку,
повторюється весь пакет. Параметр ReportBatchItemFailures дозволяє повернути
лише ідентифікатори невдалих елементів, і решта пакета видаляється.
variable "function_name" { type = string}
resource "aws_sqs_queue" "dlq" { name = "orders-dlq" message_retention_seconds = 1209600}
resource "aws_sqs_queue" "orders" { name = "orders" visibility_timeout_seconds = 180
redrive_policy = jsonencode({ deadLetterTargetArn = aws_sqs_queue.dlq.arn maxReceiveCount = 5 })}
resource "aws_lambda_event_source_mapping" "orders" { event_source_arn = aws_sqs_queue.orders.arn function_name = var.function_name batch_size = 10 function_response_types = ["ReportBatchItemFailures"]}variable "namespace_name" { type = string}
resource "azurerm_resource_group" "rg" { name = "rg-orders" location = "eastus"}
resource "azurerm_servicebus_namespace" "sb" { name = var.namespace_name location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name sku = "Standard"}
resource "azurerm_servicebus_queue" "orders" { name = "orders" namespace_id = azurerm_servicebus_namespace.sb.id lock_duration = "PT2M" max_delivery_count = 5 dead_lettering_on_message_expiration = true}Dead-letter queue створювати не треба: вона є підчергою кожної черги й підписки.
resource "google_pubsub_topic" "orders" { name = "orders"}
resource "google_pubsub_topic" "orders_dead" { name = "orders-dead"}
resource "google_pubsub_subscription" "worker" { name = "orders-worker" topic = google_pubsub_topic.orders.id ack_deadline_seconds = 60 message_retention_duration = "604800s"
retry_policy { minimum_backoff = "10s" maximum_backoff = "300s" }
dead_letter_policy { dead_letter_topic = google_pubsub_topic.orders_dead.id max_delivery_attempts = 5 }}Щоб повідомлення реально переходили в orders-dead, службовому акаунту Pub/Sub
треба видати право публікувати в цю тему, а на orders-dead створити підписку,
бо тема без підписки повідомлень не зберігає.
Надіслати повідомлення можна так:
aws sqs send-message \ --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/orders \ --message-body '{"orderId":"o-1001"}'Azure CLI не має команди надсилання повідомлення в Service Bus. Повідомлення
надсилають SDK (ServiceBusSender), Service Bus Explorer у порталі або
інструмент зі стороннього набору. Створити чергу й переглянути її лічильники
можна командами az servicebus queue create і az servicebus queue show.
gcloud pubsub topics publish orders --message='{"orderId":"o-1001"}'gcloud pubsub subscriptions pull orders-worker --auto-ack --limit=1Оркестрація
Section titled “Оркестрація”Коли дія складається з кількох кроків (списати гроші, зарезервувати товар, відправити, а при збої повернути кошти), є два підходи. У хореографії кожен сервіс реагує на подію попереднього й публікує власну. Логіка розсипана по сервісах, зате нічого не координує центр. В оркестрації одна сутність тримає схему процесу: викликає кроки, повторює невдалі, виконує компенсації (saga) і пам’ятає, де зупинилася.
- AWS Step Functions. Процес описується автоматом на Amazon States Language (JSON) або в конструкторі. Стандартний тип працює до року й оплачується за переходи між станами, експрес-тип розрахований на короткі процеси з великою частотою й оплачується за запити та тривалість. Крім того, є Lambda durable functions: код у Lambda з контрольними точками, який може чекати до року, не оплачуючи очікування. Після відновлення код виконується заново, пропускаючи вже виконані кроки.
- Azure Durable Functions. Оркестратор пишуть звичайним кодом (C#, Python, JavaScript, Java, PowerShell), а розширення зберігає історію подій і відтворює виконання після перезапуску. Тому код оркестратора має бути детермінованим: без поточного часу й випадкових чисел прямо в ньому. Як сховище стану рекомендовано Durable Task Scheduler.
- Google Workflows. Процес описується YAML або JSON, кроки викликають HTTP-ендпоінти й API Google Cloud, є повторення, обробка помилок і очікування зворотного виклику.
Вибір залежить від того, хто пише процес. Step Functions і Workflows описують процес окремо від коду сервісів і добре показують його стан у консолі. Durable Functions і Lambda durable functions тримають процес у коді застосунку, що зручно програмістам, але вимагає дисципліни детермінізму.
variable "role_arn" { type = string}
variable "charge_arn" { type = string}
resource "aws_sfn_state_machine" "order" { name = "order" role_arn = var.role_arn
definition = jsonencode({ StartAt = "Charge" States = { Charge = { Type = "Task" Resource = var.charge_arn Retry = [{ ErrorEquals = ["States.TaskFailed"] IntervalSeconds = 2 MaxAttempts = 3 BackoffRate = 2 }] Catch = [{ ErrorEquals = ["States.ALL"] Next = "Failed" }] End = true } Failed = { Type = "Fail" Cause = "Charge failed after retries" } } })}import azure.durable_functions as df
def orchestrator(context: df.DurableOrchestrationContext): order = context.get_input() retry = df.RetryOptions(first_retry_interval_in_milliseconds=2000, max_number_of_attempts=3) try: yield context.call_activity_with_retry("charge", retry, order) except Exception: yield context.call_activity("cancel_order", order)
main = df.Orchestrator.create(orchestrator)main: params: [order] steps: - charge: try: call: http.post args: url: https://charge-example.a.run.app/charge body: ${order} auth: type: OIDC retry: ${http.default_retry} except: as: e steps: - fail: raise: ${e}Скільки це коштує
Section titled “Скільки це коштує”Станом на вересень 2026, регіони us-east-1, eastus, us-central1, Linux, оплата за вимогою.
Функції.
| Сервіс | За виклики | За виконання |
|---|---|---|
| Lambda | $0.20 за млн | x86 $0.0000166667, Arm $0.0000133334 за ГБ-с |
| Azure Functions Flex Consumption, on demand | $0.40 за млн (по $0.000004 за 10) | $0.000026 за ГБ-с |
| Cloud Run (request-based) | $0.40 за млн | $0.000024 за vCPU-с і $0.0000025 за ГіБ-с |
Безкоштовні квоти на місяць: Lambda 1 млн викликів і 400 000 ГБ-с; Flex Consumption 250 000 виконань і 100 000 ГБ-с; Cloud Run 2 млн запитів, 180 000 vCPU-с і 360 000 ГіБ-с. Прогріті інстанси оплачуються окремо: provisioned concurrency у Lambda $0.0000041667 за ГБ-с і $0.0000097222 за ГБ-с виконання; always ready у Flex $0.000004 за ГБ-с базової плати і $0.000016 за ГБ-с виконання, без безкоштовної квоти. Перед Lambda для HTTP зазвичай стоїть шлюз: API Gateway HTTP API $1.00 за млн запитів, REST API $3.50 за млн. Lambda Function URL шлюзу не потребує.
Посередники.
| Сервіс | Ціна |
|---|---|
| SQS standard | $0.40 за млн запитів (перший мільйон безкоштовно), FIFO $0.50; запит = порція до 64 КБ |
| SNS | $0.50 за млн публікацій (перший мільйон безкоштовно) |
| EventBridge, власні події | $1.00 за млн подій |
| Service Bus Standard | база $10 на місяць ($0.013441 за годину), $0.80 за млн операцій понад 13 млн |
| Event Grid | $0.60 за млн операцій (перший мільйон безкоштовно) |
| Pub/Sub | $40 за ТіБ (перші 10 ГіБ на місяць безкоштовно) |
Оркестрація. Step Functions Standard: $0.000025 за перехід між станами (4000 безкоштовних на місяць), повтори рахуються як додаткові переходи; Express: $1.00 за млн запусків плюс $0.00001667 за ГБ-с. Google Workflows: $0.01 за 1000 внутрішніх кроків і $0.025 за 1000 зовнішніх (5000 і 2000 безкоштовних). Для Durable Functions окремої плати за оркестрацію немає, платять за виконання функцій і за сховище стану.
Де функція стає дорожчою за контейнер
Section titled “Де функція стає дорожчою за контейнер”Розрахуємо навантаження, де кожен запит потребує 200 мс одного vCPU (наприклад, обробка зображення чи розбір великого JSON). У Lambda 1769 МБ дають еквівалент одного vCPU, тож функція на Arm з такою памʼяттю коштує 0.2 с × 1.769 ГБ × $0.0000133334 + $0.0000002 = $4.92 за мільйон запитів. Порівняємо з тим, що коштує постійно працюючий контейнер або ВМ:
| Альтернатива | На місяць | Точка беззбитковості |
|---|---|---|
| одна ВМ c7g.large (2 vCPU, $0.0725 за годину) | $52.92 | 10.8 млн запитів, близько 4 запитів за секунду |
| дві такі ВМ для відмовостійкості | $105.85 | 21.5 млн запитів, близько 8 запитів за секунду |
| одне завдання Fargate Arm (1 vCPU, 2 ГБ) | $28.84 | 5.9 млн запитів, близько 2 запитів за секунду |
| два завдання Fargate | $57.67 | 11.7 млн запитів, близько 4.5 запиту за секунду |
Нижче цієї точки Lambda дешевша, бо ви не платите за простій. Вище дешевшим стає контейнер, за умови що ви тримаєте його завантаженим на 40-60%: одна c7g.large обробляє до 10 запитів за секунду при повному навантаженні, тобто близько 15 млн на місяць при 60%. Якщо перед функцією стоїть API Gateway HTTP API ($1.00 за млн), функція коштує $5.92 за мільйон, і точка для однієї ВМ зсувається до 8.9 млн запитів.
Для коду, що переважно чекає, картина інша. Запит, який 200 мс чекає базу й займає 512 МБ, коштує в Lambda на Arm $1.53 за мільйон, тож точка беззбитковості для однієї c7g.large складає близько 35 млн запитів (13 запитів за секунду), а сама ВМ при цьому обробляла б сотні одночасних запитів. Тут різниця між моделлю «один запит на середовище» і одночасністю на інстанс стає вирішальною: у Cloud Run з одночасністю 80 платять за інстанс, і очікування різних запитів не оплачується окремо.
Розрахунки не враховують безкоштовні квоти, шлюз, логи, оркестрацію й вартість людини, яка обслуговує кластер, тож точку варто рахувати для власного профілю (тривалість, памʼять, частка очікування). Порядок величини: одиниці запитів за секунду в середньому. Для Flex Consumption той самий розрахунок при інстансі 2 ГБ дає $10.80 за мільйон запитів, для Cloud Run у режимі request-based при 1 vCPU, 2 ГіБ і одночасності 1 близько $6.20.
Розбір: замовлення, що прийшло двічі
Section titled “Розбір: замовлення, що прийшло двічі”Інтернет-магазин ставить замовлення в чергу SQS. Воркер у контейнері (ECS Fargate, модуль 8) бере повідомлення, списує гроші через платіжний API й видаляє повідомлення. Черга створена з параметрами за замовчуванням: visibility timeout 30 секунд. У п’ятницю платіжний API уповільнився, і списання почало займати 45 секунд.
Що відбувається з повідомленням. Воркер А отримує його о 12:00:00, повідомлення
стає невидимим. О 12:00:30 таймер закінчується, а воркер А ще чекає платіжний
API. Повідомлення знову видиме, його отримує воркер Б і починає списання.
О 12:00:45 воркер А отримує успіх від платіжного API й намагається видалити
повідомлення, але його ReceiptHandle уже застарів: повідомлення отримано вдруге,
і видалення за старим дескриптором його не прибирає. Воркер Б доводить своє
списання до кінця. Клієнт бачить два списання, хоча жодної помилки в системі не
було, а якщо таймер закінчиться й для воркера Б, з’явиться третє.
Це не збій брокера, а розбіжність між таймером і реальним часом обробки. Виправлення складається з трьох шарів:
- Visibility timeout ставлять із запасом над часом обробки, а воркер, чия
обробка може бути довшою, продовжує його викликом
ChangeMessageVisibility. - Обробник ідемпотентний: ключ ідемпотентності (
orderId) записується перед списанням, і той самий ключ передається платіжному API, тож повторний виклик не призводить до другого списання. - Черга має DLQ з
maxReceiveCount3-5, а на глибину DLQ стоїть сповіщення, щоб отруйне повідомлення не крутилося вічно й не лишилося непоміченим.
Типові помилки розуміння
Section titled “Типові помилки розуміння”«Serverless завжди дешевший». Функція дешева при рідкому чи нерівному навантаженні. При стабільно високому вона програє контейнеру, і точку легко порахувати.
«Брокер гарантує, що повідомлення прийде один раз». Гарантії «рівно раз» у брокерів обмежені: вони не перекривають побічні ефекти поза брокером і дублікати від виробника. Споживач має бути ідемпотентним.
«DLQ вирішує проблему». DLQ лише зберігає повідомлення, які не вдалося обробити. Без сповіщення й процесу розбору вона стає кладовищем.
«Функція масштабується безмежно, отже безпечна». Тисяча одночасних інстансів відкриють тисячу з’єднань із базою, яка розрахована на сто. Одночасність функції обмежують (reserved concurrency, максимум інстансів у Flex і Cloud Run), а зі стороною бази ставлять проксі чи пул з’єднань.
«Холодний старт є лише проблемою Java». Java найгірша через JVM, але завантаження коду, старт рантайму й init мають усі мови. Вплив залежить від розміру пакета й обсягу ініціалізації.
«Повтор виправить збій». Повторна спроба без затримки й без обмеження перетворює тимчасовий збій залежності на лавину. Потрібні експоненціальна затримка й ліміт спроб.
Перевір себе
Джерела
Section titled “Джерела”- Ціни: Lambda, SQS, SNS, EventBridge, Step Functions, API Gateway, Azure Functions, Service Bus, Event Grid, Cloud Run, Pub/Sub, Workflows, Azure retail prices API
- AWS: життєвий цикл середовища виконання Lambda, SnapStart, Lambda з SQS, SQS visibility timeout, durable functions, стандартизація оплати INIT
- Azure: Flex Consumption, dead-letter queues у Service Bus, Durable Functions
- GCP: Cloud Run functions і 1st gen, dead letter topics, exactly-once delivery
- Firecracker: підтримка знімків; курс ОС: віртуальна памʼять, віртуалізація і контейнери