Оптимізація Cold Start для Serverless Functions

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.
Розробка та обслуговування будь-яких видів сайтів:
Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Оптимізація Cold Start для Serverless Functions
Середня
~2-3 робочих дні
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Оптимізація Cold Start для Serverless Functions

Cold start — це затримка при першому виклику Lambda після періоду простою або при масштабуванні на новий екземпляр. Складається з кількох фаз: створення контейнера (~100–500ms), ініціалізація runtime (~50–200ms для Node.js), виконання init-коду вашої функції (залежить від вас). Перші дві фази майже не поддаються оптимізації — все, що можна зробити, це працювати з третьою.

Реальні цифри для Node.js 20 на AWS Lambda: без оптимізацій init-фаза займає 300–800ms. Після оптимізацій — 50–150ms. На arm64 (Graviton2) runtime-фаза на 10–20% швидше x86.

Що відбувається при cold start

Фази cold start:
1. Container init    │ ~100-500ms  │ AWS керує, не оптимізується
2. Runtime init      │ ~50-200ms   │ залежить від runtime та архітектури
3. Function init     │ ВАШ КОД     │ require/import, DB-підключення, SDK init
4. Handler execution │ ВАШ КОД     │ собственно робота функції

Профайлювання init-фази через змінну окруження:

# Додаємо в Lambda environment
AWS_LAMBDA_EXEC_WRAPPER=/opt/aws-lambda-exec-wrapper
# або використовуємо вбудоване профайлювання

Найкращий спосіб замірити — CloudWatch Logs. Шукаємо рядок Init Duration:

REPORT RequestId: abc123
Duration: 45.23 ms
Billed Duration: 46 ms
Memory Size: 512 MB
Max Memory Used: 89 MB
Init Duration: 312.45 ms  ← це cold start overhead

Зменшення розміру бандла

Головна причина повільного cold start — великий бандл з непотрібними модулями.

До оптимізації:

// Плохо — імпортуємо весь AWS SDK
import AWS from 'aws-sdk';
const s3 = new AWS.S3();
const dynamo = new AWS.DynamoDB.DocumentClient();

Після:

// Хорошо — лише потрібні клієнти, AWS SDK v3 модульний
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';

// Ініціалізуємо один раз поза handler
const s3 = new S3Client({ region: process.env.AWS_REGION });
const dynamo = DynamoDBDocumentClient.from(new DynamoDBClient({}));

Вилучення AWS SDK з бандла (він вже є в Lambda runtime для Node.js 18+):

// esbuild конфіг
{
  "external": ["@aws-sdk/*"],
  "bundle": true,
  "minify": true,
  "target": "node20",
  "platform": "node"
}

Розміри до/після. Типовий Express-приложення з aws-sdk v2:

  • До: 8–15 MB zip
  • Після: 500KB–2MB zip

Lazy loading для рідко використовуваних модулів

// Плохо — все завантажується при init
import { createCanvas } from 'canvas';
import sharp from 'sharp';
import { PDFDocument } from 'pdf-lib';

export const handler = async (event) => {
  if (event.type === 'generate-pdf') {
    // використовується 5% вызовів
    const pdf = await PDFDocument.create();
  }
};

// Хорошо — важкі модулі лише коли потрібні
export const handler = async (event) => {
  if (event.type === 'generate-pdf') {
    const { PDFDocument } = await import('pdf-lib');
    const pdf = await PDFDocument.create();
  }
};

Ініціалізація поза handler

Код поза handler виконується один раз при cold start та переиспользуется між гарячими вызовами:

import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

// INIT PHASE — один раз
const client = new DynamoDBClient({
  // Переиспользуємо TCP-соединення
  requestHandler: {
    requestTimeout: 3000,
    httpsAgent: { keepAlive: true, maxSockets: 50 },
  },
});
const dynamo = DynamoDBDocumentClient.from(client);

// Читаємо змінні окружения один раз
const TABLE_NAME = process.env.TABLE_NAME!;
const STAGE = process.env.STAGE ?? 'dev';

// HANDLER — виклікається кожен раз
export const handler = async (event) => {
  // dynamo уже ініціалізований, соединення переиспользуется
  const result = await dynamo.send(new GetCommand({
    TableName: TABLE_NAME,
    Key: { pk: event.userId, sk: 'profile' },
  }));

  return result.Item;
};

Оптимізація підключень до бази даних

Обичний пул соединень (pg Pool, Sequelize) не працює в serverless — кожен екземпляр Lambda створює своє підключення, при 1000 concurrent executions отримуємо 1000 підключень до PostgreSQL.

Рішення 1: RDS Proxy (AWS-managed connection pooler):

// Підключаємось до RDS Proxy, не напрямую до RDS
import { Pool } from 'pg';

const pool = new Pool({
  host: process.env.RDS_PROXY_ENDPOINT,  // xxx.proxy-xxx.rds.amazonaws.com
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  max: 1,          // 1 соединение на экземпляр Lambda
  idleTimeoutMillis: 0,
  connectionTimeoutMillis: 5000,
});

// Переиспользуємо между горячими вызовами
let cachedClient: pg.PoolClient | null = null;

export const getDbClient = async () => {
  if (!cachedClient) {
    cachedClient = await pool.connect();
  }
  return cachedClient;
};

Рішення 2: PlanetScale або Neon — HTTP-based serverless databases без постійного соединення:

import { neon } from '@neondatabase/serverless';

// Кожен запит — HTTP, не TCP
const sql = neon(process.env.DATABASE_URL!);

export const handler = async (event) => {
  const users = await sql`SELECT id, email FROM users WHERE active = true LIMIT 10`;
  return users;
};

Provisioned Concurrency

Provisioned Concurrency — AWS тримає N екземплярів Lambda всегда прогрітими. Init-фаза виконується заранее, користувач отримує відповідь без затримки.

# serverless.yml або SAM template
Resources:
  ApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      # ...
      AutoPublishAlias: live

  ApiProvisionedConcurrency:
    Type: AWS::Lambda::ProvisionedConcurrencyConfig
    Properties:
      FunctionName: !Ref ApiFunction
      Qualifier: live
      ProvisionedConcurrentExecutions: 5  # тримаємо 5 гарячих екземплярів

Через Serverless Framework:

functions:
  api:
    handler: src/handler.main
    provisionedConcurrency: 5
    # Автоматичне масштабування через Application Auto Scaling
    # налаштовується окремо через AWS Console або CDK

Provisioned Concurrency коштує грошей (оплачується навіть в idle), тому використовується лише для критичних ендпоінтів.

arm64 vs x86_64

Переключення на Graviton2 (arm64) — найпростіша оптимізація з нульовими змінами коду:

# SAM template
Globals:
  Function:
    Architectures: [arm64]  # було x86_64

Виигріш: ~10–20% менше init duration, ~20% дешевше по тарифах AWS. Єдине обмеження: нативні Node.js модулі (.node файли) потрібно пересобрати під arm64. Звичайні JS/TS модулі працюють без змін.

Терміни

Аудит та базова оптимізація бандла (esbuild, tree shaking, вилучення AWS SDK) — 1 день. Переробка ініціалізації з вилученням клієнтів з handler, настройка RDS Proxy — 2–3 дні. Настройка Provisioned Concurrency з моніторингом та автоскейлингом — 1–2 дні. Повний цикл: від аудиту до production з вимірюваним результатом — 1 тиждень.