AWS re:Post에서 발견한 최고의 GenAI 문제 해결하기: Amazon Bedrock과 AWS Lambda를 사용한 실시간 토큰 스트리밍
요약
Amazon Bedrock과 AWS Lambda를 활용하여 LLM 스트리밍의 두 가지 주요 문제를 해결하는 방법을 제시합니다. 기존 API Gateway는 응답을 버퍼링하고 29초 제한으로 인해 실시간 토큰 전송(TTFT) 및 긴 답변 처리에 어려움이 있었습니다. 이를 'Lambda Function URLs in Response Streaming mode'를 사용하여 개선할 수 있습니다.
핵심 포인트
- API Gateway의 기본 설정은 LLM 스트리밍에 적합하지 않아 응답을 버퍼링합니다.
- 전통적인 아키텍처는 29초 타임아웃과 높은 TTFT(Time-To-First-Token)를 유발합니다.
- Lambda Function URLs의 Response Streaming 모드를 사용하면 실시간 토큰 스트리밍이 가능해집니다.
- Node.js와 Python 코드가 제공되어 실제 구현에 도움을 줍니다.
지난 1년 동안 Amazon Bedrock을 다루며 시간을 보내셨다면, 아마도 AWS re:Post의 #AmazonBedrock 및 #AWSLambda 태그 아래에서 반복되는 대화를 목격했을 것입니다.
2~3일마다 엔지니어들이 정확히 같은 질문을 던집니다:
"Bedrock (Claude 3.5 Sonnet)을 API Gateway 뒤의 Lambda 함수에 연결했지만, 채팅 앱이 첫 단어가 표시되기까지 10초에서 15초가 걸립니다. 토큰을 생성되는 대로 스트리밍하려면 어떻게 해야 하나요?"
그리고 그 바로 뒤에는 두 번째 골칫거리가 따릅니다:
"모델이 긴 답변을 생성하거나 다단계 추론을 수행할 때마다, API Gateway가 29초 후에 504 Gateway Timeout으로 요청을 종료합니다. 어떻게 우회할 수 있나요?"
저도 프로덕션 프로젝트에서 이 정확한 벽에 부딪혔습니다. Bedrock이 기본적으로 스트리밍을 지원함에도 불구하고, 표준 서버리스 접착제(glue)를 앞에 두는 것이 모든 것을 망가뜨리는 것처럼 보이기 때문에 좌절스러웠습니다.
실제로 내부적으로 무슨 일이 일어나고 있는지, 왜 API Gateway가 응답을 비밀리에 버퍼링하는지, 그리고 Lambda Function URLs in Response Streaming mode를 사용하여 어떻게 진정한 300ms 미만의 Time-To-First-Token (TTFT)을 달성할 수 있는지에 대해 설명합니다. 여기에는 Node.js와 Python 모두에 대한 작동 코드가 포함되어 있습니다.
1. API Gateway가 토큰 스트리밍을 망가뜨리는 이유
AWS에서 웹 API를 구축할 때, 우리의 본능은 Amazon API Gateway로 향하는 것입니다. 일반적인 아키텍처는 다음과 같습니다:
flowchart TD
Client[Web Browser] -->|HTTP Request| APIGW[Amazon API Gateway]
APIGW -->|Full Buffering & 29s Limit| Lambda[AWS Lambda Function]
...
이 설정은 전통적인 REST 마이크로서비스에는 훌륭하지만, LLM(대규모 언어 모델)의 경우 완전히 작동하지 않습니다:
- 전체 응답 버퍼링 (Full Response Buffering): API Gateway의 REST 및 HTTP API는 응답을 메모리에 버퍼링합니다. Lambda가 토큰을 청크(chunk) 단위로 내보내더라도, API Gateway는 Lambda 실행이 완료되거나 10MB에 도달할 때까지 모든 바이트를 붙잡아 두었다가, 그제야 브라우저로 한꺼번에 전송합니다. 사용자는 이 시간 동안 스피너만 바라보고 있게 됩니다.
- 29초의 절대적인 제한 (The 29-Second Hard Limit): API Gateway는 변경할 수 없는 29초의 통합 타임아웃(integration timeout)을 강제합니다. 만약 Claude나 Llama가 상세한 2,000 토큰 분량의 설명을 생성하는 데 32초가 걸린다면, API Gateway는
504 Gateway Timeout오류와 함께 연결을 종료시킵니다. 이 제한은 늘릴 수 없습니다. - 끔찍한 체감 지연 시간 (Terrible Perceived Latency): 단어들이 즉시 나타나는 대신, 사용자는 무언가를 보기까지 8초에서 15초를 기다려야 합니다.
숫자로 보는 비교: 버퍼링 vs. 스트리밍
| 측정 항목 | API Gateway + 전통적인 Lambda | 응답 스트리밍을 사용하는 Lambda Function URL |
|---|---|---|
| 첫 토큰까지의 시간 (TTFT) | 8,400 ms (사용자가 전체 답변을 기다림) | ~260 ms (즉각적인 시각적 피드백) |
2. 아키텍처적 해결책: RESPONSE_STREAM 모드의 Lambda Function URL
토큰을 사용자에게 직접 스트리밍하려면, API Gateway를 우회하고 InvokeMode: RESPONSE_STREAM으로 설정된 AWS Lambda Function URLs를 사용합니다.
Lambda Function URLs는 함수에 전용 HTTPS 엔드포인트를 제공합니다. 응답 스트리밍이 활성화되면, Lambda는 HTTP 청크 전송 인코딩(chunked transfer encoding)을 사용하여 클라이언트로 데이터를 전송합니다. 바이트들은 Lambda를 떠나자마자 Bedrock이 생성하는 즉시 브라우저에 도달합니다.
flowchart TD
Client[웹 브라우저가 ReadableStream 가져오기] -->|SSE 스트림| CF[Amazon CloudFront CDN]
CF -->|청크 HTTP 전송| FURL[Lambda Function URL: RESPONSE_STREAM]
...
사용자 지정 도메인, 정적 에셋에 대한 엣지 캐싱(edge caching), 또는 AWS WAF 보호가 필요한 경우, CloudFront를 Function URL 앞에 배치할 수 있습니다. 단, 이때 CloudFront가 버퍼링하지 않도록 설정해야 합니다.
Node.js와 Python 모두에서 이를 구현하는 방법을 살펴보겠습니다.
3. Node.js 20+ 구현: 네이티브 응답 스트리밍
Node.js는 전역 awslambda.streamifyResponse() 래퍼와 awslambda.HttpResponseStream을 통해 AWS Lambda에서 응답 스트리밍에 대한 최상급(first-class) 네이티브 지원을 제공합니다.
다음은 최신 Amazon Bedrock ConverseStream API를 사용한 전체 Lambda 핸들러입니다:
// lambda/index.mjs
import {
BedrockRuntimeClient,
...
invokeModelWithResponseStream 대신 ConverseStream을 사용하는 이유?
Converse API 이전에는 Bedrock의 모든 파운데이션 모델(Claude, Titan, Llama, Mistral)이 완전히 다른 JSON 페이로드 구조를 가지고 있었습니다. ConverseStream API는 이 모든 것을 표준화합니다: 일관된 요청 형식, 통합된 도구 호출(tool calling), 그리고 모든 모델 제공업체에 걸친 일관된 토큰 원격 측정(token telemetry)을 제공합니다.
4. Python 솔루션: FastAPI & AWS Lambda Web Adapter
AWS re:Post에서 파이썬 개발자들은 자주 다음과 같이 질문합니다:
"왜 Node.js만
streamifyResponse를 가지고 있나요? Boto3 코드를 JavaScript로 다시 작성해야 하나요?"
답변은 아닙니다. FastAPI와 Boto3을 사용하여 표준 파이썬 코드를 작성하고, 공식 오픈 소스인 **AWS Lambda Web Adapter (LWA)**로 실행할 수 있습니다.
LWA는 AWS가 제공하는 작은 확장 기능으로, 표준 HTTP ASGI 서버(FastAPI/Uvicorn)를 Lambda Function URL 응답 스트리밍에 직접 연결해 줍니다.
main.py
import os
import json
import asyncio
...
Lambda Web Adapter가 포함된 Dockerfile
FROM public.ecr.aws/awslabs/aws-lambda-web-adapter:0.8.4 AS aws-lwa
FROM public.ecr.aws/docker/library/python:3.11-slim
...
5. Infrastructure as Code: 핵심 설정
SAM 템플릿이나 CloudFormation에서 마법의 스위치는 Function URL 구성에 InvokeMode: RESPONSE_STREAM을 설정하는 것입니다:
# infra/template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
...
CloudFront가 스트림을 버퍼링하게 두지 마세요
CloudFront를 Lambda Function URL로 라우팅하는 경우, 캐시 정책이 올바르게 설정되지 않으면 CloudFront가 응답을 버퍼링할 수 있습니다:
CachePolicyId: 4135ea2d-6df8-44a3-9df3-44ca84e08fad(AWS 관리형 CachingDisabled)를 첨부합니다.OriginRequestPolicyId: b6847045-a537-4142-8132-7d0dc659e210(AllViewerExceptHostHeader)를 첨부합니다.
6. 브라우저에서 스트림 읽는 방법
구식 EventSource(GET 요청만 지원하고 채팅 기록 전송이 까다로운)는 잊으세요. 최신 fetch()와 ReadableStreamDefaultReader로 깔끔하게 스트림을 소비할 수 있습니다:
async function streamChat(prompt, onToken) {
const response = await fetch("https://your-lambda-url.on.aws/", {
method: "POST",
...
7. 프로덕션 환경의 실제 문제점 (Gotchas)
re:Post에서 자주 논의되며 디버깅 시간을 몇 시간이나 절약해 줄 수 있는 네 가지 일반적인 함정(pitfalls)이 있습니다:
- Content-Length 헤더를 절대 설정하지 마세요: 사용자나 중개 프록시가
Content-Length헤더를 첨부하면, 브라우저와 프록시는 모든 바이트가 도착할 때까지 아무것도 표시하지 않고 기다립니다. - CORS OPTIONS 사전 요청(preflights)을 명시적으로 처리하세요: 브라우저는 스트리밍 POST 요청을 시작하기 전에 HTTP
OPTIONS요청을 보냅니다. 스트리밍 핸들러는 이 OPTIONS를 가로채서 즉시 HTTP 204 또는 200을 반환해야 합니다. - Bedrock Agent 이벤트 필터링: 직접 모델을 사용하는 것이 아니라 Amazon Bedrock Agents를 통해 스트리밍하는 경우, 세션 ID를 공유하는 동시 요청이 때때로 추적 메타데이터(trace metadata)를 방출할 수 있습니다.
contentBlockDelta와 일치하는 청크만 렌더링하도록 하세요. - 상당한 비용 절감: Lambda Function URL은 표준 Lambda 호출 시간 및 지속 시간에 더해 AWS Data Transfer Out을 부과합니다. API Gateway를 제거함으로써, LLM 트래픽에 대한 API Gateway의 요청당 $1.00~$3.50 수수료를 피할 수 있습니다.
결론 및 코드 저장소
토큰 스트리밍은 느린 GenAI 경험을 즉각적이고 반응성이 느껴지는 것으로 변화시킵니다. API Gateway에서 응답 스트리밍 모드의 Lambda Function URL로 이동하면 TTFT(Time To First Token)가 8초 이상에서 약 260ms까지 떨어지고, 29초의 실행 지연 구간을 완전히 제거할 수 있습니다.
모든 코드, SAM 템플릿(SAM templates), CDK 정의(CDK definitions), 그리고 인터랙티브 다크 모드 테스트 플레이그라운드는 오픈 소스이며 배포 준비가 되어 있습니다:
🔗 GitHub 저장소: sharma-the-karma/serverless-bedrock-token-streaming
여러분의 AWS 계정에서 직접 사용해 보시고, 팀이 스트리밍 GenAI 워크로드를 어떻게 처리하고 있는지 댓글로 알려주세요!
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기