Java가 Lambda에 돌아왔다: Spring Boot 3, SnapStart, Bedrock으로 서브초 GenAI API 구축
Source: Dev.to

Java가 AWS Lambda에 너무 느리나요? 수년간 답은 “예, 주로 콜드 스타트 때문에”였습니다. 오늘날 Java 21과 SnapStart를 사용하면 답은 “절대 그렇지 않다”가 됩니다.
이 글에서는 Spring Boot 3, Java 21, 그리고 AWS Bedrock (Claude 3.5)을 사용해 500 ms 이하로 시작하는 프로덕션 급 서버리스 API를 구축한 과정을 보여드립니다.
문제: “콜드 스타트” 세금
Lambda에서 Java를 실행해 본 적이 있다면 그 고통을 잘 알 겁니다. JVM은 무겁고, 클래스를 로드하고, Spring 컨텍스트를 초기화하며, AWS SDK를 설정하는 데 5 ~ 15 초가 걸릴 수 있습니다.
- 비동기 백그라운드 작업은 이를 견딜 수 있습니다.
- 동기식 API(예: 챗봇이나 REST 엔드포인트)는 이를 견딜 수 없습니다.
솔루션: AWS Lambda SnapStart
SnapStart는 CRaC(Coordinated Restore at Checkpoint)를 사용하여 판도를 바꿉니다.
- AWS는 배포 단계에 함수 실행을 시작합니다.
- 전체 초기화(JVM 워밍업, Spring 컨텍스트, 의존성 주입)를 수행합니다.
- 초기화된 Firecracker microVM의 메모리 스냅샷을 찍습니다.
- 이 스냅샷을 캐시합니다.
사용자가 API를 호출하면 Lambda는 단순히 메모리 상태를 복원합니다—마치 노트북을 차가운 상태에서 부팅하는 대신 대기 모드에서 깨우는 것과 같습니다.
아키텍처
이 프로젝트는 Spring Cloud Function을 통해 생성 AI(AWS Bedrock)를 통합합니다.
핵심 구성 요소
| 구성 요소 | 세부 정보 |
|---|---|
| 런타임 | Java 21 (AWS Corretto) |
| 프레임워크 | Spring Boot 3.2 + Spring Cloud Function |
| 인프라 | Terraform |
| AI 모델 | Anthropic Claude 3.5 Sonnet (AWS Bedrock을 통해) |
Source:
코드: 최적화 기법
1. 스마트 초기화 (생성자 주입)
무거운 작업(베드락 클라이언트 생성)을 생성자로 옮겨서 SnapStart가 배포 시점에 이를 캡처하도록 하고, 호출 시점에는 수행되지 않게 합니다.
@Service
public class BedrockService {
private final BedrockRuntimeClient bedrockClient;
public BedrockService() {
// CRITICAL: runs during the "Deployment" phase, not the "Invocation" phase!
this.bedrockClient = BedrockRuntimeClient.builder()
.region(Region.US_EAST_1)
// Use the lightweight HTTP client instead of Netty
.httpClient(UrlConnectionHttpClient.builder().build())
.build();
}
// ... business logic ...
}
프로 팁: 기본 Netty HTTP 클라이언트를 UrlConnectionHttpClient 로 교체하면 아티팩트 크기와 시작 시간이 감소해 Lambda 성능에 중요한 영향을 줍니다.
2. Terraform 구성
SnapStart를 활성화하는 것은 한 줄이면 되지만, 버전 퍼블리시도 함께 활성화해야 합니다.
resource "aws_lambda_function" "java_snapstart_function" {
function_name = "java-bedrock-poc"
runtime = "java21"
handler = "org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest"
# ... other config ...
publish = true # REQUIRED for SnapStart
snap_start {
apply_on = "PublishedVersions"
}
environment {
variables = {
# Tune JVM for fast tier‑1 compilation
JAVA_TOOL_OPTIONS = "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
}
}
}
벤치마크: 효과가 있었나요?
함수를 배포하고 AWS CLI로 테스트했습니다. 차이가 밤과 낮처럼 크게 나타났습니다.
| 지표 | SnapStart 없이 | SnapStart 적용 🚀 |
|---|---|---|
| 초기화 시간 | ~8,000 ms | 0 ms (캐시) |
| 복원 시간 | N/A | ~350 ms |
| 실행 시간 | ~1,500 ms | ~1,500 ms |
| 총 사용자 대기 시간 | ~9.5초 🐢 | ~1.8초 🚀 |
Note: 실행 시간에는 Claude 3.5 (GenAI) 호출이 포함됩니다. Java Lambda 오버헤드가 1초 미만 수준으로 감소했습니다.
결론
Java는 이제 서버리스 세계에서 2등 시민이 아닙니다. Spring Boot 3, SnapStart, 경량 클라이언트를 결합함으로써 우리는 엔터프라이즈 급의 강력한 타입과 테스트 가능한 애플리케이션을 구축할 수 있으며, 성능도 Node.js나 Python만큼 뛰어납니다.
레거시 마이그레이션을 담당하는 시니어 아키텍트에게는 모든 것을 새로운 언어로 다시 작성하지 않고도 복잡한 모놀리스를 AWS로 옮길 수 있는 실현 가능한 경로를 제공합니다.
소스 코드
전체 작업 예제(테라폼 코드와 특수 셸 스크립트 포함)는 GitHub에서 확인할 수 있습니다: