Реалізація Serverless Warming для зменшення латентності
Cold start — головна проблема serverless функцій в latency-sensitive приложеннях. Перший виклик після періоду бездіяльності займає 200ms-2s залежно від runtime, розміру пакета та конфігурації. Для API, що обробляє реальні запити користувачів, це неприйнятно. Warming вирішує проблему, підтримуючи функції «гарячими».
Природа cold start
Що відбувається при cold start:
- Хмара знаходить доступний контейнер/VM
- Завантажує образ функції
- Ініціалізує runtime (Node.js, Python, JVM)
- Виконує initialization code (поза handler)
- Виконує handler
Кроки 1-4 — overhead cold start. Кроки 1-3 контролює провайдер, кроки 4-5 — розробник.
Типові часи cold start:
- Python 3.12 (AWS Lambda, 256MB) — 200-400ms
- Node.js 20 — 100-300ms
- Java 17 — 800ms-2s (JVM startup)
- Go — 50-150ms
Scheduled Warming
Найпростіший підхід: запускати функцію кожні 5 хвилин через CloudWatch Events / EventBridge, щоб вона не остигала.
# lambda_warmer.py — ping-функція
import json
def handler(event, context):
if event.get('source') == 'warming':
# Це ping від warmers, не реальний запит
return {'statusCode': 200, 'body': json.dumps({'warm': True})}
# Реальна логіка функції
return process_request(event)
# Terraform: CloudWatch rule для warming
resource "aws_cloudwatch_event_rule" "warmer" {
name = "lambda-warmer"
schedule_expression = "rate(5 minutes)"
}
resource "aws_cloudwatch_event_target" "warmer" {
rule = aws_cloudwatch_event_rule.warmer.name
arn = aws_lambda_function.api.arn
input = jsonencode({"source": "warming"})
}
Обмеження: кожен EventBridge trigger запускає лише один concurrent інстанс. При кількох бажаних гарячих інстансах потрібно N паралельних викликів.
Warming кількох паралельних інстансів
import boto3
import asyncio
lambda_client = boto3.client('lambda')
async def warm_instance(function_name: str, instance_num: int):
lambda_client.invoke(
FunctionName=function_name,
InvocationType='RequestResponse',
Payload=json.dumps({
'source': 'warming',
'instance': instance_num,
'sleep': 10 # Тримаємо інстанс занятим 10 секунд
})
)
async def warm_function(function_name: str, concurrent_count: int = 5):
"""Запустити N паралельних warmup викликів"""
tasks = [warm_instance(function_name, i) for i in range(concurrent_count)]
await asyncio.gather(*tasks)
Поки один виклик тримає інстанс занятим (sleep 10s), Lambda створює новий контейнер для наступного паралельного виклику. Результат: 5 гарячих інстансів.
AWS Lambda Provisioned Concurrency
Офіційне рішення від AWS: резервування ініціалізованих інстансів. За доплату, але гарантує P99 latency без cold start.
resource "aws_lambda_provisioned_concurrency_config" "api" {
function_name = aws_lambda_function.api.function_name
qualifier = aws_lambda_alias.live.name
provisioned_concurrent_executions = 5
}
Auto Scaling Provisioned Concurrency — масштабувати провіжніннінг за розкладом (вранці більше, вночі менше):
resource "aws_appautoscaling_target" "lambda_pc" {
max_capacity = 20
min_capacity = 2
resource_id = "function:${aws_lambda_function.api.function_name}:live"
scalable_dimension = "lambda:function:ProvisionedConcurrency"
service_namespace = "lambda"
}
resource "aws_appautoscaling_policy" "lambda_pc_tracking" {
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.lambda_pc.resource_id
scalable_dimension = aws_appautoscaling_target.lambda_pc.scalable_dimension
service_namespace = aws_appautoscaling_target.lambda_pc.service_namespace
target_tracking_scaling_policy_configuration {
target_value = 0.7 # 70% utilization провіжніннінгу
predefined_metric_specification {
predefined_metric_type = "LambdaProvisionedConcurrencyUtilization"
}
}
}
Оптимізація initialization code
Warming допомагає, але зменшення самого cold start — краща стратегія:
# ПЛОХО: створювати клієнти всередині handler
def handler(event, context):
dynamodb = boto3.resource('dynamodb') # Кожен cold start
db_client = psycopg2.connect(DSN) # Створює connection
...
# ХОРОШО: створювати клієнти на рівні модуля (один раз)
import boto3
import psycopg2
dynamodb = boto3.resource('dynamodb') # Ініціалізується при cold start
_connection = None # Lazy connection pool
def get_connection():
global _connection
if _connection is None or _connection.closed:
_connection = psycopg2.connect(DSN)
return _connection
def handler(event, context):
conn = get_connection() # Переиспользує існуюче соединення
...
Lambda SnapStart (Java)
AWS Lambda SnapStart для Java: створює снапшот ініціалізованого стану функції. Cold start для Java скорочується з 1-2s до 100-200ms.
resource "aws_lambda_function" "java_api" {
...
snap_start {
apply_on = "PublishedVersions"
}
}
Терміни реалізації
- Scheduled warming (EventBridge) — 0.5 дня
- Parallel warming скрипт — 1 день
- Provisioned Concurrency + Auto Scaling — 1-2 дні
- Оптимізація initialization code — 1-3 дні (залежить від кодової бази)







