본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 19. · 5 Views
AWS SQS 메시지 큐 완벽 가이드
AWS SQS를 활용한 비동기 메시지 처리의 모든 것. 표준 큐와 FIFO 큐의 차이부터 Lambda 연동, 배치 처리, 데드레터큐까지 실무에 바로 적용 가능한 패턴을 초급자도 이해하기 쉽게 설명합니다.
목차
1. SQS란 무엇인가
입사 6개월 차 김개발 씨는 오늘도 회의실에서 난감한 표정을 짓고 있습니다. "회원가입 API가 너무 느려요.
이메일 발송 때문에 응답이 5초나 걸립니다." 팀장님이 말했습니다. "SQS를 써보는 게 어때요?"
**SQS(Simple Queue Service)**는 AWS에서 제공하는 완전 관리형 메시지 큐 서비스입니다. 마치 우체국의 우편함처럼 메시지를 안전하게 보관하고 전달합니다.
시간이 오래 걸리는 작업을 나중에 처리하도록 미뤄두면 API 응답 속도가 빨라집니다.
다음 코드를 살펴봅시다.
// AWS SDK v3로 SQS 메시지 전송하기
const { SQSClient, SendMessageCommand } = require('@aws-sdk/client-sqs');
const client = new SQSClient({ region: 'ap-northeast-2' });
async function sendWelcomeEmail(userEmail) {
// 메시지 전송 파라미터 설정
const params = {
QueueUrl: 'https://sqs.ap-northeast-2.amazonaws.com/123456789/welcome-emails',
MessageBody: JSON.stringify({
email: userEmail,
type: 'WELCOME',
timestamp: new Date().toISOString()
})
};
// SQS에 메시지 전송 - 즉시 반환됨
const command = new SendMessageCommand(params);
await client.send(command);
console.log('메시지가 큐에 추가되었습니다');
}
김개발 씨는 팀장님의 조언에 고개를 갸우뚱했습니다. SQS라니, 처음 듣는 단어였습니다.
점심시간에 선배 박시니어 씨를 찾아가 물어봤습니다. "선배님, SQS가 뭔가요?" 박시니어 씨가 커피를 한 모금 마시며 설명을 시작했습니다.
메시지 큐란 무엇인가 쉽게 비유하자면 SQS는 마치 우체국의 우편함과 같습니다. 편지를 쓴 사람은 우편함에 넣기만 하면 됩니다.
우편함에 넣는 순간 할 일이 끝나는 거죠. 그 편지를 언제 어떻게 배달할지는 우체부가 알아서 처리합니다.
SQS도 똑같이 동작합니다. 우리 API는 "이메일을 보내야 한다"는 메시지만 큐에 넣으면 됩니다.
실제로 이메일을 보내는 건 다른 워커가 나중에 처리합니다. 왜 필요한가 김개발 씨의 회원가입 API를 예로 들어보겠습니다.
현재는 어떻게 동작할까요? 사용자가 가입 버튼을 누릅니다.
API가 데이터베이스에 사용자 정보를 저장합니다. 그리고 바로 이메일 전송 API를 호출합니다.
이메일 서비스가 응답할 때까지 기다립니다. 외부 SMS 서비스도 호출합니다.
또 기다립니다. 이 모든 과정이 끝나야 사용자에게 "가입 완료"라는 응답을 보낼 수 있습니다.
문제는 이메일 서비스가 느리면 어떻게 될까요? 사용자는 한없이 기다려야 합니다.
더 큰 문제는 이메일 서비스가 다운되면 회원가입 자체가 실패한다는 것입니다. 이건 말이 안 됩니다.
회원가입은 성공했는데 이메일 전송 때문에 실패 처리된다니요. SQS의 등장 바로 이런 문제를 해결하기 위해 메시지 큐가 등장했습니다.
SQS를 사용하면 API는 데이터베이스에 저장하고, "이메일 보내야 함"이라는 메시지만 큐에 넣으면 됩니다. 단 0.1초도 안 걸립니다.
그러면 즉시 사용자에게 "가입 완료" 응답을 보낼 수 있습니다. 실제 이메일 전송은 별도의 워커가 큐에서 메시지를 꺼내서 천천히 처리합니다.
이렇게 하면 응답 속도가 빨라집니다. API와 이메일 전송이 독립적으로 동작합니다.
이메일 서비스가 다운되어도 메시지는 큐에 안전하게 보관됩니다. 코드로 이해하기 위의 코드를 살펴보겠습니다.
먼저 AWS SDK의 SQSClient를 생성합니다. 리전은 서울로 설정했습니다.
그 다음 SendMessageCommand를 준비합니다. QueueUrl은 큐의 주소이고, MessageBody는 실제 전송할 데이터입니다.
핵심은 await client.send(command) 부분입니다. 이 코드가 실행되면 메시지가 큐에 추가됩니다.
실제 이메일이 발송되는 게 아닙니다. 단지 "나중에 이 이메일을 보내야 해"라는 할 일 목록에 추가하는 것입니다.
실무에서의 활용 실제 현업에서는 어떻게 활용할까요? 쇼핑몰을 운영한다고 가정해봅시다.
주문이 들어오면 해야 할 일이 많습니다. 재고 차감, 결제 처리, 주문 확인 이메일, SMS 발송, 배송 시스템 연동, 포인트 적립 등등.
이 모든 걸 동기적으로 처리하면 주문 API는 10초 이상 걸릴 겁니다. 하지만 SQS를 사용하면 주문 정보만 데이터베이스에 저장하고, 나머지는 전부 큐에 넣으면 됩니다.
각 작업마다 별도의 큐를 만들 수도 있습니다. 이메일 큐, SMS 큐, 재고 큐처럼요.
그러면 각 워커가 독립적으로 처리할 수 있습니다. 완전 관리형의 의미 AWS SQS의 가장 큰 장점은 완전 관리형이라는 점입니다.
Redis나 RabbitMQ로 직접 큐를 운영하려면 서버 관리, 모니터링, 스케일링을 모두 직접 해야 합니다. 하지만 SQS는 AWS가 전부 알아서 합니다.
메시지가 많아지면 자동으로 확장됩니다. 서버가 다운될 걱정도 없습니다.
우리는 그저 메시지를 보내고 받기만 하면 됩니다. 비용 구조 SQS는 사용한 만큼만 비용을 지불합니다.
처음 100만 건의 요청은 매달 무료입니다. 그 이후로는 100만 건당 40센트 정도입니다.
스타트업이나 소규모 서비스에서 부담 없이 사용할 수 있는 수준입니다. 정리 김개발 씨는 설명을 듣고 눈이 반짝였습니다.
"아, 그럼 이메일 전송을 큐로 분리하면 API 응답이 빨라지는 거네요!" 맞습니다. SQS를 사용하면 느린 작업을 백그라운드로 분리할 수 있습니다.
API는 빠르게 응답하고, 실제 작업은 안전하게 나중에 처리됩니다. 이것이 바로 현대적인 비동기 처리 패턴의 핵심입니다.
실전 팁
💡 - 첫 100만 건 요청은 매달 무료이므로 부담 없이 시작할 수 있습니다
- MessageBody는 최대 256KB까지 가능하며, 더 큰 데이터는 S3에 저장하고 참조만 전달하세요
- 메시지는 기본 4일, 최대 14일까지 큐에 보관됩니다
2. 표준 큐 vs FIFO 큐
김개발 씨가 SQS 콘솔에서 큐를 만들려고 하자 선택지가 두 개 나왔습니다. "Standard"와 "FIFO".
무슨 차이일까요? 잘못 선택하면 큰일 날까요?
SQS에는 두 가지 타입이 있습니다. 표준 큐는 초당 무제한 처리가 가능하지만 순서가 보장되지 않습니다.
FIFO 큐는 순서를 엄격하게 보장하지만 초당 3,000개까지만 처리됩니다. 용도에 맞게 선택해야 합니다.
다음 코드를 살펴봅시다.
// 표준 큐 - 순서 상관없는 이메일 발송
const standardParams = {
QueueUrl: 'https://sqs.ap-northeast-2.amazonaws.com/123/emails.fifo',
MessageBody: JSON.stringify({ email: 'user@example.com' })
};
// FIFO 큐 - 순서가 중요한 주문 상태 업데이트
const fifoParams = {
QueueUrl: 'https://sqs.ap-northeast-2.amazonaws.com/123/orders.fifo',
MessageBody: JSON.stringify({ orderId: '12345', status: 'SHIPPED' }),
// 메시지 그룹 ID - 같은 주문은 순서 보장
MessageGroupId: 'order-12345',
// 중복 제거 ID - 5분 내 같은 ID는 중복 처리 안 함
MessageDeduplicationId: `${Date.now()}-12345`
};
await client.send(new SendMessageCommand(fifoParams));
박시니어 씨가 김개발 씨의 화면을 들여다봤습니다. "아, 큐 타입을 선택하는 거구나.
이게 중요해요." 두 가지 선택지 SQS를 처음 만들 때 가장 먼저 결정해야 할 것이 큐의 타입입니다. 마치 식당에서 일반석과 예약석을 선택하는 것과 비슷합니다.
일반석은 빠르게 앉을 수 있지만 순서가 바뀔 수 있습니다. 예약석은 정확한 순서가 보장되지만 수용 인원이 제한적입니다.
표준 큐의 특징 표준 큐는 AWS의 기본 큐입니다. 가장 큰 장점은 무제한 처리량입니다.
초당 몇만 건이 들어와도 문제없습니다. AWS가 자동으로 스케일링해서 처리합니다.
또한 비용이 저렴합니다. 하지만 단점도 있습니다.
순서가 보장되지 않습니다. A, B, C 순서로 넣었는데 B, A, C 순서로 나올 수 있습니다.
또한 극히 드물게 같은 메시지가 두 번 전달될 수도 있습니다. "최소 한 번 전달(at-least-once delivery)"이라고 부릅니다.
언제 표준 큐를 쓸까 순서가 중요하지 않은 작업에 적합합니다. 이메일 발송이 대표적입니다.
A 사용자에게 먼저 보내든 B 사용자에게 먼저 보내든 상관없습니다. 이미지 리사이징, 로그 처리, 알림 발송 같은 작업도 마찬가지입니다.
각 메시지가 독립적이라면 표준 큐가 최선의 선택입니다. FIFO 큐의 특징 FIFO는 "First In First Out"의 약자입니다.
가장 큰 장점은 엄격한 순서 보장입니다. A, B, C 순서로 넣으면 무조건 A, B, C 순서로 나옵니다.
또한 **정확히 한 번 전달(exactly-once processing)**을 보장합니다. 중복이 절대 발생하지 않습니다.
대신 제약사항이 있습니다. 초당 최대 3,000개의 메시지만 처리할 수 있습니다.
배치를 사용하면 초당 3만 개까지 가능하지만 그래도 제한적입니다. 또한 큐 이름이 반드시 ".fifo"로 끝나야 합니다.
메시지 그룹의 개념 FIFO 큐에는 MessageGroupId라는 개념이 있습니다. 같은 그룹 ID를 가진 메시지들끼리만 순서가 보장됩니다.
예를 들어 주문 번호를 그룹 ID로 사용하면 같은 주문의 이벤트는 순서대로 처리됩니다. 하지만 다른 주문은 병렬로 처리됩니다.
이게 왜 중요할까요? 만약 그룹을 나누지 않으면 모든 메시지가 순차 처리됩니다.
주문 A를 처리하는 동안 주문 B는 기다려야 합니다. 하지만 그룹을 나누면 각 주문이 독립적으로 병렬 처리되면서도, 같은 주문 내에서는 순서가 보장됩니다.
중복 제거 메커니즘 MessageDeduplicationId는 중복을 막는 ID입니다. 5분 이내에 같은 ID의 메시지가 다시 들어오면 무시됩니다.
네트워크 문제로 같은 요청이 두 번 전송되더라도 한 번만 처리됩니다. 이 ID를 생략하면 메시지 본문의 SHA-256 해시를 자동으로 사용합니다.
실무 활용 사례 김개발 씨가 물었습니다. "그럼 언제 FIFO를 써야 하나요?" 주문 상태 업데이트가 대표적입니다.
"결제 완료 → 배송 준비 → 배송 중 → 배송 완료" 순서가 중요합니다. 만약 "배송 완료"가 "배송 준비"보다 먼저 처리되면 큰일입니다.
주식 거래, 금융 트랜잭션, 채팅 메시지 같은 경우도 FIFO를 써야 합니다. 순서가 바뀌면 비즈니스 로직이 깨집니다.
코드 비교 위의 코드를 보면 차이가 명확합니다. 표준 큐는 그냥 MessageBody만 있으면 됩니다.
하지만 FIFO 큐는 MessageGroupId와 MessageDeduplicationId가 추가로 필요합니다. 큐 URL도 ".fifo"로 끝나는 것을 볼 수 있습니다.
성능과 비용 고려 표준 큐는 초당 수만 건을 처리할 수 있고 비용이 저렴합니다. FIFO 큐는 초당 3,000건 제한이 있고 비용이 약간 더 비쌉니다.
하지만 순서가 중요하다면 선택의 여지가 없습니다. 정리 박시니어 씨가 정리해줬습니다.
"순서가 중요하지 않고 대량 처리가 필요하면 표준 큐, 순서가 중요하고 중복이 절대 안 되면 FIFO 큐를 쓰세요." 대부분의 경우 표준 큐로 충분합니다. 하지만 금융, 거래, 상태 관리처럼 순서가 중요한 도메인이라면 반드시 FIFO를 선택해야 합니다.
실전 팁
💡 - FIFO 큐 이름은 반드시 ".fifo"로 끝나야 합니다
- MessageGroupId를 잘 설계하면 병렬 처리와 순서 보장을 동시에 얻을 수 있습니다
- 순서가 정말 중요한지 먼저 고민하세요. 대부분은 표준 큐로 충분합니다
3. 메시지 전송과 수신
SQS에 메시지를 넣는 건 알겠는데, 그럼 누가 꺼내서 처리하나요? 김개발 씨는 워커 프로세스를 만들어야 한다는 사실을 깨달았습니다.
어떻게 만들어야 할까요?
SQS에서 메시지를 꺼내는 것을 **폴링(polling)**이라고 합니다. 짧은 폴링은 즉시 반환되지만 빈 응답이 많고, 긴 폴링은 메시지가 올 때까지 최대 20초 기다리므로 효율적입니다.
메시지를 처리한 후에는 반드시 삭제해야 합니다.
다음 코드를 살펴봅시다.
// 메시지 수신 및 처리 워커
const { ReceiveMessageCommand, DeleteMessageCommand } = require('@aws-sdk/client-sqs');
async function processMessages() {
while (true) {
// 긴 폴링으로 메시지 수신 (최대 20초 대기)
const receiveParams = {
QueueUrl: 'https://sqs.ap-northeast-2.amazonaws.com/123/emails',
MaxNumberOfMessages: 10, // 한 번에 최대 10개
WaitTimeSeconds: 20, // 긴 폴링 - 메시지 올 때까지 대기
VisibilityTimeout: 30 // 처리 중에는 다른 워커가 못 가져감
};
const { Messages } = await client.send(new ReceiveMessageCommand(receiveParams));
if (!Messages || Messages.length === 0) continue;
// 각 메시지 처리
for (const message of Messages) {
const data = JSON.parse(message.Body);
await sendEmail(data.email); // 실제 이메일 발송
// 처리 완료 후 메시지 삭제 (중요!)
await client.send(new DeleteMessageCommand({
QueueUrl: receiveParams.QueueUrl,
ReceiptHandle: message.ReceiptHandle
}));
}
}
}
김개발 씨가 SQS에 메시지를 넣는 코드를 작성했습니다. 테스트해보니 메시지가 잘 들어갑니다.
AWS 콘솔에서 확인하니 "Messages Available: 1"이라고 표시됩니다. 그런데 문제가 생겼습니다.
메시지가 큐에만 쌓이고 실제로 이메일이 발송되지 않습니다. 당연합니다.
아무도 꺼내지 않으니까요. 메시지 수신의 원리 SQS는 푸시(push) 방식이 아니라 폴(pull) 방식입니다.
무슨 뜻일까요? 카카오톡은 푸시 방식입니다.
메시지가 오면 서버가 알아서 우리 폰에 알림을 보냅니다. 하지만 SQS는 폴 방식입니다.
우리가 직접 "메시지 있어요?"라고 물어봐야 합니다. 이걸 **폴링(polling)**이라고 부릅니다.
워커 프로세스를 만들어서 계속 큐를 확인해야 합니다. 마치 우편함을 주기적으로 확인하는 것처럼요.
짧은 폴링 vs 긴 폴링 폴링에는 두 가지 방식이 있습니다. **짧은 폴링(short polling)**은 즉시 반환됩니다.
메시지가 없으면 빈 응답을 받습니다. 그러면 다시 요청합니다.
또 없으면 또 요청합니다. 이렇게 계속 반복하면 API 호출 횟수가 엄청나게 많아집니다.
비용도 늘어나고 비효율적입니다. **긴 폴링(long polling)**은 다릅니다.
"메시지 있어요?"라고 물어봤는데 없으면, AWS가 최대 20초까지 기다립니다. 그 사이에 메시지가 들어오면 즉시 반환합니다.
20초 동안 아무것도 안 오면 그때 빈 응답을 줍니다. 긴 폴링이 훨씬 효율적입니다.
API 호출 횟수가 줄어들고, 메시지를 더 빨리 받을 수 있습니다. AWS도 긴 폴링을 권장합니다.
코드 분석 위의 코드를 살펴보겠습니다. WaitTimeSeconds: 20이 긴 폴링을 활성화하는 핵심입니다.
0으로 설정하면 짧은 폴링입니다. MaxNumberOfMessages: 10은 한 번에 최대 10개까지 가져온다는 뜻입니다.
실제로는 10개보다 적게 올 수도 있습니다. VisibilityTimeout: 30이 중요합니다.
이건 무슨 의미일까요? 가시성 타임아웃의 개념 메시지를 가져가면 그 메시지는 다른 워커에게 보이지 않게 됩니다.
30초 동안요. 왜 이렇게 동작할까요?
워커 A가 메시지를 가져가서 처리 중인데, 워커 B가 또 같은 메시지를 가져가면 안 되니까요. 그래서 AWS는 "이 메시지는 지금 누가 처리 중이에요"라고 표시합니다.
만약 30초 안에 메시지를 삭제하지 않으면 어떻게 될까요? 메시지가 다시 큐에 나타납니다.
다른 워커가 또 가져갈 수 있습니다. 이건 의도된 동작입니다.
워커가 중간에 죽으면 메시지가 유실되지 않고 다른 워커가 처리할 수 있게 하는 겁니다. 메시지 삭제의 중요성 처리가 끝나면 반드시 DeleteMessage를 호출해야 합니다.
많은 초보자가 이 부분을 빼먹습니다. 그러면 어떻게 될까요?
메시지가 삭제되지 않고 계속 큐에 남아있습니다. VisibilityTimeout이 지나면 다시 나타나서 또 처리됩니다.
같은 이메일이 계속 발송됩니다. ReceiptHandle은 메시지의 영수증 번호 같은 겁니다.
이 값을 사용해서 "내가 방금 받은 이 메시지를 삭제해주세요"라고 요청합니다. 배치로 받기 MaxNumberOfMessages를 10으로 설정했습니다.
이렇게 하면 한 번에 여러 개를 받을 수 있습니다. API 호출 횟수가 줄어들어 효율적입니다.
하지만 주의할 점이 있습니다. 10개를 요청했다고 해서 항상 10개가 오는 건 아닙니다.
큐에 메시지가 5개밖에 없으면 5개만 옵니다. 심지어 분산 시스템 특성상 메시지가 있는데도 빈 응답이 올 수 있습니다.
그래서 while 루프로 계속 폴링해야 합니다. 무한 루프 워커 위 코드는 while(true)로 무한 루프를 돕니다.
이게 일반적인 패턴입니다. 서버가 켜져 있는 동안 계속 큐를 확인하며 메시지를 처리합니다.
메시지가 없을 때는 긴 폴링 덕분에 20초간 대기하므로 CPU를 낭비하지 않습니다. 에러 처리 실무에서는 에러 처리가 중요합니다.
sendEmail()이 실패하면 어떻게 해야 할까요? 메시지를 삭제하지 않으면 됩니다.
그러면 VisibilityTimeout 후에 다시 시도됩니다. 하지만 계속 실패하면 무한 반복됩니다.
이 문제는 나중에 DLQ에서 해결하겠습니다. 실무 배포 이런 워커는 보통 별도의 서버나 컨테이너에서 실행합니다.
EC2, ECS, Lambda 등 어디서든 실행할 수 있습니다. PM2나 systemd로 프로세스를 관리하면 죽었을 때 자동으로 재시작됩니다.
정리 박시니어 씨가 말했습니다. "메시지를 넣는 건 쉬워요.
하지만 꺼내서 처리하는 워커를 만드는 게 진짜 일입니다. 긴 폴링, VisibilityTimeout, 메시지 삭제, 이 세 가지만 제대로 이해하면 됩니다." 김개발 씨는 워커 코드를 작성하고 실행했습니다.
큐에 쌓여있던 메시지들이 하나씩 처리되며 이메일이 발송되기 시작했습니다.
실전 팁
💡 - 반드시 긴 폴링(WaitTimeSeconds: 20)을 사용하세요
- VisibilityTimeout은 메시지 처리 시간보다 길게 설정하세요
- 처리 성공 후 DeleteMessage를 잊지 마세요
4. Lambda와 SQS 연동
김개발 씨는 EC2에 워커를 띄우고 있었습니다. 그런데 팀장님이 말했습니다.
"서버 관리가 번거롭지 않아요? Lambda를 써보는 게 어때요?" Lambda로 SQS를 처리할 수 있다고?
Lambda와 SQS를 연동하면 워커 서버 없이도 메시지를 처리할 수 있습니다. SQS가 메시지가 들어오면 자동으로 Lambda를 트리거합니다.
폴링 로직을 직접 작성할 필요가 없고, 사용한 만큼만 비용을 지불합니다.
다음 코드를 살펴봅시다.
// Lambda 함수 - SQS 이벤트 핸들러
exports.handler = async (event) => {
// SQS가 자동으로 Lambda를 트리거하며 Records 전달
for (const record of event.Records) {
const message = JSON.parse(record.body);
try {
// 메시지 처리 로직
await sendEmail(message.email);
console.log(`이메일 발송 완료: ${message.email}`);
// 성공하면 Lambda가 자동으로 메시지 삭제
} catch (error) {
// 실패하면 에러 던지기 - 메시지는 삭제되지 않음
console.error('이메일 발송 실패:', error);
throw error; // 재시도를 위해 에러 전파
}
}
return { statusCode: 200 };
};
// 배치 크기 설정: 람다 콘솔에서 1~10개 설정 가능
// 배치 윈도우: 최대 5분까지 메시지 모아서 처리 가능
김개발 씨는 고민에 빠졌습니다. 워커 서버를 EC2에서 돌리고 있는데 관리가 번거롭습니다.
서버가 죽으면 재시작해야 하고, 스케일링도 직접 해야 합니다. 메시지가 많을 때는 서버를 늘리고, 적을 때는 줄여야 하는데 자동화가 쉽지 않습니다.
박시니어 씨가 새로운 방법을 알려줬습니다. "Lambda를 쓰면 서버 관리가 필요 없어요." 서버리스 워커 Lambda는 AWS의 서버리스 컴퓨팅 서비스입니다.
서버를 직접 관리하지 않아도 됩니다. 코드만 올려두면 필요할 때 자동으로 실행됩니다.
사용한 시간만큼만 비용을 지불합니다. 아무 일도 하지 않을 때는 비용이 0원입니다.
SQS와 Lambda를 연결하면 어떻게 될까요? 우리가 폴링 코드를 작성할 필요가 없습니다.
AWS가 알아서 큐를 모니터링하다가 메시지가 들어오면 Lambda를 실행합니다. 이벤트 소스 매핑 Lambda와 SQS를 연결하는 것을 Event Source Mapping이라고 부릅니다.
AWS 콘솔에서 Lambda 함수를 만들고, "트리거 추가"를 누르고, SQS를 선택하면 됩니다. 그러면 끝입니다.
이제 메시지가 큐에 들어올 때마다 Lambda가 자동으로 실행됩니다. 내부적으로는 AWS가 우리 대신 폴링을 해줍니다.
긴 폴링으로 효율적으로 메시지를 가져와서 Lambda에 전달합니다. 우리는 그저 메시지를 받아서 처리하기만 하면 됩니다.
코드 구조 Lambda 코드는 EC2 워커와 많이 다릅니다. 먼저 while 루프가 없습니다.
Lambda는 메시지가 올 때마다 실행되므로 무한 루프가 필요 없습니다. event.Records 배열에 메시지가 들어옵니다.
기본적으로 1~10개까지 배치로 들어올 수 있습니다. DeleteMessage 호출도 없습니다.
Lambda가 성공적으로 종료되면 AWS가 자동으로 메시지를 삭제합니다. 우리는 신경 쓸 필요가 없습니다.
에러 처리의 차이 여기가 중요합니다. 처리 중에 에러가 발생하면 throw error로 에러를 던져야 합니다.
그러면 Lambda가 실패로 간주하고 메시지를 삭제하지 않습니다. 메시지는 큐로 돌아가고 나중에 다시 시도됩니다.
만약 에러를 잡아서(try-catch) 아무 일도 없었던 것처럼 return하면 어떻게 될까요? Lambda는 성공으로 판단하고 메시지를 삭제합니다.
실제로는 실패했는데 메시지가 사라지는 겁니다. 조심해야 합니다.
배치 크기 설정 Lambda 트리거 설정에서 Batch size를 지정할 수 있습니다. 1~10까지 설정 가능합니다.
10으로 설정하면 메시지 10개가 모일 때마다 Lambda가 한 번 실행됩니다. Lambda 실행 횟수가 줄어들어 비용이 절감됩니다.
또한 Batch window를 설정할 수 있습니다. 예를 들어 5분으로 설정하면, 메시지가 1개만 있어도 5분이 지나면 Lambda를 실행합니다.
메시지가 적을 때 오래 기다리지 않게 하는 겁니다. 동시성 제어 Lambda는 기본적으로 무제한 동시 실행이 가능합니다.
메시지가 1,000개 쌓여있으면 Lambda 인스턴스가 100개 동시에 실행될 수도 있습니다. 문제는 데이터베이스나 외부 API가 감당하지 못할 수 있다는 점입니다.
이럴 때는 Reserved concurrency를 설정합니다. 예를 들어 10으로 설정하면 최대 10개의 Lambda만 동시 실행됩니다.
메시지 처리 속도는 느려지지만 시스템이 안정적입니다. 비용 비교 EC2로 워커를 돌리면 24시간 내내 비용이 나갑니다.
t3.small 기준 월 15달러 정도입니다. Lambda는 사용한 만큼만 비용을 냅니다.
매달 100만 건 요청과 40만 GB-초의 컴퓨팅은 무료입니다. 이메일 발송 같은 작업은 대부분 무료 티어 안에 들어갑니다.
메시지가 많아도 EC2보다 저렴한 경우가 많습니다. 실무 활용 대부분의 경우 Lambda가 더 나은 선택입니다.
서버 관리가 없고, 자동 스케일링되고, 비용도 저렴합니다. 하지만 Lambda에도 제약이 있습니다.
실행 시간이 최대 15분입니다. 메모리는 최대 10GB입니다.
처리 시간이 오래 걸리거나 많은 메모리가 필요하면 EC2를 써야 합니다. 모니터링 Lambda는 자동으로 CloudWatch에 로그를 남깁니다.
console.log로 출력한 내용이 전부 CloudWatch Logs에 저장됩니다. 에러가 발생하면 CloudWatch Alarms로 알림을 받을 수 있습니다.
X-Ray를 연결하면 성능 분석도 가능합니다. 정리 김개발 씨는 Lambda 함수를 만들고 SQS와 연결했습니다.
EC2 워커를 종료했습니다. 이제 메시지가 들어오면 자동으로 Lambda가 실행되며 처리됩니다.
서버 관리 걱정이 사라졌습니다. 박시니어 씨가 말했습니다.
"대부분의 경우 Lambda로 충분합니다. 처음 시작한다면 Lambda를 추천합니다."
실전 팁
💡 - Lambda 무료 티어는 매달 100만 건 요청까지 무료입니다
- 에러 발생 시 반드시 throw error로 전파해야 재시도됩니다
- 동시성이 높으면 Reserved concurrency로 제한하세요
5. 배치 처리 최적화
Lambda가 메시지를 하나씩 처리하니 느립니다. 메시지 100개를 처리하는 데 Lambda가 100번 실행됩니다.
비용도 많이 나오고 효율도 떨어집니다. 배치로 묶어서 처리할 수는 없을까요?
SQS는 한 번에 최대 10개의 메시지를 배치로 전송하고 받을 수 있습니다. Lambda에서도 배치로 처리하면 실행 횟수가 줄어들어 비용이 절감됩니다.
SendMessageBatch와 DeleteMessageBatch를 사용하면 API 호출 횟수도 크게 줄어듭니다.
다음 코드를 살펴봅시다.
// 배치로 메시지 전송 (최대 10개)
const { SendMessageBatchCommand } = require('@aws-sdk/client-sqs');
async function sendBatchEmails(users) {
const entries = users.slice(0, 10).map((user, index) => ({
Id: String(index), // 배치 내에서 고유한 ID
MessageBody: JSON.stringify({ email: user.email, name: user.name })
}));
const result = await client.send(new SendMessageBatchCommand({
QueueUrl: 'https://sqs.ap-northeast-2.amazonaws.com/123/emails',
Entries: entries
}));
// 성공/실패 확인
console.log(`성공: ${result.Successful?.length || 0}개`);
console.log(`실패: ${result.Failed?.length || 0}개`);
}
// Lambda에서 배치 처리
exports.handler = async (event) => {
const emails = event.Records.map(r => JSON.parse(r.body).email);
// 10개를 한 번에 처리 (배치 크기에 따라)
await sendBulkEmails(emails);
};
김개발 씨의 서비스가 성장하며 하루에 수만 건의 이메일을 발송하게 되었습니다. Lambda 비용 명세서를 보니 생각보다 많이 나왔습니다.
메시지 하나당 Lambda가 한 번씩 실행되니 실행 횟수가 엄청났습니다. 박시니어 씨가 코드를 보더니 말했습니다.
"배치로 처리하면 비용을 10분의 1로 줄일 수 있어요." 배치 처리의 개념 마트에서 물건을 살 때를 생각해봅시다. 장바구니에 물건 하나를 담고 계산대에 갑니다.
계산하고 나옵니다. 다시 들어가서 물건 하나를 담고 계산합니다.
이걸 10번 반복하면 얼마나 비효율적일까요? 10개를 한 번에 담아서 한 번에 계산하는 게 당연히 빠릅니다.
SQS도 마찬가지입니다. 메시지를 하나씩 보내는 대신 10개를 묶어서 한 번에 보낼 수 있습니다.
API 호출이 10번에서 1번으로 줄어듭니다. SendMessageBatch 사용법 단일 메시지를 보낼 때는 SendMessageCommand를 썼습니다.
배치는 SendMessageBatchCommand를 씁니다. Entries 배열에 메시지들을 담습니다.
최대 10개까지 가능합니다. 각 메시지는 배치 내에서 고유한 Id를 가져야 합니다.
이건 그냥 "0", "1", "2" 같은 문자열이면 됩니다. 중요한 점은 부분 실패가 가능하다는 것입니다.
10개 중 8개는 성공하고 2개는 실패할 수 있습니다. result.Successful과 result.Failed로 확인할 수 있습니다.
실패한 것만 재시도하면 됩니다. Lambda 배치 크기 설정 Lambda 트리거 설정에서 Batch size를 10으로 올리면 어떻게 될까요?
메시지 10개가 모일 때까지 기다렸다가 한 번에 Lambda를 실행합니다. event.Records에 10개의 메시지가 배열로 들어옵니다.
for 루프로 10개를 처리하면 됩니다. 이렇게 하면 Lambda 실행 횟수가 10분의 1로 줄어듭니다.
비용도 크게 절감됩니다. 배치 윈도우 활용 문제는 메시지가 항상 10개씩 들어오지 않는다는 점입니다.
메시지가 3개만 있으면 어떻게 될까요? 10개가 될 때까지 계속 기다립니다.
새벽에 트래픽이 적으면 한없이 기다릴 수도 있습니다. 이걸 해결하는 게 Batch window입니다.
예를 들어 300초(5분)로 설정하면, 10개가 안 모여도 5분이 지나면 Lambda를 실행합니다. 3개만 있어도 5분 후에 처리됩니다.
DeleteMessageBatch로 삭제 메시지 처리 후 삭제할 때도 배치를 쓸 수 있습니다. EC2 워커에서 10개를 받아서 처리한 후 DeleteMessageBatchCommand로 한 번에 삭제합니다.
DeleteMessage를 10번 호출하는 것보다 훨씬 효율적입니다. Lambda는 자동으로 삭제하므로 DeleteMessageBatch를 직접 쓸 일은 거의 없습니다.
하지만 부분 실패 처리를 직접 하고 싶다면 수동으로 삭제할 수도 있습니다. 비용 계산 구체적으로 비용이 얼마나 줄어들까요?
메시지 10만 건을 처리한다고 가정합니다. 배치 없이 하나씩 처리하면 Lambda가 10만 번 실행됩니다.
배치 크기 10으로 처리하면 1만 번만 실행됩니다. Lambda 비용은 실행 횟수에 비례하므로 비용이 10분의 1로 줄어듭니다.
SQS API 호출도 마찬가지입니다. SendMessage를 10만 번 하면 10만 건의 요청입니다.
SendMessageBatch로 1만 번 하면 1만 건입니다. 비용이 90% 절감됩니다.
실무 패턴 실제로는 어떻게 사용할까요? 회원가입 API에서 사용자 100명이 동시에 가입했다고 합시다.
이메일을 하나씩 큐에 넣는 대신, 100개를 10개씩 묶어서 SendMessageBatch를 10번 호출합니다. API 호출이 100번에서 10번으로 줄어듭니다.
Lambda에서는 배치 크기를 10으로 설정해두면 자동으로 10개씩 묶어서 처리됩니다. 우리는 그냥 for 루프로 돌리기만 하면 됩니다.
주의사항 배치에도 제약이 있습니다. 최대 10개까지만 가능합니다.
256KB 제한도 배치 전체에 적용됩니다. 10개의 메시지 크기 합계가 256KB를 넘으면 안 됩니다.
또한 배치 내에서 하나라도 형식이 잘못되면 전체가 실패할 수 있습니다. 따라서 메시지 형식을 검증하고 보내는 게 좋습니다.
부분 실패 처리 SendMessageBatch의 결과를 꼼꼼히 확인해야 합니다. 10개를 보냈는데 8개만 성공했다면, result.Failed 배열에 실패한 메시지 정보가 들어있습니다.
Id를 보고 어떤 메시지가 실패했는지 알 수 있습니다. 실패한 것만 다시 보내면 됩니다.
정리 김개발 씨는 코드를 배치 방식으로 수정했습니다. Lambda 배치 크기를 10으로 올리고, SendMessageBatch를 사용했습니다.
다음 달 청구서를 보니 Lambda 비용이 80% 이상 줄어들었습니다. 박시니어 씨가 말했습니다.
"메시지 처리량이 많다면 무조건 배치를 써야 합니다. 비용과 성능 모두 개선됩니다."
실전 팁
💡 - 배치 크기는 최대 10개, 총 크기는 256KB 제한입니다
- Lambda 배치 윈도우를 설정해 메시지가 적을 때도 빠르게 처리하세요
- 부분 실패를 확인하고 실패한 메시지만 재시도하세요
6. 데드레터큐 DLQ
메시지 처리가 계속 실패하면 어떻게 될까요? 같은 메시지가 무한 반복됩니다.
김개발 씨는 잘못된 이메일 주소 때문에 같은 메시지가 수백 번 재시도되는 것을 발견했습니다. 이런 문제를 어떻게 해결할까요?
**데드레터큐(DLQ)**는 처리 실패한 메시지를 모아두는 별도의 큐입니다. 원본 큐에서 일정 횟수 이상 실패하면 자동으로 DLQ로 이동합니다.
실패 원인을 분석하고 수동으로 처리하거나 알림을 받을 수 있습니다.
다음 코드를 살펴봅시다.
// 1. DLQ 생성 (콘솔 또는 코드로)
const { CreateQueueCommand } = require('@aws-sdk/client-sqs');
// DLQ 큐 생성
const dlqResult = await client.send(new CreateQueueCommand({
QueueName: 'email-dlq'
}));
// 원본 큐에 DLQ 설정 (Redrive Policy)
const redrivePolicy = {
deadLetterTargetArn: 'arn:aws:sqs:ap-northeast-2:123456789:email-dlq',
maxReceiveCount: 3 // 3번 실패하면 DLQ로 이동
};
await client.send(new SetQueueAttributesCommand({
QueueUrl: 'https://sqs.ap-northeast-2.amazonaws.com/123/emails',
Attributes: {
RedrivePolicy: JSON.stringify(redrivePolicy)
}
}));
// 2. DLQ 모니터링 및 알림
// CloudWatch Alarm으로 DLQ에 메시지가 쌓이면 알림
김개발 씨는 CloudWatch Logs를 보다가 이상한 점을 발견했습니다. 같은 에러 로그가 수십 번 반복됩니다.
"Invalid email format"이라는 에러입니다. 누군가 잘못된 이메일 주소로 가입했고, 이메일 발송이 계속 실패하는 겁니다.
문제는 이 메시지가 무한 반복된다는 것입니다. 처리 실패 → 큐로 복귀 → 다시 시도 → 실패 → 복귀 → 반복.
Lambda가 쓸데없이 계속 실행되며 비용만 낭비됩니다. 데드레터큐란 **DLQ(Dead Letter Queue)**는 말 그대로 "죽은 편지 보관함"입니다.
우체국에서 주소가 잘못된 편지는 배달할 수 없습니다. 이런 편지들을 모아두는 별도의 보관함이 있습니다.
나중에 수동으로 확인하고 처리합니다. SQS의 DLQ도 똑같습니다.
처리가 계속 실패하는 메시지를 별도의 큐로 이동시킵니다. 원본 큐는 정상 메시지만 처리하고, 문제 있는 메시지는 DLQ에 격리됩니다.
왜 필요한가 DLQ가 없으면 어떻게 될까요? 메시지 처리가 실패하면 다시 큐로 돌아갑니다.
또 시도하고 또 실패합니다. 무한 반복입니다.
이건 여러 문제를 일으킵니다. 첫째, 리소스 낭비입니다.
Lambda가 계속 실행되며 비용이 나갑니다. 둘째, 정상 메시지 처리가 지연됩니다.
실패 메시지가 계속 앞에 끼어들면 정상 메시지가 밀립니다. 셋째, 문제를 인지하기 어렵습니다.
로그에 묻혀서 중요한 에러를 놓칠 수 있습니다. maxReceiveCount 설정 DLQ의 핵심은 maxReceiveCount입니다.
이 값을 3으로 설정하면 어떻게 될까요? 메시지가 3번 실패하면 자동으로 DLQ로 이동합니다.
4번째 시도는 하지 않습니다. 원본 큐에서 완전히 제거되고 DLQ로 옮겨집니다.
3이 적절한 값입니다. 1번 실패는 일시적인 네트워크 문제일 수 있습니다.
2~3번 재시도하면 대부분 해결됩니다. 하지만 3번 연속 실패하면 이건 구조적인 문제입니다.
코드 버그거나 잘못된 데이터입니다. 계속 시도해봐야 소용없습니다.
Redrive Policy 설정 RedrivePolicy는 DLQ 설정을 담는 JSON입니다. deadLetterTargetArn은 DLQ의 ARN입니다.
어느 큐로 이동시킬지 지정합니다. maxReceiveCount는 몇 번 실패하면 이동시킬지 정합니다.
이 정책을 원본 큐의 Attributes로 설정하면 DLQ가 활성화됩니다. 이제 실패한 메시지는 자동으로 DLQ로 이동합니다.
DLQ 모니터링 DLQ는 만들어두고 잊어버리면 안 됩니다. DLQ에 메시지가 쌓인다는 건 뭔가 문제가 있다는 신호입니다.
CloudWatch Alarm을 설정해서 DLQ의 ApproximateNumberOfMessagesVisible 메트릭을 모니터링합니다. 메시지가 1개 이상 쌓이면 SNS로 알림을 보내도록 설정합니다.
이메일이나 슬랙으로 알림을 받으면 즉시 확인할 수 있습니다. 실패 원인 분석 DLQ에 메시지가 쌓이면 직접 확인해야 합니다.
AWS 콘솔에서 DLQ를 열고 "Poll for messages"를 누르면 메시지를 볼 수 있습니다. 메시지 내용을 보고 왜 실패했는지 분석합니다.
흔한 원인은 잘못된 데이터 형식, 외부 API 변경, 권한 문제 등입니다. 원인을 파악하고 코드를 수정하거나 데이터를 정제합니다.
메시지 복구 문제를 해결했으면 DLQ의 메시지를 다시 처리해야 합니다. AWS 콘솔에서 "Redrive"를 누르면 DLQ의 메시지를 원본 큐로 다시 보낼 수 있습니다.
또는 별도의 스크립트로 DLQ에서 메시지를 읽어서 다시 전송합니다. 중요한 건 원인을 먼저 해결하는 것입니다.
코드를 고치지 않고 그냥 다시 보내면 또 DLQ로 갑니다. Lambda와 DLQ Lambda에도 자체 DLQ 기능이 있습니다.
SQS DLQ와는 다릅니다. Lambda DLQ는 Lambda 실행 자체가 실패했을 때 이벤트를 보관합니다.
보통은 SQS DLQ만 사용해도 충분합니다. SQS → Lambda 구조에서는 SQS의 DLQ가 더 적합합니다.
메시지 레벨에서 관리하는 게 명확합니다. 실무 활용 실제 프로덕션에서는 모든 중요한 큐에 DLQ를 설정합니다.
결제 처리, 주문 처리 같은 중요한 작업은 반드시 DLQ가 있어야 합니다. 실패한 메시지를 놓치면 매출 손실로 이어집니다.
로그 처리, 분석 같은 덜 중요한 작업도 DLQ를 두는 게 좋습니다. 실패 패턴을 분석하면 시스템 개선 포인트를 찾을 수 있습니다.
보존 기간 설정 DLQ의 메시지도 영원히 보관되지 않습니다. 기본 4일, 최대 14일입니다.
DLQ는 보존 기간을 14일로 설정하는 게 좋습니다. 주말에 문제가 발생해도 월요일에 확인할 수 있습니다.
정리 김개발 씨는 DLQ를 설정하고 CloudWatch Alarm을 연결했습니다. 다음 날 슬랙으로 알림이 왔습니다.
"DLQ에 메시지 5개 유입." 확인해보니 이메일 형식 검증 버그였습니다. 코드를 수정하고 DLQ의 메시지를 다시 처리했습니다.
박시니어 씨가 칭찬했습니다. "DLQ는 안전망입니다.
문제를 숨기지 않고 드러내서 해결할 수 있게 해줍니다."
실전 팁
💡 - maxReceiveCount는 보통 3~5가 적절합니다
- DLQ에는 반드시 CloudWatch Alarm을 설정하세요
- DLQ 메시지 보존 기간은 14일로 설정하는 것이 안전합니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
서비스 메시 완벽 가이드
마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.
EFK 스택 로깅 완벽 가이드
마이크로서비스 환경에서 로그를 효과적으로 수집하고 분석하는 EFK 스택(Elasticsearch, Fluentd, Kibana)의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 정리한 가이드입니다.
Grafana 대시보드 완벽 가이드
실시간 모니터링의 핵심, Grafana 대시보드를 처음부터 끝까지 배워봅니다. Prometheus 연동부터 알람 설정까지, 초급 개발자도 쉽게 따라할 수 있는 실전 가이드입니다.
분산 추적 완벽 가이드
마이크로서비스 환경에서 요청의 전체 흐름을 추적하는 분산 추적 시스템의 핵심 개념을 배웁니다. Trace, Span, Trace ID 전파, 샘플링 전략까지 실무에 필요한 모든 것을 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.