MidТеория4 min

Serverless

Serverless архитектура: FaaS vs BaaS, AWS Lambda, cold start, когда использовать и anti-patterns

Что такое Serverless

Serverless — модель облачных вычислений, в которой провайдер полностью управляет инфраструктурой. Разработчик пишет только бизнес-логику, не думая о серверах, масштабировании и provisioning.

Важно: "Serverless" не означает "без серверов". Серверы есть, но они полностью абстрагированы — вы не управляете ими, не настраиваете и не масштабируете вручную.

Serverless = FaaS + BaaS

Компонент Описание Примеры
FaaS (Function as a Service) Запуск функций по событиям AWS Lambda, Google Cloud Functions, Azure Functions
BaaS (Backend as a Service) Управляемые backend-сервисы Firebase, AWS DynamoDB, Auth0, Supabase

FaaS: Functions as a Service

Модель выполнения

Событие (HTTP, Queue, Schedule, S3 upload)
        │
        ▼
┌─────────────────────┐
│  Cloud Provider      │
│                     │
│  1. Получить событие │
│  2. Создать sandbox  │  ← Cold Start (если нет тёплого)
│  3. Загрузить код    │
│  4. Выполнить        │
│  5. Вернуть ответ    │
│  6. Заморозить / ─── │  ← Warm instance (reuse)
│     удалить sandbox  │  ← Kill после ~15 min простоя
└─────────────────────┘

AWS Lambda — пример функции

# handler.py — AWS Lambda function
import json
import os
import boto3
from datetime import datetime, timezone

# Initialized once per cold start, reused across invocations
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['ORDERS_TABLE'])

def handler(event, context):
    """
    Process incoming order from API Gateway.
    Triggered by HTTP POST /orders
    """
    try:
        body = json.loads(event['body'])

        order = {
            'order_id': context.aws_request_id,
            'user_id': body['user_id'],
            'items': body['items'],
            'total': calculate_total(body['items']),
            'status': 'pending',
            'created_at': datetime.now(timezone.utc).isoformat(),
        }

        table.put_item(Item=order)

        return {
            'statusCode': 201,
            'headers': {'Content-Type': 'application/json'},
            'body': json.dumps({
                'order_id': order['order_id'],
                'status': order['status'],
            }),
        }
    except KeyError as e:
        return {
            'statusCode': 400,
            'body': json.dumps({'error': f'Missing field: {str(e)}'}),
        }
    except Exception as e:
        print(f'Error processing order: {e}')  # Goes to CloudWatch
        return {
            'statusCode': 500,
            'body': json.dumps({'error': 'Internal server error'}),
        }

def calculate_total(items):
    return sum(item['price'] * item['quantity'] for item in items)

Serverless Framework — определение инфраструктуры

# serverless.yml — Infrastructure as Code for Lambda
service: orders-api

provider:
  name: aws
  runtime: python3.12
  region: eu-west-1
  memorySize: 256
  timeout: 10
  environment:
    ORDERS_TABLE: !Ref OrdersTable

functions:
  createOrder:
    handler: handler.handler
    events:
      - httpApi:
          path: /orders
          method: POST
    reservedConcurrency: 100  # Limit max concurrent executions

  processOrder:
    handler: processor.handler
    events:
      - sqs:
          arn: !GetAtt OrderQueue.Arn
          batchSize: 10
    timeout: 30

  dailyReport:
    handler: reports.handler
    events:
      - schedule: cron(0 8 * * ? *)  # Every day at 8:00 UTC

resources:
  Resources:
    OrdersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: orders
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: order_id
            AttributeType: S
        KeySchema:
          - AttributeName: order_id
            KeyType: HASH

Cold Start

Cold Start — задержка при первом вызове функции, когда платформа создаёт новый sandbox, загружает код и инициализирует рантайм.

Анатомия Cold Start

Cold Start:   [Init Runtime] [Load Code] [Init Dependencies] [Execute]
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~       ~~~~~~~~
               Cold start overhead (100ms - 10s)              Actual work

Warm Start:   [Execute]
               ~~~~~~~~
               Only actual work (1-10ms overhead)

Факторы, влияющие на Cold Start

Фактор Влияние Оптимизация
Язык Java/C# > Python > Node.js > Go/Rust Выбирать Go/Rust для критичных по latency
Размер пакета Больше пакет = дольше загрузка Tree-shaking, минимальные зависимости
Memory Больше памяти = больше CPU Увеличить memory (пропорционально CPU)
VPC +200-500ms для ENI attach Использовать VPC только при необходимости
Init код DB connections, SDK init Инициализировать вне handler

Стратегии минимизации Cold Start

# ANTI-PATTERN: initialization inside handler
def handler(event, context):
    import heavy_library  # Loaded every cold start
    db = connect_to_database()  # Connection every cold start
    return process(event, db)

# CORRECT: initialization outside handler (reused across invocations)
import heavy_library  # Loaded once per cold start
db = connect_to_database()  # Connection once per cold start

def handler(event, context):
    return process(event, db)  # Reuses warm connection

Provisioned Concurrency — AWS Lambda позволяет предварительно прогревать N экземпляров:

functions:
  criticalApi:
    handler: handler.handler
    provisionedConcurrency: 5  # Always 5 warm instances ready

Сравнение Cold Start по языкам

Язык Cold Start (типичный) Warm Invocation
Go 30-80ms <1ms
Rust 30-80ms <1ms
Python 100-300ms 1-5ms
Node.js 100-300ms 1-5ms
Java 500ms-3s 1-10ms
.NET 300ms-2s 1-5ms

Когда использовать Serverless

Идеально подходит

Сценарий Почему
Event-driven обработка S3 upload → resize image → save
API с переменной нагрузкой 0 запросов ночью, пики днём
Cron-задачи Ежедневные отчёты, очистка данных
Webhooks Обработка callback от сторонних сервисов
MVP и прототипы Быстрый запуск без управления инфраструктурой
Data pipelines ETL, обработка потоков из Kinesis/SQS

Не подходит

Сценарий Почему
Долгие процессы Лимит 15 минут (Lambda), costly
Стабильная высокая нагрузка Дороже, чем reserved instances
Stateful приложения WebSocket, long-polling — ограниченно
Low-latency требования Cold start неприемлем для <10ms SLA
Большие монолиты Не масштабируются по функциям
GPU/ML inference Ограниченные ресурсы

Экономика Serverless

Сценарий: API с 1M запросов/месяц, 200ms среднее время, 256MB memory

AWS Lambda:
  1M requests × $0.20/1M     = $0.20
  1M × 200ms × 256MB         = 51,200 GB-seconds
  51,200 × $0.0000166667     = $0.85
  Итого: ~$1.05/месяц

EC2 t3.micro (постоянно):
  $0.0104/час × 730 часов    = $7.59/месяц

Break-even: ~10M запросов/месяц при постоянной нагрузке

Serverless Anti-Patterns

1. Mega-Function

# ANTI-PATTERN: one function does everything
def handler(event, context):
    if event['path'] == '/users':
        return handle_users(event)
    elif event['path'] == '/orders':
        return handle_orders(event)
    elif event['path'] == '/payments':
        return handle_payments(event)
    # ... 50 more routes

Проблема: огромный размер пакета, долгий cold start, невозможность масштабировать отдельные эндпоинты.

Решение: одна функция = одна ответственность.

2. Lambda-to-Lambda Synchronous Calls

Client → Lambda A → Lambda B → Lambda C → Response
         ↓ waiting   ↓ waiting   ↓ working

Проблема: каждая Lambda платит за ожидание. Cascading latency, double billing.

Решение: асинхронная коммуникация через SQS/SNS/EventBridge или Step Functions.

3. Serverless Monolith

Весь монолит в одной Lambda. Получаем все минусы serverless (лимиты, cold start) без плюсов (гранулярное масштабирование).

4. Ignoring Cold Start в пути критических запросов

Использование Java Lambda для API с SLA <100ms без Provisioned Concurrency.

5. Overuse для стабильной нагрузки

24/7 нагрузка 1000 RPS
Lambda: ~$800/месяц
ECS Fargate: ~$200/месяц
EC2 Reserved: ~$50/месяц

Serverless паттерны

Event-Driven Processing

S3 Upload → Lambda (resize) → S3 (thumbnails)
         → Lambda (metadata) → DynamoDB
         → Lambda (notify) → SNS → Email

API Gateway + Lambda

API Gateway → Lambda → DynamoDB
           → Lambda → RDS Proxy → PostgreSQL
           → Lambda → S3

Fan-Out / Fan-In

SNS Topic → Lambda 1 (email)
          → Lambda 2 (push notification)
          → Lambda 3 (analytics)
          → SQS → Lambda 4 (batch processing)

Step Functions — оркестрация

# AWS Step Functions — state machine
Comment: "Order processing workflow"
StartAt: ValidateOrder
States:
  ValidateOrder:
    Type: Task
    Resource: "arn:aws:lambda:...:validate"
    Next: CheckInventory
    Catch:
      - ErrorEquals: ["ValidationError"]
        Next: RejectOrder

  CheckInventory:
    Type: Task
    Resource: "arn:aws:lambda:...:check-inventory"
    Next: ProcessPayment

  ProcessPayment:
    Type: Task
    Resource: "arn:aws:lambda:...:process-payment"
    Retry:
      - ErrorEquals: ["PaymentTimeout"]
        MaxAttempts: 3
        BackoffRate: 2
    Next: FulfillOrder

  FulfillOrder:
    Type: Task
    Resource: "arn:aws:lambda:...:fulfill"
    End: true

  RejectOrder:
    Type: Task
    Resource: "arn:aws:lambda:...:reject"
    End: true

Итоги

Концепция Суть
Serverless Полная абстракция инфраструктуры
FaaS Функции по событиям (Lambda)
BaaS Управляемые backend-сервисы
Cold Start Задержка инициализации при первом вызове
Provisioned Concurrency Предварительный прогрев Lambda
Step Functions Оркестрация serverless-функций

Правило выбора: Serverless — не серебряная пуля. Если нагрузка стабильная и высокая, containers (ECS/Kubernetes) будут дешевле. Serverless идеален для event-driven, переменной нагрузки и быстрых прототипов.


Проверь себя

Какой язык программирования обеспечивает минимальный Cold Start на AWS Lambda?

Какой anti-pattern описывает синхронный вызов Lambda из другой Lambda?

Для чего используется Provisioned Concurrency в AWS Lambda?

Когда Serverless экономически НЕвыгоден?

Что такое Cold Start в контексте AWS Lambda?