본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 28. · 2 Views
대규모 트래픽을 위한 확장 가능한 아키텍처 완벽 가이드
AWS 환경에서 대규모 트래픽을 처리하기 위한 확장 가능한 아키텍처 설계 방법을 다룹니다. 수평/수직 확장부터 Stateless 설계, 세션 관리, 메시지 큐, Lambda, API Gateway까지 실무에서 바로 적용할 수 있는 핵심 개념을 초급 개발자도 이해할 수 있도록 친절하게 설명합니다.
목차
- 수평_확장_vs_수직_확장_전략
- Stateless_애플리케이션_설계
- 세션_관리_ElastiCache_DynamoDB
- 비동기_처리와_메시지_큐_SQS_SNS
- Lambda와_이벤트_기반_아키텍처
- API_Gateway_스로틀링_및_캐싱
1. 수평 확장 vs 수직 확장 전략
어느 날 스타트업에서 일하는 김개발 씨에게 긴급 호출이 왔습니다. "서버가 터졌어요!" 마케팅 팀의 이벤트가 대박을 치면서 평소의 100배 트래픽이 몰려온 것입니다.
서버 한 대로 버티던 서비스가 순식간에 먹통이 되어버렸습니다.
**확장(Scaling)**이란 늘어나는 트래픽을 감당하기 위해 시스템의 처리 능력을 늘리는 것입니다. 마치 식당에 손님이 많아졌을 때 대응하는 방법과 같습니다.
주방을 더 크게 만들거나(수직 확장), 아니면 지점을 여러 개 내거나(수평 확장) 선택해야 합니다. 이 결정이 서비스의 미래를 좌우합니다.
다음 코드를 살펴봅시다.
// 수직 확장: 더 강력한 인스턴스로 업그레이드
const verticalScaling = {
before: { instanceType: 't3.micro', cpu: 2, memory: '1GB' },
after: { instanceType: 'r5.xlarge', cpu: 4, memory: '32GB' }
};
// 수평 확장: Auto Scaling Group 설정
const autoScalingConfig = {
minInstances: 2, // 최소 인스턴스 수
maxInstances: 10, // 최대 인스턴스 수
targetCPUUtilization: 70, // CPU 70% 넘으면 확장
cooldownPeriod: 300 // 5분 대기 후 다음 조정
};
// 로드 밸런서가 트래픽을 분산
const loadBalancer = {
type: 'Application Load Balancer',
healthCheck: '/health',
targets: ['instance-1', 'instance-2', 'instance-3']
};
김개발 씨는 입사 6개월 차 주니어 개발자입니다. 서비스가 잘 되다 보니 사용자가 점점 늘어났고, 어느 날 갑자기 서버가 응답을 멈춰버렸습니다.
CPU 사용률 100%, 메모리 부족 경고가 계속 울렸습니다. 팀장님이 다급하게 물었습니다.
"지금 당장 어떻게 해야 해?" 김개발 씨는 고민에 빠졌습니다. 서버를 더 좋은 걸로 바꿔야 할까요, 아니면 서버를 더 늘려야 할까요?
**수직 확장(Vertical Scaling)**은 쉽게 말해 "더 좋은 컴퓨터로 바꾸는 것"입니다. 마치 자전거가 느리다고 느껴질 때 오토바이로 바꾸는 것과 같습니다.
AWS에서는 t3.micro 인스턴스를 r5.xlarge로 업그레이드하는 식입니다. 장점은 간단하다는 것입니다.
기존 코드를 하나도 바꾸지 않아도 됩니다. 하지만 치명적인 단점이 있습니다.
세상에서 가장 좋은 컴퓨터에도 한계가 있다는 것입니다. 그리고 그 한계에 도달하면 더 이상 방법이 없습니다.
**수평 확장(Horizontal Scaling)**은 "서버를 여러 대 두는 것"입니다. 식당 비유로 돌아가면, 본점이 너무 바쁘니까 2호점, 3호점을 여는 것과 같습니다.
각 지점에서 손님을 나눠 받으면 한 곳에 부담이 집중되지 않습니다. AWS에서는 Auto Scaling Group이 이 역할을 합니다.
트래픽이 늘어나면 자동으로 서버를 추가하고, 트래픽이 줄어들면 서버를 줄입니다. 위 코드에서 CPU 사용률이 70%를 넘으면 새 인스턴스가 생성됩니다.
그런데 서버가 여러 대면 문제가 하나 있습니다. 사용자 요청을 어떤 서버가 처리해야 할까요?
이때 등장하는 것이 **로드 밸런서(Load Balancer)**입니다. 마치 식당 입구에서 "3번 테이블로 가세요"라고 안내하는 직원과 같습니다.
로드 밸런서는 각 서버의 상태를 주기적으로 확인합니다. 위 코드의 healthCheck가 바로 그 설정입니다.
서버가 /health 요청에 응답하지 않으면 "이 서버는 문제가 있구나"라고 판단하고 트래픽을 보내지 않습니다. 수평 확장의 핵심 장점은 이론상 무한히 확장 가능하다는 것입니다.
서버 10대가 부족하면 100대로, 100대가 부족하면 1000대로 늘릴 수 있습니다. 넷플릭스, 아마존 같은 대형 서비스들이 모두 이 방식을 사용합니다.
하지만 수평 확장에는 한 가지 전제 조건이 있습니다. 애플리케이션이 Stateless해야 한다는 것입니다.
이게 무슨 뜻인지는 다음 카드에서 자세히 알아보겠습니다. 김개발 씨는 결국 수평 확장을 선택했습니다.
Auto Scaling을 설정하고 나니, 다음 이벤트 때는 서버가 자동으로 10대까지 늘어났다가 트래픽이 줄자 다시 2대로 돌아왔습니다. 비용도 절약하고 안정성도 확보한 것입니다.
실전 팁
💡 - 수직 확장은 빠른 임시 방편, 수평 확장은 장기적 해결책입니다
- Auto Scaling의 cooldown 기간을 적절히 설정해야 불필요한 인스턴스 생성을 방지할 수 있습니다
- 로드 밸런서의 health check 주기와 임계값을 신중하게 설정하세요
2. Stateless 애플리케이션 설계
수평 확장을 도입한 김개발 씨에게 새로운 문제가 생겼습니다. 사용자가 로그인한 후 다른 페이지로 이동했더니 갑자기 로그아웃이 되어버린 것입니다.
서버가 여러 대인데, 로그인 정보가 한 서버에만 저장되어 있었기 때문입니다.
Stateless 애플리케이션이란 서버가 사용자의 상태 정보를 직접 저장하지 않는 설계 방식입니다. 마치 은행 창구 직원이 바뀌어도 고객 정보는 중앙 시스템에서 조회할 수 있는 것과 같습니다.
어떤 서버가 요청을 받아도 동일하게 처리할 수 있어야 진정한 수평 확장이 가능합니다.
다음 코드를 살펴봅시다.
// Stateful - 문제가 되는 방식
class StatefulServer {
private sessions: Map<string, UserData> = new Map();
login(userId: string, data: UserData) {
this.sessions.set(userId, data); // 이 서버에만 저장됨!
}
}
// Stateless - 올바른 방식
class StatelessServer {
constructor(private redis: RedisClient) {}
async login(userId: string, data: UserData) {
// 외부 저장소에 저장 - 모든 서버가 접근 가능
await this.redis.set(`session:${userId}`, JSON.stringify(data));
await this.redis.expire(`session:${userId}`, 3600); // 1시간 후 만료
}
async getSession(userId: string): Promise<UserData | null> {
const data = await this.redis.get(`session:${userId}`);
return data ? JSON.parse(data) : null;
}
}
박시니어 씨가 김개발 씨에게 물었습니다. "서버 3대가 있는데, 사용자가 1번 서버에 로그인했어요.
그 다음 요청이 2번 서버로 가면 어떻게 될까요?" 김개발 씨가 대답했습니다. "로그인 정보가 1번 서버에만 있으니까...
2번 서버에서는 로그인 안 된 걸로 보이겠네요." "맞아요. 이게 바로 Stateful 설계의 문제점이에요." Stateful이란 서버가 클라이언트의 상태를 기억하고 있는 것입니다.
세션 정보, 장바구니 내용, 임시 데이터 같은 것들이 서버 메모리에 저장됩니다. 단일 서버에서는 문제가 없지만, 서버가 여러 대가 되면 심각한 문제가 됩니다.
해결책 중 하나로 Sticky Session이 있습니다. 로드 밸런서가 특정 사용자를 항상 같은 서버로 보내는 방식입니다.
하지만 이 방법에는 치명적인 단점이 있습니다. 첫째, 특정 서버에 사용자가 몰릴 수 있습니다.
로드 밸런싱의 장점이 사라지는 것입니다. 둘째, 해당 서버가 죽으면 그 서버에 붙어있던 모든 사용자의 세션이 날아갑니다.
장애 복구가 어려워집니다. 그래서 등장한 것이 Stateless 설계입니다.
서버는 어떤 상태 정보도 직접 저장하지 않습니다. 대신 모든 상태는 외부 저장소에 보관합니다.
Redis, DynamoDB, S3 같은 곳에 말이죠. 위 코드를 보면 차이가 명확합니다.
Stateful 방식에서는 Map 객체에 세션을 저장합니다. 이 데이터는 해당 서버 프로세스가 죽으면 함께 사라집니다.
반면 Stateless 방식에서는 Redis에 세션을 저장합니다. Redis는 모든 서버가 공유하는 외부 저장소입니다.
어떤 서버가 요청을 받아도 Redis에서 세션을 조회할 수 있습니다. Stateless 설계를 하면 또 다른 장점이 있습니다.
서버를 껐다 켜도 사용자에게 영향이 없습니다. 배포할 때 순차적으로 서버를 재시작해도 세션이 유지됩니다.
이것이 바로 무중단 배포가 가능해지는 이유입니다. 또한 Auto Scaling이 제대로 동작합니다.
새 서버가 추가되어도 즉시 트래픽을 받을 수 있습니다. 서버가 줄어들어도 해당 서버에 있던 사용자 정보가 사라지지 않습니다.
JWT(JSON Web Token)를 사용하는 것도 Stateless를 구현하는 한 방법입니다. 토큰 자체에 사용자 정보가 담겨 있어서 서버가 상태를 저장할 필요가 없습니다.
다만 토큰 무효화가 어렵다는 단점이 있어서, 상황에 따라 적절히 선택해야 합니다. 김개발 씨는 모든 세션 관련 코드를 Redis 기반으로 변경했습니다.
이제 서버가 몇 대든, 어떤 서버가 요청을 받든 동일하게 동작합니다. 진정한 수평 확장의 첫걸음을 뗀 것입니다.
실전 팁
💡 - 로컬 파일 시스템에 임시 파일을 저장하는 것도 Stateful입니다. S3를 활용하세요
- 서버 메모리 캐시 대신 Redis나 Memcached 같은 분산 캐시를 사용하세요
- Stateless 여부를 테스트하려면 서버를 무작위로 재시작해도 서비스가 정상 동작하는지 확인해보세요
3. 세션 관리 ElastiCache DynamoDB
Stateless 설계의 중요성을 깨달은 김개발 씨는 이제 세션을 어디에 저장해야 할지 고민입니다. 선배가 "ElastiCache를 써봐"라고 했는데, DynamoDB도 좋다는 글을 봤습니다.
도대체 뭘 써야 할까요?
ElastiCache는 AWS에서 제공하는 인메모리 캐시 서비스로, Redis나 Memcached를 관리형으로 제공합니다. DynamoDB는 완전 관리형 NoSQL 데이터베이스입니다.
둘 다 세션 저장소로 사용할 수 있지만, 특성이 다릅니다. ElastiCache는 빠른 속도가 필요할 때, DynamoDB는 데이터 영속성이 중요할 때 선택합니다.
다음 코드를 살펴봅시다.
// ElastiCache (Redis) 세션 관리
import { Redis } from 'ioredis';
const redis = new Redis({
host: 'my-cluster.cache.amazonaws.com',
port: 6379
});
async function setSession(sessionId: string, userData: object) {
await redis.setex(
`session:${sessionId}`,
3600, // TTL: 1시간
JSON.stringify(userData)
);
}
// DynamoDB 세션 관리
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
const dynamodb = new DynamoDBClient({ region: 'ap-northeast-2' });
async function setSessionDynamo(sessionId: string, userData: object) {
const ttl = Math.floor(Date.now() / 1000) + 3600;
await dynamodb.send(new PutItemCommand({
TableName: 'Sessions',
Item: {
sessionId: { S: sessionId },
userData: { S: JSON.stringify(userData) },
ttl: { N: ttl.toString() } // 자동 삭제를 위한 TTL
}
}));
}
박시니어 씨가 화이트보드 앞에 섰습니다. "자, ElastiCache와 DynamoDB.
둘 다 세션 저장에 쓸 수 있어요. 하지만 성격이 완전히 달라요." 먼저 ElastiCache를 살펴봅시다.
Redis 또는 Memcached를 AWS가 관리해주는 서비스입니다. 핵심 특징은 **인메모리(In-memory)**라는 것입니다.
데이터를 메모리에 저장하기 때문에 읽기/쓰기가 밀리초 단위로 매우 빠릅니다. 세션은 거의 모든 요청에서 조회됩니다.
사용자가 페이지를 이동할 때마다 "이 사용자가 로그인한 사람인가?"를 확인해야 하니까요. 이런 빈번한 조회에는 ElastiCache의 빠른 속도가 큰 장점입니다.
Redis는 단순한 키-값 저장 외에도 다양한 자료구조를 지원합니다. List, Set, Hash, Sorted Set 같은 것들이죠.
그래서 세션뿐 아니라 실시간 랭킹, 최근 본 상품 목록 같은 기능에도 활용할 수 있습니다. 그런데 인메모리의 단점이 있습니다.
서버가 재시작되면 데이터가 사라질 수 있다는 것입니다. 물론 Redis는 RDB나 AOF 방식으로 디스크에 백업할 수 있지만, 근본적으로 메모리 기반이라는 점을 기억해야 합니다.
DynamoDB는 완전히 다른 접근입니다. 디스크 기반의 NoSQL 데이터베이스입니다.
데이터가 여러 가용 영역에 복제되어 저장되므로 **내구성(Durability)**이 매우 높습니다. DynamoDB의 또 다른 장점은 서버리스라는 것입니다.
용량을 미리 정할 필요 없이, 트래픽에 따라 자동으로 확장됩니다. On-Demand 모드를 사용하면 요청한 만큼만 비용을 내면 됩니다.
위 코드에서 주목할 부분은 TTL(Time To Live) 설정입니다. ElastiCache에서는 setex 명령어로 간단히 설정합니다.
DynamoDB에서는 ttl 속성에 Unix 타임스탬프를 저장하면 해당 시간이 지나면 자동으로 삭제됩니다. 그렇다면 언제 무엇을 선택해야 할까요?
ElastiCache를 선택하는 경우: 응답 속도가 매우 중요할 때, 세션 데이터가 사라져도 다시 로그인하면 되는 경우, 이미 Redis를 잘 알고 있는 팀. DynamoDB를 선택하는 경우: 세션 외에 다른 데이터도 함께 저장해야 할 때, 운영 부담을 최소화하고 싶을 때, 트래픽 변동이 심해서 용량 예측이 어려울 때.
실제로는 두 서비스를 함께 사용하기도 합니다. ElastiCache를 캐시 레이어로, DynamoDB를 영구 저장소로 사용하는 것입니다.
세션을 먼저 ElastiCache에서 찾고, 없으면 DynamoDB에서 조회하는 방식입니다. 김개발 씨의 서비스는 트래픽이 빠르게 늘고 있어서 결국 DynamoDB On-Demand를 선택했습니다.
용량 계획 없이 바로 사용할 수 있어서 편했고, 비용도 사용한 만큼만 나왔습니다.
실전 팁
💡 - ElastiCache 클러스터 모드를 사용하면 더 큰 규모로 확장할 수 있습니다
- DynamoDB의 TTL 삭제는 즉시 일어나지 않을 수 있으므로, 조회 시 만료 여부를 추가로 확인하는 게 좋습니다
- 세션 데이터에는 민감한 정보를 최소화하고, 필요하면 암호화하세요
4. 비동기 처리와 메시지 큐 SQS SNS
김개발 씨가 만든 서비스에 회원가입 기능이 있습니다. 가입 시 환영 이메일도 보내고, 마케팅 시스템에도 알려야 합니다.
그런데 이 작업들 때문에 회원가입이 5초나 걸립니다. 사용자들이 "이 서비스 왜 이렇게 느려요?"라고 불평하기 시작했습니다.
비동기 처리란 시간이 오래 걸리는 작업을 나중에 별도로 처리하는 방식입니다. **메시지 큐(Message Queue)**는 이런 비동기 작업을 안전하게 전달하는 중간 저장소입니다.
AWS의 **SQS(Simple Queue Service)**는 메시지 큐 서비스이고, **SNS(Simple Notification Service)**는 여러 구독자에게 동시에 메시지를 보내는 pub/sub 서비스입니다.
다음 코드를 살펴봅시다.
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
const sqs = new SQSClient({ region: 'ap-northeast-2' });
const sns = new SNSClient({ region: 'ap-northeast-2' });
// 회원가입 API - 빠르게 응답하고 나머지는 비동기로
async function registerUser(userData: UserData) {
// 1. 필수 작업만 동기로 처리
const user = await db.createUser(userData);
// 2. 시간 걸리는 작업은 SQS에 메시지 전송
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.WELCOME_EMAIL_QUEUE,
MessageBody: JSON.stringify({ userId: user.id, email: user.email })
}));
// 3. 여러 시스템에 알림은 SNS로 발행
await sns.send(new PublishCommand({
TopicArn: process.env.USER_SIGNUP_TOPIC,
Message: JSON.stringify({ userId: user.id, event: 'SIGNUP' })
}));
return { success: true, userId: user.id }; // 즉시 응답!
}
박시니어 씨가 타이머를 들고 왔습니다. "회원가입 API 응답 시간을 재볼게요.
지금은 5.2초... 이건 너무 느려요.
사용자들이 다 이탈해요." 문제는 회원가입 API에서 너무 많은 일을 하고 있다는 것입니다. 데이터베이스에 사용자 저장, 환영 이메일 발송, 마케팅 시스템 연동, 분석 시스템에 이벤트 전송...
이 모든 것이 끝나야 응답이 갑니다. 여기서 핵심 질문을 해봅시다.
"이 모든 작업이 사용자가 기다리는 동안 끝나야 할까요?" 데이터베이스 저장은 반드시 동기로 처리해야 합니다. 가입이 실제로 완료되었는지 확인해야 하니까요.
하지만 환영 이메일은요? 1분 후에 도착해도 사용자는 전혀 불편하지 않습니다.
마케팅 시스템 연동도 마찬가지입니다. 이럴 때 메시지 큐를 사용합니다.
"이 작업 좀 해줘"라는 메시지를 큐에 넣어두면, 별도의 워커가 나중에 처리합니다. **SQS(Simple Queue Service)**는 AWS의 메시지 큐 서비스입니다.
핵심 특징은 신뢰성입니다. 한번 큐에 들어간 메시지는 절대 유실되지 않습니다.
워커가 메시지를 가져가서 처리에 실패하면, 일정 시간 후 다시 큐에 나타납니다. 위 코드를 보면, 회원가입 API는 데이터베이스 저장 후 바로 SQS에 메시지만 보내고 응답합니다.
이메일 발송 같은 무거운 작업은 별도 워커가 처리합니다. 응답 시간이 5초에서 0.2초로 줄어듭니다.
**SNS(Simple Notification Service)**는 조금 다릅니다. pub/sub 패턴을 구현합니다.
하나의 메시지를 여러 구독자에게 동시에 전달하는 것입니다. 회원가입을 예로 들면, 마케팅팀도, 분석팀도, 포인트 시스템도 모두 새 회원 정보가 필요합니다.
각각에게 따로 메시지를 보내는 대신, SNS 토픽에 한 번만 발행하면 모든 구독자가 받아갑니다. SNS와 SQS를 함께 사용하는 패턴도 흔합니다.
SNS 토픽에 SQS 큐를 구독시키는 것입니다. 이렇게 하면 SNS의 fan-out(여러 곳에 배포) 기능과 SQS의 신뢰성을 모두 얻을 수 있습니다.
비동기 처리의 또 다른 장점은 부하 분산입니다. 갑자기 트래픽이 몰려도 큐에 메시지가 쌓이기만 할 뿐, 시스템이 다운되지 않습니다.
워커는 자기 처리 속도대로 꾸준히 메시지를 처리합니다. 물론 주의할 점도 있습니다.
비동기 처리는 "반드시 성공한다"고 보장하기 어렵습니다. 이메일 발송이 실패할 수도 있고, 외부 API가 다운될 수도 있습니다.
따라서 실패 처리 로직과 재시도 전략을 꼭 설계해야 합니다. SQS에는 **Dead Letter Queue(DLQ)**라는 기능이 있습니다.
여러 번 재시도해도 실패한 메시지를 별도 큐에 모아둡니다. 나중에 이 메시지들을 분석해서 원인을 파악하고 수동으로 처리할 수 있습니다.
김개발 씨는 회원가입 API를 리팩토링한 후 응답 시간이 0.2초로 줄었습니다. 사용자 불만도 사라지고, 서버 부하도 훨씬 안정적으로 변했습니다.
실전 팁
💡 - 메시지 처리는 **멱등성(idempotent)**을 보장해야 합니다. 같은 메시지가 두 번 처리되어도 결과가 같아야 합니다
- SQS 메시지의 가시성 제한 시간(visibility timeout)을 작업 소요 시간보다 넉넉하게 설정하세요
- 중요한 이벤트는 SNS + SQS 조합으로 처리하고, DLQ를 반드시 설정하세요
5. Lambda와 이벤트 기반 아키텍처
김개발 씨가 SQS 큐에 메시지를 넣는 것까지는 좋았는데, 이 메시지를 처리할 워커 서버를 따로 관리해야 했습니다. 서버 비용도 들고, 모니터링도 해야 하고...
그런데 선배가 "Lambda 써봐. 서버 관리 안 해도 돼"라고 했습니다.
Lambda는 AWS의 서버리스 컴퓨팅 서비스입니다. 코드만 업로드하면 AWS가 서버를 알아서 관리하고, 요청이 있을 때만 실행됩니다.
이벤트 기반 아키텍처는 시스템의 구성 요소들이 이벤트를 통해 느슨하게 연결되는 설계 방식입니다. Lambda는 SQS, SNS, S3, DynamoDB 등 다양한 AWS 서비스의 이벤트에 자동으로 반응합니다.
다음 코드를 살펴봅시다.
// Lambda 함수 - SQS 메시지 처리
import { SQSEvent, SQSHandler } from 'aws-lambda';
import { sendEmail } from './emailService';
export const handler: SQSHandler = async (event: SQSEvent) => {
// SQS가 자동으로 메시지를 Lambda에 전달
for (const record of event.Records) {
const message = JSON.parse(record.body);
try {
await sendEmail({
to: message.email,
subject: '환영합니다!',
template: 'welcome',
data: { userId: message.userId }
});
console.log(`Email sent to ${message.email}`);
} catch (error) {
console.error('Email failed:', error);
throw error; // 에러 발생 시 메시지가 다시 큐로
}
}
};
// S3 이벤트 트리거 Lambda - 이미지 업로드 시 썸네일 생성
export const thumbnailHandler = async (event: S3Event) => {
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = record.s3.object.key;
await createThumbnail(bucket, key);
}
};
"서버리스가 뭔가요?" 김개발 씨가 물었습니다. 박시니어 씨가 웃으며 대답했습니다.
"서버가 없다는 게 아니에요. 서버 관리를 안 해도 된다는 뜻이에요.
AWS가 다 알아서 해주거든요." 전통적인 서버 운영을 생각해봅시다. EC2 인스턴스를 띄우고, OS를 업데이트하고, 보안 패치를 적용하고, 모니터링을 설정하고...
서버 한 대 관리하는 데도 신경 쓸 게 산더미입니다. Lambda는 이 모든 것을 AWS에 맡깁니다.
개발자는 오직 코드만 작성하면 됩니다. 코드를 업로드하면 Lambda가 알아서 실행 환경을 준비하고, 요청이 오면 실행하고, 끝나면 정리합니다.
가장 매력적인 부분은 과금 방식입니다. 실행 시간만큼만 비용을 냅니다.
하루에 1000번 실행되고 각각 1초씩 걸린다면, 1000초만큼만 비용이 나옵니다. 24시간 켜둘 필요가 없습니다.
이벤트 기반 아키텍처는 Lambda와 찰떡궁합입니다. Lambda는 다양한 이벤트 소스에 연결할 수 있습니다.
SQS에 메시지가 들어오면 실행, S3에 파일이 업로드되면 실행, DynamoDB에 데이터가 변경되면 실행... 위 코드의 첫 번째 예제를 보세요.
SQS 큐에 메시지가 들어오면 Lambda가 자동으로 호출됩니다. 별도의 폴링 로직을 작성할 필요가 없습니다.
AWS가 알아서 큐를 모니터링하다가 메시지가 있으면 Lambda를 깨웁니다. 두 번째 예제는 S3 트리거입니다.
사용자가 이미지를 업로드하면 자동으로 썸네일이 생성됩니다. 웹 서버가 이 작업을 할 필요가 없으니 응답이 빨라지고, 썸네일 생성 로직이 분리되어 유지보수도 쉬워집니다.
Lambda의 확장성은 정말 놀랍습니다. 동시에 1000개의 요청이 와도 1000개의 Lambda 인스턴스가 병렬로 실행됩니다.
Auto Scaling 설정 같은 것 없이, 그냥 자동으로 됩니다. 물론 Lambda에도 제약이 있습니다.
실행 시간이 15분으로 제한됩니다. 메모리는 최대 10GB까지 설정 가능합니다.
그리고 Cold Start 문제가 있습니다. 한동안 실행되지 않던 Lambda가 호출되면 초기화 시간이 걸립니다.
Cold Start를 줄이는 방법도 있습니다. Provisioned Concurrency를 설정하면 미리 Lambda 인스턴스를 준비해둡니다.
또는 주기적으로 Lambda를 호출해서 따뜻하게 유지하는 방법도 있습니다. 이벤트 기반 아키텍처의 핵심 장점은 **느슨한 결합(Loose Coupling)**입니다.
각 컴포넌트가 직접 연결되지 않고 이벤트를 통해 소통합니다. 이메일 발송 로직이 변경되어도 회원가입 API는 전혀 수정할 필요가 없습니다.
김개발 씨는 이메일 발송 워커를 Lambda로 전환했습니다. EC2 인스턴스 하나가 사라지면서 월 비용이 크게 줄었고, 트래픽이 몰려도 자동으로 처리되니 새벽에 서버 걱정하며 잠 못 이루는 일도 없어졌습니다.
실전 팁
💡 - Lambda의 메모리 설정은 CPU 성능에도 영향을 줍니다. 메모리를 늘리면 CPU도 빨라집니다
- Cold Start가 중요한 API에는 Provisioned Concurrency나 SnapStart를 고려하세요
- 실행 시간이 긴 작업은 Step Functions와 조합하거나 여러 Lambda로 분할하세요
6. API Gateway 스로틀링 및 캐싱
김개발 씨의 서비스가 성장하면서 API 호출 수가 급증했습니다. 그런데 어느 날 특정 사용자가 1초에 1000번씩 API를 호출하는 것을 발견했습니다.
크롤러인지 악의적인 공격인지 모르겠지만, 다른 정상 사용자들이 느려지는 문제가 생겼습니다.
API Gateway는 AWS에서 제공하는 완전 관리형 API 관리 서비스입니다. **스로틀링(Throttling)**은 API 호출 빈도를 제한하여 시스템을 보호하는 기능입니다.
**캐싱(Caching)**은 자주 요청되는 응답을 저장해두었다가 바로 반환하여 백엔드 부하를 줄이는 기능입니다. 이 두 기능으로 API를 안전하고 효율적으로 운영할 수 있습니다.
다음 코드를 살펴봅시다.
// API Gateway 스로틀링 설정 (serverless.yml 예시)
const apiGatewayConfig = {
usagePlan: {
throttle: {
burstLimit: 200, // 순간 최대 요청 수
rateLimit: 100 // 초당 평균 요청 수
},
quota: {
limit: 10000, // 일일 요청 한도
period: 'DAY'
}
},
caching: {
enabled: true,
ttl: 300, // 캐시 유효 시간 (초)
dataEncrypted: true
}
};
// Lambda에서 캐시 관련 헤더 제어
export const handler = async (event: APIGatewayEvent) => {
const data = await fetchProductList();
return {
statusCode: 200,
headers: {
'Cache-Control': 'max-age=300', // 5분 캐시
'Vary': 'Accept-Encoding'
},
body: JSON.stringify(data)
};
};
API Gateway는 마치 건물 입구의 경비원과 같습니다. 누가 들어오는지 확인하고, 너무 많은 사람이 한꺼번에 들어오려 하면 제한하고, 자주 묻는 질문은 바로 답해줍니다.
스로틀링부터 살펴봅시다. 위 코드에서 rateLimit: 100은 초당 100개의 요청만 허용한다는 뜻입니다.
101번째 요청부터는 429 Too Many Requests 에러가 반환됩니다. burstLimit: 200은 순간적으로 200개까지는 허용한다는 의미입니다.
평소에는 초당 100개 제한이지만, 짧은 순간 200개까지 몰려도 처리해줍니다. 이렇게 burst를 허용하는 이유는 트래픽이 완벽하게 균등하지 않기 때문입니다.
quota 설정은 일일 사용량 제한입니다. 하루에 10000번까지만 API를 호출할 수 있습니다.
무료 티어 API 서비스들이 이런 방식으로 제한을 걸죠. 스로틀링이 중요한 이유는 시스템 보호입니다.
악의적인 사용자가 대량의 요청을 보내 시스템을 다운시키는 것을 방지합니다. 또한 공정한 자원 분배도 됩니다.
한 사용자가 자원을 독점하지 못하게 합니다. 캐싱은 다른 종류의 최적화입니다.
같은 요청에 대해 매번 백엔드를 호출하는 대신, 이전 응답을 저장해두었다가 재사용합니다. 예를 들어 상품 목록 API를 생각해봅시다.
상품 목록은 1분에 한 번 정도 바뀝니다. 그런데 1초에 1000명이 이 API를 호출한다면?
캐싱 없이는 1분에 60000번 데이터베이스를 조회합니다. 캐시를 1분으로 설정하면 단 1번만 조회하고 나머지는 캐시에서 반환합니다.
API Gateway의 캐시는 엔드포인트 단위로 설정할 수 있습니다. 자주 변하지 않는 데이터(상품 목록, 카테고리 등)는 캐시를 길게, 자주 변하는 데이터(재고 수량, 실시간 가격 등)는 캐시를 짧게 또는 비활성화합니다.
캐시 설정에서 중요한 개념이 Cache-Control 헤더입니다. Lambda 응답에 이 헤더를 포함하면 API Gateway가 이를 존중합니다.
위 코드에서 max-age=300은 5분 동안 이 응답을 캐시해도 된다는 의미입니다. 주의할 점도 있습니다.
사용자별로 다른 응답을 반환하는 API는 캐싱하면 안 됩니다. 예를 들어 "내 주문 내역" API를 캐시하면 다른 사용자의 주문 내역이 보일 수 있습니다.
이런 API는 반드시 캐시를 비활성화해야 합니다. 캐시 무효화도 고려해야 합니다.
상품 정보가 업데이트되었는데 캐시된 오래된 정보가 보이면 문제입니다. API Gateway 콘솔에서 수동으로 캐시를 플러시하거나, 적절한 TTL을 설정해서 자연스럽게 갱신되도록 해야 합니다.
김개발 씨는 스로틀링을 설정한 후 문제의 크롤러가 429 에러를 받기 시작했습니다. 정상 사용자들의 응답 속도가 다시 빨라졌습니다.
캐싱까지 적용하니 데이터베이스 부하가 80%나 줄었습니다. 보안과 성능, 두 마리 토끼를 잡은 것입니다.
실전 팁
💡 - 스로틀링 설정은 보수적으로 시작해서 점진적으로 조정하세요. 너무 빡빡하면 정상 사용자도 차단됩니다
- 캐시 TTL은 데이터 특성에 따라 결정하세요. 정적 데이터는 길게, 동적 데이터는 짧게
- 사용자 인증이 필요한 API와 공개 API의 캐시 정책을 반드시 구분하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
실전 프로젝트 글로벌 엔터프라이즈급 AWS 아키텍처
글로벌 서비스를 위한 엔터프라이즈급 AWS 아키텍처 설계부터 구축까지 실전 프로젝트로 배우는 완벽 가이드입니다. 멀티 리전 고가용성, 보안, 모니터링, CI/CD까지 실무에서 바로 적용할 수 있는 노하우를 담았습니다.
CodePipeline으로 완전 자동화된 CI/CD 구축
AWS CodeCommit, CodeBuild, CodeDeploy, CodePipeline을 활용하여 코드 커밋부터 배포까지 완전히 자동화된 CI/CD 파이프라인을 구축하는 방법을 초급 개발자를 위해 쉽게 설명합니다. 실무 상황을 스토리로 풀어내며 각 단계를 자세히 다룹니다.
ECS와 EKS로 컨테이너 오케스트레이션 완벽 가이드
AWS에서 컨테이너를 운영하는 두 가지 핵심 서비스인 ECS와 EKS를 비교하고, 실무에서 어떻게 활용하는지 단계별로 알아봅니다. 서버리스 컨테이너부터 서비스 메시까지 컨테이너 오케스트레이션의 모든 것을 다룹니다.
AWS 비용 최적화 및 FinOps 전략 완벽 가이드
AWS 클라우드 비용을 효과적으로 관리하고 최적화하는 방법을 알아봅니다. Reserved Instance, Spot Instance, Savings Plans 등 다양한 비용 절감 전략과 FinOps 사고방식을 초급 개발자도 쉽게 이해할 수 있도록 설명합니다.
AWS Organizations로 멀티 계정 거버넌스 구축
AWS Organizations를 활용하여 여러 AWS 계정을 효율적으로 관리하고 보안 정책을 일관성 있게 적용하는 방법을 알아봅니다. 초급 개발자도 쉽게 이해할 수 있도록 실무 사례와 함께 설명합니다.