AWS Lambda로 구축하는 AI 텍스트 생성 API 오토스케일링 가이드
AWS Lambda, API Gateway, SQS를 조합해 게임 서비스용 AI 텍스트 생성 API를 오토스케일링하는 방법을 정리합니다. 동시성 제어, 비동기 처리, 재시도, 비용 산정과 운영 지표까지 실무 관점에서 다룹니다.

AI 텍스트 생성 API를 확장할 때는 요청량과 함께 외부 모델 API의 속도 제한, Lambda 동시성, 재시도 트래픽을 고려해야 한다. 이 글은 API Gateway와 Lambda를 진입점으로 두고 오래 걸리는 생성 요청을 SQS로 분리하는 설계 예시다. 동시 실행 수와 분당 요청·토큰 수는 단위가 다르므로, 동시성 상한 외에도 동기·비동기 호출과 재시도가 공유하는 RPM·TPM 예산을 관리한다.
게임용 AI 텍스트 생성 API에 오토스케일링이 필요한 이유
NPC 대사, 퀘스트 설명, 운영 공지 초안의 생성 요청은 플레이어 이벤트나 라이브 운영 도구에서 한꺼번에 몰릴 수 있다. 인스턴스 기반 서버에도 자동 확장을 적용할 수 있다. Lambda의 온디맨드 과금은 요청 수와 메모리 크기를 반영한 실행 시간 등을 기준으로 하며, 추가 기능의 비용은 별도로 확인한다. AWS Lambda 요금
Lambda가 자동 확장돼도 모델 공급자의 처리 한도가 함께 늘어나지는 않는다. 실제 모델 요청률이나 토큰 사용률이 한도를 넘으면 제한 응답이 발생할 수 있고, 재시도가 추가 부하를 만들 수 있다. 제한 단위와 429의 원인은 사용하는 공급자의 문서와 오류 본문으로 확인한다.
| 구성 요소 | 맡는 역할 | 주의할 제한 |
|---|---|---|
| Amazon API Gateway HTTP API | 인증된 클라이언트 요청 수신 | 요청 제한, 인증·인가 |
| AWS Lambda | 요청 검증, 모델 호출, 결과 저장 | Timeout, Memory, Concurrency |
| Amazon SQS | 오래 걸리는 생성 작업의 버퍼 | Visibility Timeout, DLQ |
| Amazon DynamoDB | 작업 상태와 결과 저장 | 파티션 키 설계, TTL |
| 외부 AI 모델 API | 텍스트 생성 | RPM, TPM, 요청 크기 |

이미지는 서비스 구성 요소를 표현한 일러스트다. 화살표는 실제 AWS 데이터 흐름의 명세가 아니며, 별도의 Worker나 내구성 있는 작업 전달 절차도 모두 표현하지 않는다.
AWS Lambda 기반 AI 생성 API는 어떻게 구성할까?
동기 응답이 반드시 필요한 짧은 요청과 결과를 나중에 받아도 되는 긴 요청을 분리한다. 예를 들어 운영자 도구의 한 줄 NPC 대사는 동기 처리할 수 있지만 수백 명의 NPC 대사를 생성하는 배치 작업은 비동기 처리하는 편이 낫다.
flowchart LR
C[게임 클라이언트 또는 운영 도구] --> G[API Gateway]
G --> A[Lambda: 인증·검증]
A -->|짧은 요청| L[Lambda: AI 모델 호출]
A -->|작업과 발행 의도 함께 저장| O[DynamoDB 작업·Outbox]
O --> P[발행기: 미발행 작업 재시도]
P --> Q[SQS]
Q --> W[Lambda: Worker]
L --> D[DynamoDB]
W --> D
L --> M[AI 텍스트 모델 API]
W --> M
R[작업 결과 조회 API: 소유자 인가] --> D
Step 1. API Gateway에서 인증과 요청 크기를 먼저 제한한다
이 설계에서는 클라이언트가 임의의 전체 프롬프트를 지정하게 하지 않고 templateId, locale, characterId를 서버의 허용 목록과 권한으로 검증한다. 구조화된 입력만으로 프롬프트 인젝션을 방지할 수는 없다. 신뢰할 지시와 사용자 데이터를 분리하고, 모델 밖에서 데이터 접근 권한을 검사한다. OWASP 프롬프트 인젝션 방어 지침
- HTTP API의 JWT Authorizer에 Cognito 등의 발급자·대상·필요한 경로별 범위를 설정하거나, 서버 간 호출에 IAM 인증을 구성한다. 인증 후에도 캐릭터와 작업 결과의 소유자 권한은 각 API에서 검사한다. HTTP API JWT Authorizer
- 요청 본문 크기를 제한하고 서버에서 최대 토큰 수를 결정한다.
- 캐릭터 설정과 퀘스트 데이터는 권한을 확인한 뒤 게임 백엔드에서 조회한다. DB에 저장돼 있다는 이유만으로 사용자 입력을 신뢰하지 않는다.
- HTTP API의 경로·스테이지·계정 throttling은 최선형 제한이며 엄격한 사용자별 할당량이 아니다. 사용자별 할당량은 인증된 사용자 ID를 기준으로 백엔드에서 원자적으로 차감한다. HTTP API throttling
- AWS WAF는 API Gateway의 Regional REST API에 직접 연결할 수 있으며 HTTP API 직접 연결은 지원하지 않는다. WAF 지원 리소스
아래는 본문 검증 함수이며, 작업을 저장하거나 202를 반환하는 Lambda 핸들러가 아니다. 4,096바이트와 허용 템플릿은 예시 정책이다. 실제 핸들러는 본문 인코딩을 처리하고, 검증·인가·내구성 있는 작업 접수가 완료된 뒤에만 작업 ID를 반환한다. 런타임 예시는 2026년 10월 확인 기준으로 지원 중인 Node.js 22.x를 사용한다. 지원 일정은 배포 시 다시 확인한다. Lambda 런타임
// Node.js 22.x: 디코딩된 JSON 본문에 적용하는 검증 부분
type GenerateInput = { templateId: string };
const allowedTemplates = new Set(["npc-greeting"]);
function parseGenerateInput(body: string): GenerateInput {
if (Buffer.byteLength(body, "utf8") > 4096) throw new Error("body too large");
let input: unknown;
try { input = JSON.parse(body); }
catch { throw new Error("invalid JSON"); }
if (input === null || typeof input !== "object" || Array.isArray(input)) {
throw new Error("expected object");
}
const value = input as Record<string, unknown>;
if (Object.keys(value).some(key => key !== "templateId") ||
typeof value.templateId !== "string" || value.templateId.length === 0 ||
value.templateId.length > 64 || !allowedTemplates.has(value.templateId)) {
throw new Error("invalid templateId or field");
}
return { templateId: value.templateId };
}
Step 2. SQS와 DynamoDB로 생성 작업을 비동기화한다
DynamoDB 저장과 SQS 전송은 하나의 원자적 쓰기가 아니다. 이 설계에서는 작업 상태와 발행 의도를 같은 DB 트랜잭션으로 저장하고, 별도 발행기가 미발행 의도를 SQS로 전달·재시도하는 Outbox 방식을 사용한다. 발행기의 복구·재조정 절차와 소비자의 중복 처리는 별도로 구현해야 한다. AWS Transactional Outbox
Worker는 작업 ID를 기준으로 완료 여부와 처리 임대를 조건부 갱신하고, 현재 임대 버전이 일치할 때만 결과를 저장하는 방식으로 중복 전달을 처리한다. 접수 시에도 사용자·요청 키와 본문 해시로 동일 요청을 구분한다. 클라이언트의 결과 조회는 jobId뿐 아니라 소유자와 만료 시각을 검사한다. 공급자가 멱등 호출을 지원하지 않으면 모델 호출 후 결과 저장 전에 장애가 났을 때 중복 생성·과금을 완전히 막을 수 없다.
다음 레코드는 일부 필드만 보여 준다. 소유자, 요청 해시, 모델·템플릿 버전, 시도 횟수와 임대 정보 등의 설계도 필요하다. expiresAt은 TTL 속성으로 활성화한 Number 타입의 Unix 초 값이며 예시에서는 생성 후 24시간이다. TTL 삭제는 비동기이며 만료 후 며칠이 걸릴 수 있으므로, 접근 만료는 조회 코드에서 따로 적용한다. DynamoDB TTL
{
"jobId": "9bc5d50c-4c9d-49f0-bbf7-2df4a0a9e257",
"status": "QUEUED",
"templateId": "npc-greeting",
"createdAt": "2026-08-06T10:00:00Z",
"expiresAt": 1786096800
}
이벤트 소스 매핑의 필수 조건은 Lambda Timeout이 SQS Visibility Timeout 이하인 것이다. AWS의 운영 권장치는 함수 Timeout의 6배에 배치 윈도 시간을 더한 값 이상이다. 따라서 30초 함수·0초 배치 윈도 예시에서는 180초로 설정한다. 소스 큐의 Redrive Policy와 DLQ를 구성하며, AWS는 maxReceiveCount를 5 이상으로 권장한다. DLQ 재처리 전에는 실패 원인과 기존 완료 여부를 확인한다. SQS 이벤트 소스 설정
Step 3. Lambda 동시성을 모델 API 제한에 맞춘다
SQS 이벤트 소스는 부하에 따라 병렬 호출을 늘릴 수 있다. Reserved Concurrency는 함수 전체 동시성의 상한이자 예약이며, SQS 이벤트 소스의 Maximum Concurrency는 해당 매핑의 상한이다. 여러 매핑이 있다면 상한의 합과 다른 호출 경로를 함께 검토한다. 이것만으로 모델 API의 RPM·TPM 제한을 강제하지는 못한다. Lambda 예약 동시성, SQS 동시성 설정
정상 상태의 평균 처리량을 추정할 때, 작업당 평균 모델 호출 수를 , 분당 요청 예산을 , 사용할 예산 비율을 로 두면 동시성 설정의 출발점을 다음처럼 계산할 수 있다. 이는 평균 호출률과 평균 처리 시간의 관계에 따른 추정이며, 버스트나 재시도를 포함한 엄격한 속도 제한 공식은 아니다.
작업의 평균 처리 시간 는 초 단위이며 양수여야 한다. 예를 들어 평균 처리 시간 6초, 분당 예산 600회, 작업당 호출 1회, 예산 사용 비율 0.8이라면 추정값은 다음과 같다.
이 가정에서는 48을 동시성 설정의 후보로 볼 수 있으나, 요청 한도 준수를 보장하는 수치는 아니다. 계정·모델별 제한 단위를 확인하고, 모든 호출 경로에서 재시도마다 공유된 요청·토큰 예산을 획득해야 한다. 토큰 예약과 실제 사용량 정산도 공급자의 집계 방식에 맞춘다.
# AWS SAM 설정 조각: 핸들러 코드와 IAM 권한 등의 정의는 별도로 필요
Resources:
GenerateDLQ:
Type: AWS::SQS::Queue
GenerateQueue:
Type: AWS::SQS::Queue
Properties:
VisibilityTimeout: 180
RedrivePolicy:
deadLetterTargetArn: !GetAtt GenerateDLQ.Arn
maxReceiveCount: 5
TextGenerationWorker:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs22.x
Timeout: 30
MemorySize: 1024
ReservedConcurrentExecutions: 20
Events:
GenerateQueue:
Type: SQS
Properties:
Queue: !GetAtt GenerateQueue.Arn
BatchSize: 1
MaximumBatchingWindowInSeconds: 0
ScalingConfig:
MaximumConcurrency: 20
FunctionResponseTypes:
- ReportBatchItemFailures
이 예시의 BatchSize: 1은 한 호출에 한 메시지를 전달하도록 한다. 배치 크기와 동시성 20은 프로젝트별로 검증할 시작값이다. ReportBatchItemFailures를 설정했다면 Worker도 실패한 레코드의 messageId를 batchItemFailures[].itemIdentifier로 반환해야 한다. 설정만으로 부분 실패 처리가 구현되지는 않으며, 핸들러가 예외를 던지면 배치 전체가 실패한다. SAM SQS 속성, SQS 부분 실패 응답
429 오류와 재시도 폭주는 왜 발생하는가?
실패 직후 여러 Worker가 재시도하면 부하가 늘 수 있다. 재시도 가능한 오류를 공급자별로 구분하고, 지수 백오프·지터·시도 횟수·전체 시간 예산을 함께 제한한다. 429라고 해서 모두 일시적인 속도 제한인 것은 아니므로 오류 코드와 할당량 상태도 확인한다. AWS 재시도·타임아웃 설계
아래 함수는 서버가 구성한 JSON 객체를 전송하는 부분 예시다. 공급자별 요청·응답 스키마는 구현해야 한다. acquirePermit은 공유 RPM·TPM 예산을 획득하는 별도 구현이다. retryDelayMs는 공급자의 오류 본문과 Retry-After를 해석해 필요한 대기 시간을 반환하고, 재시도 불가이면 null을 반환한다. 두 함수 모두 전달받은 마감 시각 안에 종료해야 한다. 실제 구현은 생략했으므로 이 코드만으로 호출 한도 준수나 공급자 호환성을 보장하지 않는다.
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
type ModelCallOptions = {
url: string; apiKey: string; budgetMs: number;
acquirePermit: (deadlineMs: number) => Promise<void>;
retryDelayMs: (response: Response, deadlineMs: number) => Promise<number | null>;
};
async function callModelWithRetry(payload: Record<string, unknown>, options: ModelCallOptions) {
if (!Number.isFinite(options.budgetMs) || options.budgetMs < 1 ||
options.budgetMs > 25000 || new URL(options.url).protocol !== "https:") {
throw new Error("invalid model call configuration");
}
const deadline = Date.now() + options.budgetMs;
const body = JSON.stringify(payload);
for (let attempt = 0; attempt < 3; attempt += 1) {
await options.acquirePermit(deadline); // 재시도도 공유 예산을 사용한다
const remaining = Math.floor(deadline - Date.now());
if (remaining < 1) throw new Error("model call deadline exceeded");
const response = await fetch(options.url, {
method: "POST",
headers: {
"Authorization": `Bearer ${options.apiKey}`,
"Content-Type": "application/json"
},
body,
signal: AbortSignal.timeout(Math.min(8000, remaining))
});
if (response.ok) return await response.json();
if (attempt === 2) throw new Error("model API attempt limit exceeded");
const providerDelay = await options.retryDelayMs(response, deadline);
if (providerDelay === null) throw new Error(`model API: ${response.status}`);
if (!Number.isFinite(providerDelay) || providerDelay < 0) {
throw new Error("invalid provider retry delay");
}
const waitMs = Math.ceil(Math.max(providerDelay, (2 ** attempt) * 500 + Math.random() * 250));
if (Date.now() + waitMs >= deadline) throw new Error("retry exceeds deadline");
await delay(waitMs);
}
throw new Error("unreachable");
}
이 예시는 최대 3회 시도, 즉 최초 호출과 최대 2회 재시도다. 8초 호출 제한과 최대 25초 예산은 30초 함수의 설정 예시이며, 실제 Worker는 context.getRemainingTimeInMillis()에서 결과 저장 등 후처리 시간을 뺀 예산을 전달한다. Node.js AbortSignal.timeout
네트워크 예외·타임아웃·JSON 파싱 실패는 이 함수 밖으로 전달된다. Worker가 이를 실패로 보고하면 SQS 차원의 재전달이 추가될 수 있으므로 작업별 누적 시도·시간·비용 예산도 관리한다. 호출 취소나 로컬 타임아웃이 공급자의 생성·과금까지 취소한다는 뜻은 아니다. 재시도 설정은 실제 공급자의 계약과 장애 테스트로 검증한다.
비용과 성능은 어떤 지표로 운영할까?
비용 계획에는 Lambda 외에도 모델 사용료, API Gateway, SQS, DynamoDB와 선택한 로그·보안·전송 서비스의 요금을 포함한다. 재시도도 사용량에 반영한다. 이 글은 실제 프로젝트 비용을 측정한 자료가 아니며, 단가와 무료 구간은 사용하는 리전·모델·상품의 최신 요금으로 계산한다. 기능별 templateId, feature, 사용량을 기록해 비용을 집계하는 방안을 제안한다.
| 지표 | 확인할 현상 | 대응 |
|---|---|---|
SQS ApproximateAgeOfOldestMessage | 큐에 남은 메시지의 대략적인 나이 | 처리량·모델 한도 확인, 작업 createdAt으로 전체 지연 별도 측정 |
Lambda Throttles | 동시성 등으로 호출이 거절됨 | 함수·계정 한도와 이벤트 소스 설정 점검 |
Lambda Errors | 함수 호출 수준의 오류 | 코드·예외 로그 확인, 부분 실패 메시지는 별도 사용자 지표로 집계 |
| 모델 API 429 비율 | 공급자가 요청을 제한함 | 공급자 오류 코드와 속도·할당량 상태 확인 |
| 요청당 토큰 사용량·총비용 | 입력·출력·재시도 사용량 증가 | 품질을 검증하며 템플릿·출력 길이·재시도 예산 조정 |
SQS의 메시지 나이에는 반복 실패 메시지의 제외·재정렬과 DLQ 이동 시 기준 변경 등의 예외가 있어, 모든 작업의 최초 대기 시간을 뜻하지 않는다. Lambda Errors도 성공 응답에 포함된 부분 실패를 자동 집계하지 않는다. SQS CloudWatch 지표, Lambda 지표
다음은 알람 정책의 시작 예시다. 임계값과 전달 채널은 서비스 목표와 운영 절차에 맞춰 정한다.
- DLQ 메시지 수가 1 이상일 때 운영 채널에 알림을 보낸다.
- 큐 나이 지표와 작업별 전체 지연이 각각의 서비스 목표를 넘으면 경고한다.
- Lambda 호출 오류·사용자 정의 부분 실패·공급자 오류 비율을 각자의 분모와 평가 구간으로 집계해 알림을 보낸다.
보안과 게임 운영에서 놓치기 쉬운 점
이 설계에서는 API 키를 Secrets Manager에서 읽어 호출 함수에 전달하고 코드·로그에 남기지 않는다. Lambda 환경 변수는 저장 시 KMS로 암호화된다. AWS는 API 키 같은 민감한 값에 Secrets Manager 사용을 권장한다. 환경 변수 권장 사항, 환경 변수 암호화
Lambda 실행 역할에는 실제 사용하는 Secret·DB·큐 등의 리소스와 작업에 필요한 권한만 부여한다. 예제 SAM 조각에는 이 권한과 핸들러·코드 경로가 생략돼 있으므로 완전한 배포 템플릿으로 사용할 수 없다. IAM 최소 권한 지침
생성 텍스트에는 프로젝트 정책에 맞춘 길이·형식·내용 검사를 적용하고, 화면의 HTML·리치 텍스트 문법에 맞게 출력한다. 이름·채팅·길드 소개 같은 사용자 입력은 비신뢰 데이터로 취급한다. 필터와 입력 정규화만으로 안전성을 보장하지 않으며, 감사 로그에는 자격 증명이나 불필요한 민감한 본문을 남기지 않는다.
자주 묻는 질문 (FAQ)
Lambda Provisioned Concurrency가 항상 필요한가?
항상 필요한 것은 아니다. 동기·비동기 경로 각각의 지연 목표를 측정하고 초기화 지연이 병목일 때 추가 비용과 함께 검토한다. Provisioned Concurrency는 실행 환경을 미리 준비하며, Reserved Concurrency만 설정하는 것과 다르다. 동시성 설정, 요금
SQS 대신 Lambda를 API Gateway에서 바로 호출해도 되는가?
가능하다. 다만 동기 요청은 클라이언트·게이트웨이·함수의 제한 시간 안에 완료돼야 한다. 생성 완료를 기다릴 필요가 없는 작업에는 내구성 있는 비동기 접수와 결과 조회를 검토한다. 실제 응답 시간이나 안정성 개선은 통합·부하 테스트로 확인한다.
동시성을 높이면 생성 속도도 항상 빨라지는가?
아니다. 외부 모델 API의 RPM·TPM 제한을 넘으면 429 오류와 재시도가 늘어 전체 완료 시간이 더 길어질 수 있다.
정리
AWS Lambda의 확장 기능과 모델 API의 속도 제한은 함께 설계해야 한다. 이 글은 입력·인가, 내구성 있는 비동기 접수, 공유 호출 예산, 동시성 상한과 제한된 재시도를 조합하는 방안을 제시했다. 실제 모델 계약, 장애 복구, 결과 검증, 비용·지연 목표는 프로젝트의 통합·부하·품질 테스트로 확인해야 한다.



