Lambda Powertools TypeScript стал общедоступным

Первый взгляд на Lambda Powertools TypeScript я сделал еще в январе 2022 года. В то время я был очень рад этой библиотеке, но она сопровождалась предупреждением, что еще не готова к использованию в производстве. Объявление об общей доступности появилось 15 июля, так что пришло время взглянуть еще раз.

Оглавление

  • Что изменилось?
  • Поддержка ES-модулей
  • Сравнения
  • Логгер
  • Метрики
  • Трассировщик
  • Дорожная карта
  • Заключение

Что изменилось

Итак, что же изменилось в бета-версии? Взглянув на CHANGELOG, можно ответить, что за шесть месяцев изменилось не так уж много. Lambda Powertools TypeScript по-прежнему поддерживает декораторы классов, middy и ручной API. Он по-прежнему охватывает основные возможности протоколирования, метрики и трассировки, и никаких новых возможностей добавлено не было. За исключением некоторых исправлений ошибок и оптимизации, это все еще очень похожая библиотека, которую я предварительно рассматривал в январе.

Поддержка ES-модулей

Одним из изменений, которое я хотел бы увидеть, является поддержка ES-модулей. Интерес к ES Modules быстро растет в сообществе serverless, в основном из-за желания использовать Top-Level Await.

Lambda Powertools TypeScript не может быть использован напрямую как зависимость ES Modules, но вопрос открыт, поэтому, пожалуйста, подумайте о добавлении своего +1. Пока что можно обойтись require shim или трюками cjs, но было бы здорово увидеть встроенную поддержку ES Modules в Lambda Powertools TypeScript.

Сравнения

Учитывая, что я уже рассматривал эти модули в предыдущем посте, я решил сравнить модули Lambda Powertools TypeScript с аналогичными решениями. Я смотрю на API, размер поставляемого скрипта, холодный старт и время выполнения. Для сбора метрик я написал небольшое приложение с использованием Step Functions, которое может запускать множество экземпляров функции параллельно и снимать метрики.

Мой инструмент бенчмаркинга будет запускать каждую функцию 50 раз, стремясь достичь 20-процентного показателя холодного запуска. Примеры кода доступны на GitHub.

Логгер

Самый простой способ вести журнал в CloudWatch из AWS Lambda — это console.log. Это не увеличивает объем вашей функции, отсутствует управление зависимостями, а запись в CloudWatch происходит асинхронно, поэтому операция не блокируется. Многие разработчики используют библиотеки для обеспечения структурированного формата журналов и контроля за многословностью протоколирования.

Тем не менее, мы можем использовать console в качестве базового уровня из-за его простоты. Если нам нужна функция, которая просто пишет неструктурированные журналы, мы можем сделать что-то вроде этого.

import type {
  APIGatewayProxyEventV2,
  APIGatewayProxyResultV2,
  Context,
} from 'aws-lambda';

export const handler = async (
  event: APIGatewayProxyEventV2,
  context: Context
): Promise<APIGatewayProxyResultV2> => {
  console.log('event: ', event);
  console.log('context: ', context);
  return { statusCode: 200 };
};
Войти в полноэкранный режим Выход из полноэкранного режима

Выход из журнала событий дает строгированный контекстный объект:

2022-07-19T12:10:45.503Z    2abe532e-2b26-46b5-9a65-884363160556    INFO    context:  {
  callbackWaitsForEmptyEventLoop: [Getter/Setter],
  succeed: [Function (anonymous)],
  fail: [Function (anonymous)],
  done: [Function (anonymous)],
  functionVersion: '$LATEST',
  functionName: 'LoggerConsole',
  memoryLimitInMB: '128',
  logGroupName: '/aws/lambda/LoggerConsole',
  logStreamName: '2022/07/19/[$LATEST]384dbd25ffeb4af49bc22c2ac4f333df',
  clientContext: undefined,
  identity: undefined,
  invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456790:function:LoggerConsole',
  awsRequestId: '2abe532e-2b26-46b5-9a65-884363160556',
  getRemainingTimeInMillis: [Function: getRemainingTimeInMillis]
}
Вход в полноэкранный режим Выход из полноэкранного режима

Это довольно шумно, и наличие этих методов succeed, fail и done не дает большой ценности.

С помощью Powertools мы можем добавить более полезный контекст в сообщения журнала.

import { Logger } from '@aws-lambda-powertools/logger';

import type { LambdaInterface } from '@aws-lambda-powertools/commons';
import type { APIGatewayProxyEventV2, Context } from 'aws-lambda';

const logger = new Logger();

class Lambda implements LambdaInterface {
  @logger.injectLambdaContext({ logEvent: true })
  public async handler(
    _event: APIGatewayProxyEventV2,
    _context: Context
  ): Promise<void> {
    logger.info('Here is some info!');
  }
}

export const myFunction = new Lambda();
export const handler = myFunction.handler;
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мы получаем структурированные журналы.

{
    "cold_start": false,
    "function_arn": "arn:aws:lambda:us-east-1:123456790:function:LoggerPowertools",
    "function_memory_size": 128,
    "function_name": "LoggerPowertools",
    "function_request_id": "6eeaa0c9-e58f-45a7-bed2-a4b9d7e65d7e",
    "level": "INFO",
    "message": "Here is some info!",
    "service": "service_undefined",
    "timestamp": "2022-07-19T12:09:28.537Z",
    "xray_trace_id": "1-62d69ef6-dfdbc4be0f59a76c57c52cf8"
}
Вход в полноэкранный режим Выход из полноэкранного режима

Это намного проще для поиска и не включает бесполезные структурированные методы. Кроме того, мы получаем булеву cold_start.

Для сравнения я привел еще одну реализацию этой функции с использованием популярной и долговечной библиотеки winston. Давайте посмотрим, что у них получилось.

Функция Avg Cold Start Avg Duration Размер кода
LoggerConsole 137.24 1.63 771
LoggerPowertools 157.58 1.86 78550
LoggerWinston 184.17 1.62 233142

Версия без зависимостей всегда будет самой быстрой. Powertools добавляет около 78 кб, в то время как winston намного тяжелее — 232 кб. В любом случае мы не добавляем много задержки, но Powertools меньше и, следовательно, быстрее, и это дает нам метрику cold_start.

Метрики

Часто, когда речь заходит о метриках, мы думаем о CPU, задержках и других рабочих метриках, и сервисы AWS обычно предоставляют их из коробки. Такое мышление может быть ошибочным, когда в итоге нам приходится использовать сторонние сервисы, такие как google analytics, для вывода критических бизнес-событий. Более простое решение заключается в том, чтобы приложение выдавало метрику при наступлении бизнес-события (например, регистрации клиента). У нас есть несколько вариантов, как это сделать: Мы можем использовать aws-sdk, мы можем использовать aws-embedded-metrics lib и теперь мы можем использовать Powertools Metrics. Какой вариант лучше? Давайте посмотрим.

Чтобы определить базовый уровень, давайте воспользуемся функцией, которая не испускает метрики.

import type { APIGatewayProxyEventV2, Context } from 'aws-lambda';

export const handler = async (
  _event: APIGatewayProxyEventV2,
  _context: Context
): Promise<void> => {
  const workflowSuccess = Math.random() > 0.5;
  if (workflowSuccess) {
    console.log('The workflow was successful!');
  } else {
    console.log('The workflow failed.');
  }
};
Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы понять, успешен ли наш рабочий процесс, нам потребуется запросить журналы. Уф!

Давайте попробуем выдать метрики с помощью библиотеки @aws-sdk/client-cloudwatch из aws-sdk-v3.

import {
  CloudWatchClient,
  MetricDatum,
  PutMetricDataCommand,
} from '@aws-sdk/client-cloudwatch';

import type { APIGatewayProxyEventV2, Context } from 'aws-lambda';

const client = new CloudWatchClient({});

export const handler = async (
  _event: APIGatewayProxyEventV2,
  _context: Context
): Promise<void> => {
  const workflowSuccess = Math.random() > 0.5;
  let metric: MetricDatum;
  if (workflowSuccess) {
    console.log('The workflow was successful!');
    metric = { MetricName: 'WorkflowSuccess', Value: 1, Unit: 'Count' };
  } else {
    console.log('The workflow failed.');
    metric = { MetricName: 'WorkflowFailure', Value: 1, Unit: 'Count' };
  }
  const command = new PutMetricDataCommand({
    MetricData: [metric],
    Namespace: 'SdkV3Metrics',
  });
  await client.send(command);
};
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь у нас есть несколько хороших метрик в CloudWatch!

Недостатком использования aws-sdk для этого является то, что он полагается на вызовы API и является несколько медленным. Мы можем попытаться добиться того же, используя aws-embedded-metrics. Чем это отличается от пользовательских метрик Cloudwatch? Она лучше, и коллега из Community Builder Вишну Прассад расскажет вам почему.

import { createMetricsLogger, Unit } from 'aws-embedded-metrics';

import type { APIGatewayProxyEventV2, Context } from 'aws-lambda';

export const handler = async (
  _event: APIGatewayProxyEventV2,
  _context: Context
): Promise<void> => {
  const workflowSuccess = Math.random() > 0.5;
  const metrics = createMetricsLogger();
  metrics.putDimensions({ Service: 'EMF' });
  if (workflowSuccess) {
    metrics.putMetric('WorkflowSuccess', 1, Unit.Count);
  } else {
    metrics.putMetric('WorkflowFailure', 1, Unit.Count);
  }
  await metrics.flush();
};
Вход в полноэкранный режим Выход из полноэкранного режима

Помимо преимущества EMF, код стал немного лаконичнее.

import { LambdaInterface } from '@aws-lambda-powertools/commons';
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';

import type { APIGatewayProxyEventV2, Context } from 'aws-lambda';

const metrics = new Metrics({ namespace: 'Workflow' });

class Lambda implements LambdaInterface {
  @metrics.logMetrics()
  public async handler(
    _event: APIGatewayProxyEventV2,
    _context: Context
  ): Promise<void> {
    const workflowSuccess = Math.random() > 0.5;
    if (workflowSuccess) {
      metrics.addMetric('WorkflowSuccess', MetricUnits.Count, 1);
    } else {
      metrics.addMetric('WorkflowFailure', MetricUnits.Count, 1);
    }
  }
}

export const myFunction = new Lambda();
export const handler = myFunction.handler;
Вход в полноэкранный режим Выход из полноэкранного режима

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

Функция Avg Cold Start Avg Duration Размер кода
МетрикаНет 135.25 0.93 826
MetricsEMF 146.75 1.33 29456
MetricsSDKV3 225.37 32 257026
MetricsPowertools 141.3 1.33 7942

Я мог бы подумать, что реализация Powertools здесь обернет aws-embedded-metrics, но, очевидно, это не так! Хотя фактическое преимущество Powertools в производительности по сравнению с aws-embedded-metrics незначительно, вы должны оценить, как они уменьшили размер.

Tracer

В случае с модулем Tracer, он действительно обернут aws-xray-sdk. Так почему же мы должны использовать Tracer вместо него? Если API приятнее, и он не добавляет много задержек, то это может стоить того. Вот пример использования aws-xray-sdk. В этом случае лямбда-функция отслеживает отдельную функцию, а также вызов SDK для (несколько бесполезного) получения свойств функции.

import {
  GetFunctionCommand,
  GetFunctionCommandOutput,
  LambdaClient,
} from '@aws-sdk/client-lambda';
import { captureAsyncFunc, captureAWSv3Client } from 'aws-xray-sdk-core';

import type { Context } from 'aws-lambda';
const client = new LambdaClient({});

captureAWSv3Client(client);

const getFunction = async (
  context: Context
): Promise<GetFunctionCommandOutput> => {
  const command = new GetFunctionCommand({
    FunctionName: context.functionName,
  });
  return client.send(command);
};

export const handler = (
  _event: unknown,
  context: Context
): Promise<GetFunctionCommandOutput> =>
  captureAsyncFunc('methodWithCustomTrace', async (subsegment) => {
    const fn = await getFunction(context);
    subsegment?.close();
    return fn;
  });
Вход в полноэкранный режим Выход из полноэкранного режима

Эта часть captureAsyncFunc немного неудобна. Как Powertools делает это?

import { LambdaInterface } from '@aws-lambda-powertools/commons';
import { Tracer } from '@aws-lambda-powertools/tracer';
import {
  GetFunctionCommand,
  GetFunctionCommandOutput,
  LambdaClient,
} from '@aws-sdk/client-lambda';

import type { Context } from 'aws-lambda';

const client = new LambdaClient({});

const tracer = new Tracer({ serviceName: 'powertoolsTracer' });
tracer.captureAWSv3Client(client);

class Lambda implements LambdaInterface {
  @tracer.captureMethod()
  public async methodWithCustomTrace(
    context: Context
  ): Promise<GetFunctionCommandOutput> {
    const command = new GetFunctionCommand({
      FunctionName: context.functionName,
    });
    return client.send(command);
  }

  @tracer.captureLambdaHandler()
  public async handler(
    _event: unknown,
    context: Context
  ): Promise<GetFunctionCommandOutput> {
    return this.methodWithCustomTrace(context);
  }
}

export const handlerClass = new Lambda();
export const handler = handlerClass.handler;
Вход в полноэкранный режим Выход из полноэкранного режима

Я действительно начинаю относиться к декораторам классов для подобных случаев. Декораторы функций были бы лучше, но, как уже говорилось, в TypeScript их пока не существует.

Функция Avg Cold Start Avg Duration Размер кода
TracerXRay 266.91 50.56 410514
TracerPowertools 265.42 48.25 417980

Проверяя показатели производительности, можно заметить, что Powertools очень мал, его вес составляет около 7 кб, и он практически не влияет на производительность. В данном случае Powertools немного быстрее, но я подозреваю, что в долгосрочной перспективе это обойдется в пару мс, так что это стоит того ради devexp.

Дорожная карта

И последнее, что следует учесть при принятии решения о внедрении Lambda Powertools, это то, какие функции могут появиться в будущем. Более зрелые библиотеки Lambda Powertools Python и Lambda Powertools Java включают ряд полезных утилит, которые мы, возможно, хотели бы видеть в Lambda Powertools TypeScript. Общая дорожная карта Lambda Powertools Roadmap не говорит нам многого, кроме ожидаемых библиотек dotnet и golang, но мы можем углубиться в специфические для TypeScript вопросы и заглянуть в ближайшее будущее. Похоже, что стабильность по-прежнему является главным приоритетом, но есть и несколько интересных пунктов, например RFC: Testing Factories for AWS Data Objects в крайней левой колонке.

Участие сообщества и голосование, несомненно, помогут в разработке дорожной карты Powertools.

Заключение

Для меня это не имеет смысла. Одна из проблем при написании Lambda заключается в том, что многие из наших зависимостей не были предназначены для Lambda. Эта библиотека, очевидно, была такой, и команда явно позаботилась о том, чтобы предоставить максимальную ценность в минимальном пакете. Эти основные утилиты продвигают лучшие практики в доступной и простой для использования форме. Пишете ли вы на TypeScript или JavaScript, вы можете наслаждаться хорошей поддержкой IDE и высокоуровневым API для реализации протоколирования, метрик и трассировки в Lambda.

ОБЛОЖКА

Оцените статью
devanswers.ru
Добавить комментарий