Lambda Managed Instances용 Node.js 런타임

Lambda Managed Instances용 Node.js 런타임

Node.js 런타임의 경우 Lambda Managed Instances는 async/await 기반 실행과 함께 워커 스레드(worker threads)를 사용해 동시 요청을 처리합니다. 함수 초기화는 워커 스레드마다 한 번 발생합니다. 동시 호출은 두 차원에서 처리됩니다. 워커 스레드는 vCPU에 걸친 병렬 처리를 제공하고, 비동기 실행은 각 스레드 내의 동시성을 제공합니다. 같은 워커 스레드가 처리하는 각 동시 요청은 같은 핸들러 객체와 전역 상태를 공유하므로, 여러 동시 요청 아래에서 안전하게 처리해야 합니다.

출처: AWS Lambda 개발자 안내서

본문

최대 동시성

Lambda가 각 실행 환경에 보내는 최대 동시 요청 수는 함수 구성의 PerExecutionEnvironmentMaxConcurrency 설정으로 제어됩니다. 이는 선택 설정이며 기본값은 런타임에 따라 다릅니다. Node.js 런타임의 기본값은 vCPU당 64개 동시 요청이며, 또는 자신의 값을 구성할 수 있습니다. Lambda는 각 실행 환경이 그 요청을 흡수할 수 있는 용량에 따라 구성된 최대치까지 동시 요청 수를 자동으로 조정합니다.

Node.js에서 각 실행 환경이 처리할 수 있는 동시 요청 수는 워커 스레드 수와 각 워커 스레드가 동시 요청을 비동기적으로 처리할 수 있는 용량에 의해 결정됩니다. 기본 워커 스레드 수는 사용 가능한 vCPU 수에 의해 결정되며, 또는 AWS_LAMBDA_NODEJS_WORKER_COUNT 환경 변수를 설정해 워커 스레드 수를 구성할 수 있습니다. async 함수 핸들러를 사용할 것을 권장합니다. 그러면 워커 스레드당 여러 요청을 처리할 수 있기 때문입니다. 함수 핸들러가 동기적이면 각 워커 스레드는 한 번에 하나의 요청만 처리할 수 있습니다.

멀티 동시성을 위한 함수 구축

async 함수 핸들러를 사용하면 각 런타임 워커가 여러 요청을 동시에 처리합니다. 전역 객체는 여러 동시 요청 간에 공유됩니다. 변경 가능한(mutable) 객체에는 전역 상태 사용을 피하거나 AsyncLocalStorage를 사용하세요.

AWS SDK 클라이언트는 async 안전하며 특별한 처리가 필요하지 않습니다.

예제: 전역 상태 – 다음 코드는 함수 핸들러 안에서 변경되는 전역 객체를 사용합니다. 이는 async 안전하지 않습니다.

let state = {
    currentUser: null,
    requestData: null
};

export const handler = async (event, context) => {
    state.currentUser = event.userId;
    state.requestData = event.data;

    await processData(state.requestData);

    // state.currentUser might now belong to a different request
    return { user: state.currentUser };
};

state 객체를 함수 핸들러 안에서 초기화하면 공유 전역 상태를 피합니다.

export const handler = async (event, context) => {
    let state = {
        currentUser: event.userId,
        requestData: event.data
    };
    
    await processData(state.requestData);

    return { user: state.currentUser };
};

예제: 데이터베이스 연결 – 다음 코드는 여러 호출 간에 공유되는 공유 클라이언트 객체를 사용합니다. 사용하는 연결 라이브러리에 따라 이는 동시성 안전하지 않을 수 있습니다.

const { Client } = require('pg');

// Single connection created at init time
const client = new Client({
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD
});

// Connect once during cold start
client.connect();

exports.handler = async (event) => {
  // Multiple parallel invocations share this single connection = BAD
  // With multi-concurrent Lambda, queries will collide
  const result = await client.query('SELECT * FROM users WHERE id = $1', [event.userId]);
  
  return {
    statusCode: 200,
    body: JSON.stringify(result.rows[0])
  };
};

동시성 안전한 접근 방식은 연결 풀(connection pool)을 사용하는 것입니다. 풀은 각 동시 데이터베이스 쿼리에 별도의 연결을 사용합니다.

const { Pool } = require('pg');

// Connection pool created at init time
const pool = new Pool({
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  max: 20,  // Max connections in pool
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000
});

exports.handler = async (event) => {
  // Pool gives each parallel invocation its own connection
  const result = await pool.query('SELECT * FROM users WHERE id = $1', [event.userId]);
  
  return {
    statusCode: 200,
    body: JSON.stringify(result.rows[0])
  };
};

Node.js 22 콜백 기반 핸들러

Node.js 22를 사용할 때 Lambda Managed Instances에서는 콜백 기반 함수 핸들러를 사용할 수 없습니다. 콜백 기반 핸들러는 Lambda(기본) 함수에서만 지원됩니다. Node.js 24 이상 런타임에서는 콜백 기반 함수 핸들러가 Lambda(기본)와 Lambda Managed Instances 모두에서 더 이상 사용되지 않습니다(deprecated).

대신 Lambda Managed Instances를 쓸 때는 async 함수 핸들러를 사용하세요. 자세한 내용은 'Node.js에서 Lambda 함수 핸들러 정의'를 참고하세요.

공유 /tmp 디렉터리

/tmp 디렉터리는 실행 환경의 모든 동시 요청 간에 공유됩니다. 같은 파일에 대한 동시 쓰기는 다른 프로세스가 파일을 덮어쓰는 경우 데이터 손상을 일으킬 수 있습니다. 이를 해결하려면 공유 파일에 파일 잠금을 구현하거나 요청마다 고유한 파일 이름을 사용해 충돌을 피하세요. 사용 가능한 공간이 소진되지 않도록 필요 없는 파일을 정리하는 것을 기억하세요.

로깅

로그 인터리빙(서로 다른 요청의 로그 항목이 섞이는 것)은 멀티 동시 시스템에서 정상입니다. Lambda Managed Instances를 사용하는 함수는 항상 고급 로깅 제어로 도입된 구조적 JSON 로그 형식을 사용합니다. 이 형식에는 requestId가 포함되어 로그 항목을 단일 요청과 상관시킬 수 있습니다. 콘솔 로거를 사용하면 requestId가 각 로그 항목에 자동으로 포함됩니다. 자세한 내용은 'Node.js에서 Lambda 고급 로깅 제어 사용'을 참고하세요.

Winston 같은 널리 쓰이는 서드파티 로깅 라이브러리는 보통 로그 출력에 console을 사용하는 것을 지원합니다.

요청 컨텍스트

  • context.awsRequestId를 사용하면 현재 요청의 요청 ID에 async 안전하게 접근할 수 있습니다.
  • context.xRayTraceId를 사용해 X-Ray 추적 ID에 접근하세요. 이는 현재 요청의 추적 ID에 동시성 안전하게 접근할 수 있게 합니다. Lambda는 Lambda Managed Instances에서 _X_AMZN_TRACE_ID 환경 변수를 지원하지 않습니다. X-Ray 추적 ID는 AWS SDK 사용 시 자동으로 전파됩니다.
  • context.getRemainingTimeInMillis()를 사용해 타임아웃을 감지하세요. 자세한 내용은 '오류 처리와 복구'를 참고하세요.

예제: 타임아웃 처리 – 각 작업 단위 전에 남은 시간을 확인하고 타임아웃이 발동하기 전에 처리를 중지하세요. 다음 작업 청크의 예상 실행 시간에 따라 BUFFER_MS를 구성하세요.

const BUFFER_MS = 2000; // Configure based on your next chunk of work

exports.handler = async (event, context) => {
    for (const item of event.items) {
        if (context.getRemainingTimeInMillis() < BUFFER_MS)
            return { statusCode: 206, body: "Timeout approaching, stopping early" };
        await processItem(item);
    }
    return { statusCode: 200, body: "Done" };
};

예제: 다운스트림 호출에 데드라인 전파 – 다운스트림 서비스에 호출할 때 호출을 영원히 넘겨 채울 네트워크 호출에 매달리지 않도록 남은 시간을 타임아웃으로 전파하세요.

const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
const client = new S3Client({});

exports.handler = async (event, context) => {
    const timeout = Math.max(1000, context.getRemainingTimeInMillis() - 500);
    const response = await client.send(
        new GetObjectCommand({ Bucket: "my-bucket", Key: "my-key" }),
        { abortSignal: AbortSignal.timeout(timeout) }
    );
    return { statusCode: 200, body: "Done" };
};

초기화와 종료

함수 초기화는 워커 스레드마다 한 번 발생합니다. 함수가 초기화 중 로그를 내보낸다면 반복되는 로그 항목이 보일 수 있습니다.

확장(extensions)이 있는 Lambda 함수의 경우 실행 환경은 종료 중에 SIGTERM 신호를 내보냅니다. 이 신호는 확장이 버퍼 플러시 같은 정리 작업을 트리거하는 데 사용됩니다. 확장이 있는 Lambda(기본) 함수는 process.on()을 사용해 SIGTERM 신호를 구독할 수도 있습니다. 이는 process.on()을 워커 스레드에서 사용할 수 없으므로 Lambda Managed Instances를 사용하는 함수에서는 지원되지 않습니다. 실행 환경 수명주기에 대해 자세히 알아보려면 'Lambda 실행 환경 수명주기 이해'를 참고하세요.

의존성 버전

Lambda Managed Instances에는 다음 최소 패키지 버전이 필요합니다.

  • AWS SDK for JavaScript v3: 버전 3.933.0 이상
  • AWS X-Ray SDK for Node.js: 버전 3.12.0 이상
  • AWS Distro for OpenTelemetry - Instrumentation for JavaScript: 버전 0.8.0 이상
  • Powertools for AWS Lambda (TypeScript): 버전 2.29.0 이상

Powertools for AWS Lambda (TypeScript)

Powertools for AWS Lambda (TypeScript)는 Lambda Managed Instances와 호환되며 로깅, 추적, 지표 등의 유틸리티를 제공합니다. 자세한 내용은 'Powertools for AWS Lambda (TypeScript)'를 참고하세요.

다음 단계

  • Lambda Managed Instances용 Java 런타임 검토
  • Lambda Managed Instances용 Python 런타임 검토
  • Lambda Managed Instances용 .NET 런타임 검토
  • Lambda Managed Instances 확장 알아보기

더 알아보기 (Learn more)