AWS Lambda - 서버리스 컴퓨팅의 핵심
AWS Lambda의 개념부터 실전 활용까지 완벽 가이드
AWS Lambda란?
AWS Lambda는 서버를 프로비저닝하거나 관리하지 않고도 코드를 실행할 수 있는 서버리스 컴퓨팅 서비스임.
이벤트에 응답하여 코드를 실행하고, 컴퓨팅 리소스를 자동으로 관리함. 사용한 컴퓨팅 시간에 대해서만 요금이 부과되며, 코드가 실행되지 않을 때는 요금이 발생하지 않음.
왜 Lambda를 사용해야 하는가?
기존 서버 기반 방식의 문제점
전통적인 EC2 기반 아키텍처:
1
2
3
4
5
6
7
┌─────────────────────────────────────┐
│ EC2 인스턴스 (24시간 운영) │
│ - 최소 사양: t3.small │
│ - 월 비용: ~$15 │
│ - 실제 사용률: 5% │
│ - 95% 시간 동안 유휴 상태 │
└─────────────────────────────────────┘
문제점:
- 고정 비용: 사용하지 않아도 서버 비용 발생
- 관리 부담: OS 패치, 보안 업데이트, 모니터링 필요
- 확장성 제한: 트래픽 급증 시 수동으로 인스턴스 추가 필요
- 리소스 낭비: 트래픽이 적을 때도 서버는 계속 실행됨
- 복잡한 배포: 서버 설정, 로드 밸런서 구성 등 필요
Lambda의 해결 방법
1
2
3
4
5
6
7
┌─────────────────────────────────────┐
│ Lambda 함수 │
│ - 실행 시에만 과금 │
│ - 월 비용: ~$0.20 (동일 부하 가정)│
│ - 자동 스케일링 │
│ - 서버 관리 불필요 │
└─────────────────────────────────────┘
장점:
- 종량제 과금: 실제 사용한 시간만큼만 비용 지불 (100ms 단위)
- 제로 관리: 서버 관리, 패치, 확장 모두 AWS가 담당
- 자동 스케일링: 요청 수에 따라 자동으로 확장/축소
- 고가용성: 여러 가용 영역에 자동으로 분산
- 빠른 배포: 코드만 업로드하면 즉시 실행 가능
Lambda의 핵심 개념
1. 함수 (Function)
Lambda의 기본 실행 단위임. 특정 이벤트에 응답하여 실행되는 코드 조각.
기본 구조 (Node.js 예시):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export const handler = async (event, context) => {
// event: 함수를 트리거한 이벤트 데이터
// context: 런타임 정보 (requestId, 남은 시간 등)
console.log('Received event:', JSON.stringify(event, null, 2));
// 비즈니스 로직 수행
const result = await processData(event);
// 응답 반환
return {
statusCode: 200,
body: JSON.stringify(result)
};
};
2. 트리거 (Trigger)
Lambda 함수를 실행시키는 이벤트 소스.
주요 트리거 유형:
| 트리거 유형 | 사용 사례 | 실전 예시 |
|---|---|---|
| API Gateway | HTTP 요청 처리 | REST API, 웹 애플리케이션 백엔드 |
| S3 | 파일 업로드/삭제 | 이미지 리사이징, CSV 처리 |
| DynamoDB Streams | 데이터 변경 감지 | 실시간 데이터 동기화 |
| SQS | 메시지 큐 처리 | 비동기 작업 처리 |
| Kinesis | 스트리밍 데이터 | IoT 센서 데이터 처리 |
| EventBridge | 스케줄/이벤트 | 주기적 작업, 이벤트 라우팅 |
| CloudWatch Logs | 로그 분석 | 실시간 로그 모니터링 |
3. 실행 환경 (Execution Environment)
Lambda 함수가 실행되는 격리된 환경.
콜드 스타트 vs 웜 스타트:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
콜드 스타트 (Cold Start):
┌────────────────────────────────────┐
│ 1. 실행 환경 생성 (100-500ms) │
│ 2. 런타임 초기화 (50-200ms) │
│ 3. 코드 다운로드 (10-100ms) │
│ 4. 핸들러 초기화 (10-500ms) │
│ 5. 함수 실행 (사용자 코드) │
└────────────────────────────────────┘
총 시간: 200ms - 1300ms + 실행 시간
웜 스타트 (Warm Start):
┌────────────────────────────────────┐
│ 1. 함수 실행 (사용자 코드) │
└────────────────────────────────────┘
총 시간: 실행 시간만
콜드 스타트 최적화 방법:
- 최소 종속성: 필요한 라이브러리만 포함
- 코드 크기 최소화: 불필요한 파일 제거
- Provisioned Concurrency: 미리 웜 인스턴스 유지 (비용 증가)
- 전역 변수 활용: 연결 객체는 핸들러 밖에서 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ 나쁜 예 - 매번 새로운 클라이언트 생성
export const handler = async (event) => {
const dynamoClient = new DynamoDBClient(); // 콜드/웜 모두 매번 실행
// ...
};
// ✅ 좋은 예 - 재사용 가능한 클라이언트
const dynamoClient = new DynamoDBClient(); // 한 번만 실행 (재사용)
export const handler = async (event) => {
// 클라이언트 재사용
await dynamoClient.send(command);
};
실전 프로젝트 활용 사례
사례 1: 이미지 자동 처리 파이프라인
아키텍처:
1
2
3
4
5
사용자 → S3 Upload → EventBridge → Lambda (Image Processor)
├─ Thumbnail 생성
├─ Resized 이미지 생성
├─ 메타데이터 추출
└─ DynamoDB 저장
Lambda 함수 코드 (핵심 부분):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp';
const s3Client = new S3Client({});
export const handler = async (event) => {
// EventBridge에서 S3 업로드 이벤트 수신
const bucket = event.detail.bucket.name;
const key = event.detail.object.key;
// 원본 이미지 다운로드
const getCommand = new GetObjectCommand({ Bucket: bucket, Key: key });
const { Body } = await s3Client.send(getCommand);
const imageBuffer = await streamToBuffer(Body);
// Sharp로 썸네일 생성 (200x200)
const thumbnail = await sharp(imageBuffer)
.resize(200, 200, { fit: 'cover' })
.jpeg({ quality: 80 })
.toBuffer();
// S3에 저장
const putCommand = new PutObjectCommand({
Bucket: bucket,
Key: `thumbnails/${key}`,
Body: thumbnail,
ContentType: 'image/jpeg'
});
await s3Client.send(putCommand);
return { message: 'Image processed successfully' };
};
장점:
- S3 업로드와 동시에 자동으로 처리
- 이미지 수에 관계없이 자동 확장
- 처리 시간만큼만 과금 (약 1-2초/이미지)
사례 2: 비동기 주문 처리 시스템
아키텍처:
1
2
3
4
5
6
7
API Gateway → Lambda (Order Creator)
→ SQS Queue
→ Lambda (Order Worker) [배치 10개]
├─ 재고 확인
├─ 결제 처리
├─ DynamoDB 저장
└─ SNS 알림
Order Worker Lambda:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
export const handler = async (event) => {
const records = event.Records; // SQS 메시지 배치 (최대 10개)
const results = { successful: [], failed: [] };
for (const record of records) {
const order = JSON.parse(record.body);
try {
// 1. 재고 확인 (10% 실패율 시뮬레이션)
await checkInventory(order.items);
// 2. 결제 처리 (5% 실패율 시뮬레이션)
const payment = await processPayment(order);
// 3. 주문 저장
await saveOrder(order, payment);
// 4. 알림 전송
await sendNotification(order);
// 성공 시 SQS 메시지 삭제
await sqsClient.send(new DeleteMessageCommand({
QueueUrl: QUEUE_URL,
ReceiptHandle: record.receiptHandle
}));
results.successful.push(order.orderId);
} catch (error) {
console.error(`Order ${order.orderId} failed:`, error);
results.failed.push(order.orderId);
// 메시지를 삭제하지 않으면 자동으로 재시도됨
}
}
console.log(`Processed: ${results.successful.length} success, ${results.failed.length} failed`);
return results;
};
SQS 통합의 장점:
- 자동 배치 처리: 10개 메시지를 한 번에 처리 (비용 절감)
- 자동 재시도: 실패 시 메시지가 큐에 남아 재처리됨
- DLQ (Dead Letter Queue): 3번 실패 후 DLQ로 이동
- 부하 분산: 메시지 수에 따라 Lambda 인스턴스 자동 증가
사례 3: 실시간 IoT 센서 데이터 처리
아키텍처:
1
2
3
4
5
IoT Device → Kinesis Data Stream → Lambda (Batch 100개)
├─ DynamoDB (Raw Data)
├─ DynamoDB (1분 집계)
├─ CloudWatch Metrics
└─ SNS 알림 (임계값 초과 시)
센서 데이터 처리 Lambda:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
export const handler = async (event) => {
const records = event.Records; // Kinesis 레코드 배치 (최대 100개)
for (const record of records) {
// Base64 디코딩
const payload = Buffer.from(record.kinesis.data, 'base64').toString();
const sensorData = JSON.parse(payload);
// 1. 원시 데이터 저장
await saveSensorData(sensorData);
// 2. CloudWatch 메트릭 전송
await sendCloudWatchMetrics(sensorData);
// 3. 임계값 체크
if (sensorData.temperature > 30 || sensorData.humidity > 80) {
await publishAlert(sensorData);
}
// 4. 1분 단위 집계 업데이트
await updateAggregation(sensorData);
}
console.log(`Processed ${records.length} sensor readings`);
};
// 1분 단위 집계 (원자적 업데이트)
async function updateAggregation(data) {
const period = normalizeToMinute(data.timestamp);
await dynamoClient.send(new UpdateCommand({
TableName: AGGREGATION_TABLE,
Key: { deviceId: data.deviceId, aggregationPeriod: period },
UpdateExpression: `
SET #count = if_not_exists(#count, :zero) + :one,
sumTemperature = if_not_exists(sumTemperature, :zero) + :temp,
minTemperature = if_not_exists(minTemperature, :temp),
maxTemperature = if_not_exists(maxTemperature, :temp)
`,
ExpressionAttributeNames: { '#count': 'count' },
ExpressionAttributeValues: {
':zero': 0,
':one': 1,
':temp': data.temperature
}
}));
}
Kinesis 통합의 장점:
- 순서 보장: 동일 디바이스의 데이터는 순서대로 처리
- 고속 처리: 100개 레코드를 한 번에 배치 처리
- 내결함성: 샤드 장애 시 자동 재시도
- 병렬 처리: 샤드별로 병렬 Lambda 실행
Lambda 구성 옵션
1. 메모리 및 성능
1
2
3
4
5
functions:
myFunction:
handler: index.handler
memorySize: 512 # 128MB - 10,240MB (1MB 단위)
timeout: 30 # 최대 900초 (15분)
메모리와 CPU의 관계:
- 메모리가 증가하면 CPU도 비례해서 증가
- 1,769MB = 1 vCPU
- 10,240MB = 약 6 vCPU
메모리 최적화 예시:
1
2
3
4
테스트 결과:
- 128MB: 실행 시간 5000ms, 비용 $0.000083
- 512MB: 실행 시간 1500ms, 비용 $0.000025 ✅ (최적)
- 1024MB: 실행 시간 800ms, 비용 $0.000027
512MB가 가장 저렴한 이유: 빠른 실행으로 총 컴퓨팅 시간 감소
2. 환경 변수
1
2
3
4
5
6
7
functions:
myFunction:
environment:
TABLE_NAME: ${self:custom.tableName}
QUEUE_URL: !Ref MyQueue
API_KEY: ${env:SECRET_API_KEY} # .env 파일에서
STAGE: ${self:provider.stage}
보안 주의사항:
- 민감한 정보는 AWS Secrets Manager 또는 Parameter Store 사용
- Lambda 콘솔에서 환경 변수가 노출되므로 비밀번호는 직접 입력 금지
3. IAM 권한 (최소 권한 원칙)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
provider:
iam:
role:
statements:
# ✅ 좋은 예: 특정 리소스만 접근
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
Resource: !GetAtt MyTable.Arn
# ❌ 나쁜 예: 와일드카드 사용
- Effect: Allow
Action:
- dynamodb:*
Resource: "*"
4. VPC 구성 (선택사항)
VPC Lambda 사용 시나리오:
- RDS 데이터베이스 접근
- ElastiCache 사용
- 프라이빗 서브넷의 리소스 접근
1
2
3
4
5
6
7
8
functions:
myFunction:
vpc:
securityGroupIds:
- sg-0abc123def456
subnetIds:
- subnet-0abc123
- subnet-0def456
VPC Lambda의 단점:
- 콜드 스타트 증가 (ENI 생성 시간)
- NAT Gateway 비용 발생 (인터넷 접근 시)
해결책:
- VPC Endpoints 사용 (S3, DynamoDB 등)
- Hyperplane ENI (2019년 이후 콜드 스타트 개선)
Lambda vs 전통적 서버 비교
시나리오: 주문 처리 API
EC2 기반 아키텍처:
1
2
3
4
5
6
7
8
9
10
11
12
비용 분석 (월 평균 10만 요청):
- EC2 t3.small (2대, 고가용성): $30/월
- ALB (로드 밸런서): $22/월
- EBS 스토리지: $10/월
- CloudWatch 모니터링: $3/월
총 비용: $65/월
추가 고려사항:
- 서버 관리 시간: 월 4시간
- 보안 패치 및 업데이트 필요
- 오토 스케일링 설정 복잡
- 트래픽 급증 시 대응 지연
Lambda 기반 아키텍처:
1
2
3
4
5
6
7
8
9
10
11
비용 분석 (월 평균 10만 요청, 평균 500ms 실행):
- Lambda 요청 비용: $0.02
- Lambda 컴퓨팅 비용: $0.83
- API Gateway: $0.35
총 비용: $1.20/월
추가 고려사항:
- 서버 관리 불필요
- 자동 스케일링
- 트래픽 급증 즉시 대응
- 고가용성 기본 제공
비용 절감: 98% ($65 → $1.20)
Lambda 제약사항 및 해결 방법
제약사항
| 제약 | 한계 | 해결 방법 |
|---|---|---|
| 실행 시간 | 최대 15분 | Step Functions로 장기 워크플로우 오케스트레이션 |
| 메모리 | 최대 10GB | 큰 파일은 S3에서 스트리밍 처리 |
| 배포 패키지 | 50MB (압축), 250MB (압축 해제) | Lambda Layers 사용, 종속성 최소화 |
| /tmp 디스크 | 최대 10GB | 임시 파일은 S3 사용 |
| 동시 실행 | 계정당 1,000개 (기본) | 한도 증가 요청 또는 Reserved Concurrency |
실행 시간 제약 해결: Step Functions 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 15분 이상 걸리는 CSV 처리
stepFunctions:
stateMachines:
csvProcessor:
definition:
StartAt: ValidateCSV
States:
ValidateCSV:
Type: Task
Resource: !GetAtt ValidatorLambda.Arn
Next: TransformData
Retry:
- ErrorEquals: [States.Timeout]
MaxAttempts: 2
TransformData:
Type: Task
Resource: !GetAtt TransformerLambda.Arn
Next: LoadData
LoadData:
Type: Task
Resource: !GetAtt LoaderLambda.Arn
End: true
각 단계는 15분 내에 완료되지만, 전체 워크플로우는 1시간 이상 걸릴 수 있음.
Lambda 모니터링 및 디버깅
CloudWatch Logs
Lambda는 자동으로 CloudWatch Logs에 로그를 전송함.
구조화된 로깅 패턴:
1
2
3
4
5
6
7
8
9
10
11
12
// ❌ 나쁜 예
console.log('Processing order');
// ✅ 좋은 예
console.log(JSON.stringify({
level: 'INFO',
message: 'Processing order',
orderId: order.id,
customerId: order.customerId,
timestamp: new Date().toISOString(),
requestId: context.requestId
}));
CloudWatch Logs Insights 쿼리:
1
2
3
4
fields @timestamp, level, message, orderId, customerId
| filter level = "ERROR"
| sort @timestamp desc
| limit 100
CloudWatch Metrics
커스텀 메트릭 전송:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { CloudWatchClient, PutMetricDataCommand } from '@aws-sdk/client-cloudwatch';
const cwClient = new CloudWatchClient({});
async function publishMetric(metricName, value, dimensions) {
await cwClient.send(new PutMetricDataCommand({
Namespace: 'MyApplication',
MetricData: [{
MetricName: metricName,
Value: value,
Unit: 'Count',
Timestamp: new Date(),
Dimensions: dimensions
}]
}));
}
// 사용 예시
await publishMetric('OrderProcessed', 1, [
{ Name: 'Status', Value: 'Success' },
{ Name: 'Region', Value: 'ap-northeast-2' }
]);
X-Ray를 통한 분산 추적
1
2
3
4
5
6
7
provider:
tracing:
lambda: true # X-Ray 추적 활성화
functions:
myFunction:
handler: index.handler
X-Ray SDK 사용:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import AWSXRay from 'aws-xray-sdk-core';
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
export const handler = async (event) => {
// 서브세그먼트로 세분화된 추적
const segment = AWSXRay.getSegment();
const subsegment = segment.addNewSubsegment('DatabaseQuery');
try {
const result = await queryDatabase();
subsegment.close();
return result;
} catch (error) {
subsegment.addError(error);
subsegment.close();
throw error;
}
};
Lambda 비용 최적화 전략
1. Graviton2 프로세서 사용 (ARM64)
1
2
3
4
functions:
myFunction:
handler: index.handler
architecture: arm64 # 20% 비용 절감
x86_64 대비 20% 저렴하고 성능도 유사하거나 더 좋음.
2. 메모리 크기 최적화
AWS Lambda Power Tuning 도구 사용:
1
npm install -g aws-lambda-power-tuning
자동으로 여러 메모리 크기를 테스트하여 최적의 구성 찾아줌.
3. 불필요한 종속성 제거
1
2
3
4
5
// ❌ 전체 AWS SDK 임포트 (100MB+)
const AWS = require('aws-sdk');
// ✅ 필요한 클라이언트만 임포트 (5MB)
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
4. Reserved Concurrency 활용
예측 가능한 트래픽이 있다면:
1
2
3
functions:
myFunction:
reservedConcurrency: 10 # 항상 10개 인스턴스 예약
콜드 스타트 감소, 하지만 비용 증가 (idle 시간에도 예약됨).
Lambda 배포 전략
1. 버전 및 별칭
1
2
3
4
5
6
7
8
functions:
myFunction:
handler: index.handler
# 배포 시 자동으로 버전 생성
deploy:
function:
versioning: true
별칭을 통한 트래픽 라우팅:
1
2
3
4
5
6
7
8
# 새 버전 배포
aws lambda publish-version --function-name myFunction
# 별칭 업데이트 (50% 트래픽을 새 버전으로)
aws lambda update-alias \
--function-name myFunction \
--name production \
--routing-config AdditionalVersionWeights={"2"=0.5}
2. 카나리 배포
1
2
3
4
5
6
7
functions:
myFunction:
handler: index.handler
deploymentSettings:
type: Canary10Percent5Minutes
alarms:
- FunctionErrorAlarm
10%씩 트래픽을 증가시키며 5분마다 검증.
실전 팁 및 Best Practices
1. 재사용 가능한 연결 객체
1
2
3
4
5
6
7
// SDK 클라이언트는 핸들러 외부에서 생성
const dynamoClient = new DynamoDBClient({});
const s3Client = new S3Client({});
export const handler = async (event) => {
// 재사용
};
2. 환경별 구성 관리
1
2
3
4
5
6
7
8
9
10
11
12
13
custom:
stages:
dev:
memorySize: 512
timeout: 30
prod:
memorySize: 1024
timeout: 60
functions:
myFunction:
memorySize: ${self:custom.stages.${self:provider.stage}.memorySize}
timeout: ${self:custom.stages.${self:provider.stage}.timeout}
3. 에러 핸들링 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export const handler = async (event) => {
try {
const result = await processEvent(event);
return {
statusCode: 200,
body: JSON.stringify(result)
};
} catch (error) {
console.error('Error processing event:', {
error: error.message,
stack: error.stack,
event: JSON.stringify(event)
});
// 재시도 가능한 에러는 throw (SQS, Kinesis 등)
if (error.retryable) {
throw error;
}
// 재시도 불가능한 에러는 로그만 남기고 성공 반환
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal Server Error' })
};
}
};
4. 동시성 제어
1
2
3
4
functions:
myFunction:
reservedConcurrency: 5 # 최대 5개 동시 실행
# 외부 API 레이트 리밋 준수
5. 데이터베이스 연결 관리
RDS 같은 연결 기반 DB 사용 시:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import mysql from 'mysql2/promise';
let connection;
async function getConnection() {
if (!connection) {
connection = await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
connectionLimit: 1 # Lambda는 단일 연결로 충분
});
}
return connection;
}
export const handler = async (event) => {
const conn = await getConnection();
const [rows] = await conn.execute('SELECT * FROM users WHERE id = ?', [event.userId]);
return rows;
};
또는 RDS Proxy 사용 (권장):
- 연결 풀링 자동 관리
- Lambda 동시성에 따른 연결 폭증 방지
마치며
AWS Lambda는 서버리스 컴퓨팅의 핵심 서비스로, 다음과 같은 상황에서 특히 유용함:
Lambda를 사용해야 할 때:
- 이벤트 기반 처리 (파일 업로드, 메시지 큐 등)
- 간헐적으로 실행되는 작업
- 트래픽 변동이 큰 애플리케이션
- 빠른 프로토타이핑 및 개발
- 마이크로서비스 아키텍처
Lambda 대신 다른 방식을 고려해야 할 때:
- 15분 이상 실행되는 작업 (→ ECS, Fargate)
- 지속적으로 높은 트래픽 (→ EC2 Auto Scaling이 더 저렴할 수 있음)
- 복잡한 상태 관리 필요 (→ Kubernetes)
- WebSocket 장시간 연결 (→ EC2, ECS)
실전 프로젝트에서 Lambda는 S3, DynamoDB, SQS, SNS 등 다른 AWS 서비스와 조합하여 강력한 이벤트 기반 아키텍처를 구축할 수 있음.
적절한 메모리 크기, 타임아웃, 배치 크기 조정을 통해 비용과 성능을 최적화할 수 있으며, CloudWatch와 X-Ray를 통한 모니터링으로 안정적인 운영이 가능함.