Amazon EventBridge - 서버리스 이벤트 버스
EventBridge로 느슨하게 결합된 이벤트 기반 아키텍처 구축하기
Amazon EventBridge란?
Amazon EventBridge는 서버리스 이벤트 버스 서비스로, AWS 서비스, 사용자 정의 애플리케이션, SaaS 애플리케이션 간에 이벤트를 쉽게 연결할 수 있음.
이벤트 기반 아키텍처(Event-Driven Architecture)를 구축하는 핵심 서비스로, 이벤트 생산자와 소비자를 느슨하게 결합시킴.
왜 EventBridge를 사용해야 하는가?
기존 방식의 문제점
전통적인 직접 통합 방식:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────┐
│ 이미지 업로드 Lambda │
│ ├─ S3에 이미지 저장 │
│ ├─ DynamoDB에 메타데이터 저장 │
│ ├─ SNS로 알림 전송 │
│ ├─ SQS로 썸네일 작업 큐잉 │
│ └─ CloudWatch 메트릭 기록 │
│ │
│ 문제점: │
│ - 하나의 Lambda가 모든 작업 처리 │
│ - 강한 결합 (하나 실패 시 전체 실패) │
│ - 새 기능 추가 시 코드 수정 필요 │
│ - 재시도 로직 복잡 │
└─────────────────────────────────────────┘
Lambda 실행 시간: 5초
에러율: 높음 (하나라도 실패 시 롤백 필요)
유지보수: 어려움
S3 Event Notification의 한계:
1
2
3
4
5
6
7
8
9
S3 Bucket
└─ Event Notification
└─ Lambda 또는 SNS 또는 SQS (하나만 선택 가능)
문제점:
1. 하나의 이벤트를 여러 대상으로 보낼 수 없음
2. 이벤트 필터링 제한적
3. 동적 라우팅 불가능
4. 재시도 정책 제한적
EventBridge의 해결 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────┐
│ S3 Bucket (이미지 업로드) │
│ ↓ │
│ EventBridge (이벤트 버스) │
│ ↓ │
│ ┌──────┴──────┬──────┬───────────┐ │
│ ↓ ↓ ↓ ↓ │
│ Lambda1 Lambda2 Lambda3 Lambda4 │
│ (썸네일) (메타데이터) (알림) (분석) │
│ │
│ 장점: │
│ - 느슨한 결합 (독립적 실행) │
│ - 병렬 처리 (동시 실행) │
│ - 새 기능 추가 쉬움 (룰만 추가) │
│ - 자동 재시도 + DLQ │
│ - 이벤트 아카이빙 + 리플레이 │
└─────────────────────────────────────────┘
Lambda 실행 시간: 1초 (병렬 실행)
에러율: 낮음 (독립적 처리)
유지보수: 쉬움
장점:
- 느슨한 결합: 이벤트 생산자는 소비자를 알 필요 없음
- 확장 용이: 새 소비자 추가 시 기존 코드 수정 불필요
- 병렬 처리: 여러 타겟에 동시 이벤트 전달
- 중앙 집중식 이벤트 관리: 모든 이벤트를 한 곳에서 관리
- 이벤트 패턴 매칭: 복잡한 필터링 규칙 지원
EventBridge 핵심 개념
1. Event Bus
이벤트가 라우팅되는 파이프라인. 세 가지 타입:
- Default Event Bus: AWS 서비스 이벤트 (EC2, S3 등)
- Custom Event Bus: 애플리케이션 이벤트
- Partner Event Bus: SaaS 파트너 이벤트 (Datadog, Zendesk 등)
2. Event
JSON 형식의 구조화된 데이터:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"version": "0",
"id": "c7eef9bc-9b6f-4b9e-9b0f-0a8b8e8f8b8f",
"detail-type": "ImageUploaded",
"source": "custom.imageApp",
"account": "123456789012",
"time": "2024-12-02T10:30:00Z",
"region": "ap-northeast-2",
"resources": [],
"detail": {
"bucket": "my-images-bucket",
"key": "uploads/image123.jpg",
"size": 1024000,
"contentType": "image/jpeg",
"userId": "user-456"
}
}
3. Rule
이벤트를 필터링하고 타겟으로 라우팅:
1
2
3
4
5
6
7
8
{
"source": ["custom.imageApp"],
"detail-type": ["ImageUploaded"],
"detail": {
"contentType": ["image/jpeg", "image/png"],
"size": [{"numeric": ["<", 10485760]}] // 10MB 미만
}
}
4. Target
이벤트를 받는 AWS 서비스:
- Lambda, Step Functions, SQS, SNS, Kinesis
- ECS Task, Batch Job, SSM Run Command
- API Destination (HTTP 엔드포인트)
실전 프로젝트 사례
1. 이미지 처리 파이프라인 (aws-image-pipeline)
아키텍처:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
S3 Bucket (이미지 업로드)
│
├─ EventBridge Notification 활성화
│
↓
EventBridge (default event bus)
│
├─ Rule: "ProcessImageRule"
│ └─ Event Pattern: 이미지 파일만 (.jpg, .png, .gif)
│ └─ Target: Lambda (image-processor)
│
↓
Lambda Functions (병렬 실행)
├─ 썸네일 생성 → S3 thumbnails/
├─ 이미지 리사이징 → S3 resized/
└─ 메타데이터 추출 → DynamoDB
Serverless Framework 설정:
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# serverless.yml
provider:
name: aws
runtime: nodejs20.x
resources:
Resources:
# S3 Bucket - EventBridge 알림 활성화
ImageBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:service}-images-${sls:stage}
NotificationConfiguration:
EventBridgeConfiguration:
EventBridgeEnabled: true # EventBridge 활성화
# EventBridge Rule
ProcessImageRule:
Type: AWS::Events::Rule
Properties:
Name: ${self:service}-process-image-rule
Description: Process uploaded images
EventBusName: default
EventPattern:
source:
- aws.s3
detail-type:
- Object Created
detail:
bucket:
name:
- !Ref ImageBucket
object:
key:
- prefix: "uploads/"
- suffix: ".jpg"
- suffix: ".png"
- suffix: ".gif"
State: ENABLED
Targets:
- Arn: !GetAtt ImageProcessorLambdaFunction.Arn
Id: ImageProcessorTarget
RetryPolicy:
MaximumRetryAttempts: 2
MaximumEventAge: 3600
DeadLetterConfig:
Arn: !GetAtt ProcessImageDLQ.Arn
# Lambda에 EventBridge 호출 권한 부여
ImageProcessorEventPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref ImageProcessorLambdaFunction
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt ProcessImageRule.Arn
# Dead Letter Queue
ProcessImageDLQ:
Type: AWS::SQS::Queue
Properties:
QueueName: ${self:service}-process-image-dlq
MessageRetentionPeriod: 1209600 # 14일
functions:
imageProcessor:
handler: functions/process-image.handler
environment:
BUCKET_NAME: !Ref ImageBucket
iamRoleStatements:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource: !Sub "${ImageBucket.Arn}/*"
Lambda 핸들러 (process-image.handler):
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp';
const s3Client = new S3Client();
const BUCKET_NAME = process.env.BUCKET_NAME;
export const handler = async (event) => {
console.log('EventBridge Event:', JSON.stringify(event, null, 2));
// EventBridge에서 전달된 S3 이벤트 파싱
const bucket = event.detail.bucket.name;
const key = event.detail.object.key;
console.log(`Processing: s3://${bucket}/${key}`);
try {
// S3에서 원본 이미지 다운로드
const getResponse = await s3Client.send(new GetObjectCommand({
Bucket: bucket,
Key: key
}));
const imageBuffer = await streamToBuffer(getResponse.Body);
// 썸네일 생성 (200x200)
const thumbnail = await sharp(imageBuffer)
.resize(200, 200, { fit: 'cover' })
.jpeg({ quality: 80 })
.toBuffer();
// 썸네일 저장
const thumbnailKey = key.replace('uploads/', 'thumbnails/');
await s3Client.send(new PutObjectCommand({
Bucket: bucket,
Key: thumbnailKey,
Body: thumbnail,
ContentType: 'image/jpeg'
}));
// 리사이즈 이미지 생성 (800x600)
const resized = await sharp(imageBuffer)
.resize(800, 600, { fit: 'inside' })
.jpeg({ quality: 90 })
.toBuffer();
const resizedKey = key.replace('uploads/', 'resized/');
await s3Client.send(new PutObjectCommand({
Bucket: bucket,
Key: resizedKey,
Body: resized,
ContentType: 'image/jpeg'
}));
console.log('Image processing completed:', {
original: key,
thumbnail: thumbnailKey,
resized: resizedKey
});
return {
statusCode: 200,
body: JSON.stringify({ message: 'Success' })
};
} catch (error) {
console.error('Error processing image:', error);
throw error; // EventBridge가 재시도
}
};
async function streamToBuffer(stream) {
const chunks = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
S3 → EventBridge 이벤트 구조:
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
{
"version": "0",
"id": "17793124-05d4-b198-2fde-7ededc63b103",
"detail-type": "Object Created",
"source": "aws.s3",
"account": "123456789012",
"time": "2024-12-02T12:34:56Z",
"region": "ap-northeast-2",
"resources": [
"arn:aws:s3:::my-images-bucket"
],
"detail": {
"version": "0",
"bucket": {
"name": "my-images-bucket"
},
"object": {
"key": "uploads/image123.jpg",
"size": 1024000,
"etag": "d41d8cd98f00b204e9800998ecf8427e",
"sequencer": "0A1B2C3D4E5F678901"
},
"request-id": "D82B88E5F771DA89",
"requester": "123456789012",
"source-ip-address": "1.2.3.4",
"reason": "PutObject"
}
}
2. 커스텀 이벤트 발행 (주문 시스템)
시나리오: 주문 생성 시 여러 서비스에 이벤트 전파:
- 재고 관리 서비스
- 결제 처리 서비스
- 알림 서비스
- 분석 서비스
아키텍처:
1
2
3
4
5
6
7
8
9
10
11
12
13
Lambda (주문 생성)
│
├─ DynamoDB에 주문 저장
│
├─ EventBridge로 커스텀 이벤트 발행
│
↓
EventBridge (custom event bus)
│
├─ Rule 1: 재고 확인 → Lambda (inventory-check)
├─ Rule 2: 결제 처리 → Step Functions
├─ Rule 3: 알림 전송 → SNS
└─ Rule 4: 분석 저장 → Kinesis Firehose
주문 생성 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
48
49
50
51
52
53
54
import { EventBridgeClient, PutEventsCommand } from '@aws-sdk/client-eventbridge';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb';
const eventBridgeClient = new EventBridgeClient();
const dynamoClient = DynamoDBDocumentClient.from(new DynamoDBClient());
const EVENT_BUS_NAME = process.env.EVENT_BUS_NAME;
const TABLE_NAME = process.env.TABLE_NAME;
export const handler = async (event) => {
const orderData = JSON.parse(event.body);
const order = {
orderId: `order-${Date.now()}`,
userId: orderData.userId,
items: orderData.items,
totalAmount: orderData.totalAmount,
status: 'pending',
createdAt: new Date().toISOString()
};
// 1. DynamoDB에 주문 저장
await dynamoClient.send(new PutCommand({
TableName: TABLE_NAME,
Item: order
}));
// 2. EventBridge로 커스텀 이벤트 발행
await eventBridgeClient.send(new PutEventsCommand({
Entries: [{
EventBusName: EVENT_BUS_NAME,
Source: 'custom.orderSystem',
DetailType: 'OrderCreated',
Detail: JSON.stringify({
orderId: order.orderId,
userId: order.userId,
items: order.items,
totalAmount: order.totalAmount,
timestamp: order.createdAt
})
}]
}));
console.log('Order created and event published:', order.orderId);
return {
statusCode: 201,
body: JSON.stringify({
message: 'Order created',
orderId: order.orderId
})
};
};
Serverless Framework 설정:
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# serverless.yml
resources:
Resources:
# Custom Event Bus
OrderEventBus:
Type: AWS::Events::EventBus
Properties:
Name: order-event-bus-${sls:stage}
# Rule 1: 재고 확인
InventoryCheckRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref OrderEventBus
EventPattern:
source:
- custom.orderSystem
detail-type:
- OrderCreated
Targets:
- Arn: !GetAtt InventoryCheckLambdaFunction.Arn
Id: InventoryCheckTarget
# Rule 2: 결제 처리 (고액 주문만)
PaymentProcessingRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref OrderEventBus
EventPattern:
source:
- custom.orderSystem
detail-type:
- OrderCreated
detail:
totalAmount:
- numeric: [">=", 100000] # 10만원 이상
Targets:
- Arn: !Ref PaymentStateMachine
Id: PaymentTarget
RoleArn: !GetAtt EventBridgeStepFunctionsRole.Arn
# Rule 3: 알림 전송
NotificationRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref OrderEventBus
EventPattern:
source:
- custom.orderSystem
detail-type:
- OrderCreated
Targets:
- Arn: !Ref NotificationTopic
Id: NotificationTarget
# Rule 4: 분석 데이터 저장
AnalyticsRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref OrderEventBus
EventPattern:
source:
- custom.orderSystem
detail-type:
- OrderCreated
- OrderCompleted
- OrderCancelled
Targets:
- Arn: !GetAtt AnalyticsStream.Arn
Id: AnalyticsTarget
RoleArn: !GetAtt EventBridgeKinesisRole.Arn
functions:
orderCreator:
handler: functions/create-order.handler
environment:
EVENT_BUS_NAME: !Ref OrderEventBus
TABLE_NAME: !Ref OrdersTable
iamRoleStatements:
- Effect: Allow
Action:
- events:PutEvents
Resource: !GetAtt OrderEventBus.Arn
재고 확인 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
export const handler = async (event) => {
console.log('Inventory Check Event:', JSON.stringify(event, null, 2));
const { orderId, items } = event.detail;
// 재고 확인 로직
for (const item of items) {
const stock = await checkStock(item.productId);
if (stock < item.quantity) {
console.error(`Insufficient stock for ${item.productId}`);
// 재고 부족 이벤트 발행
await eventBridgeClient.send(new PutEventsCommand({
Entries: [{
EventBusName: EVENT_BUS_NAME,
Source: 'custom.inventorySystem',
DetailType: 'InsufficientStock',
Detail: JSON.stringify({
orderId,
productId: item.productId,
requested: item.quantity,
available: stock
})
}]
}));
return;
}
}
// 재고 확보 성공 이벤트
await eventBridgeClient.send(new PutEventsCommand({
Entries: [{
EventBusName: EVENT_BUS_NAME,
Source: 'custom.inventorySystem',
DetailType: 'StockReserved',
Detail: JSON.stringify({ orderId, items })
}]
}));
console.log('Stock reserved for order:', orderId);
};
EventBridge 고급 기능
1. Event Pattern 매칭
기본 매칭:
1
2
3
4
{
"source": ["custom.orderSystem"],
"detail-type": ["OrderCreated"]
}
복잡한 매칭 (Numeric, Prefix, Suffix):
1
2
3
4
5
6
7
8
9
10
11
{
"source": ["custom.orderSystem"],
"detail-type": ["OrderCreated"],
"detail": {
"totalAmount": [{"numeric": [">=", 100000, "<=", 1000000]}],
"userId": [{"prefix": "premium-"}],
"items": {
"productId": [{"suffix": "-electronics"}]
}
}
}
Anything-but (제외 패턴):
1
2
3
4
5
{
"detail": {
"status": [{"anything-but": ["cancelled", "refunded"]}]
}
}
Exists (필드 존재 여부):
1
2
3
4
5
{
"detail": {
"couponCode": [{"exists": true}]
}
}
2. Input Transformation
이벤트를 변환하여 타겟에 전달:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Targets:
- Arn: !GetAtt ProcessOrderLambdaFunction.Arn
Id: ProcessOrderTarget
InputTransformer:
InputPathsMap:
orderId: "$.detail.orderId"
amount: "$.detail.totalAmount"
user: "$.detail.userId"
InputTemplate: |
{
"order": {
"id": "<orderId>",
"amount": <amount>,
"userId": "<user>",
"processedAt": "$.time"
}
}
3. Event Archive & Replay
이벤트 아카이브 (저장):
1
2
3
4
5
6
7
8
9
OrderEventArchive:
Type: AWS::Events::Archive
Properties:
ArchiveName: order-events-archive
SourceArn: !GetAtt OrderEventBus.Arn
RetentionDays: 365
EventPattern:
source:
- custom.orderSystem
이벤트 리플레이 (재처리):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { EventBridgeClient, StartReplayCommand } from '@aws-sdk/client-eventbridge';
const eventBridgeClient = new EventBridgeClient();
// 특정 기간 이벤트 재처리
await eventBridgeClient.send(new StartReplayCommand({
ReplayName: 'order-replay-20241202',
EventSourceArn: 'arn:aws:events:ap-northeast-2:123456789012:archive/order-events-archive',
EventStartTime: new Date('2024-12-01T00:00:00Z'),
EventEndTime: new Date('2024-12-01T23:59:59Z'),
Destination: {
Arn: 'arn:aws:events:ap-northeast-2:123456789012:event-bus/order-event-bus-dev'
}
}));
사용 사례:
- 버그 수정 후 실패한 이벤트 재처리
- 새 기능 추가 후 과거 이벤트로 테스트
- 데이터 복구
4. API Destination (HTTP Endpoint 호출)
외부 HTTP API를 타겟으로 설정:
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
48
49
# Webhook Destination
SlackWebhook:
Type: AWS::Events::Connection
Properties:
Name: slack-webhook-connection
AuthorizationType: API_KEY
AuthParameters:
ApiKeyAuthParameters:
ApiKeyName: Authorization
ApiKeyValue: !Ref SlackWebhookToken
SlackDestination:
Type: AWS::Events::ApiDestination
Properties:
Name: slack-destination
ConnectionArn: !GetAtt SlackWebhook.Arn
InvocationEndpoint: https://hooks.slack.com/services/XXX/YYY/ZZZ
HttpMethod: POST
InvocationRateLimitPerSecond: 10
SlackNotificationRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- custom.orderSystem
detail-type:
- OrderFailed
Targets:
- Arn: !GetAtt SlackDestination.Arn
RoleArn: !GetAtt EventBridgeApiDestinationRole.Arn
HttpParameters:
HeaderParameters:
Content-Type: application/json
InputTransformer:
InputPathsMap:
orderId: "$.detail.orderId"
error: "$.detail.errorMessage"
InputTemplate: |
{
"text": "주문 실패 알림",
"blocks": [{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*주문 ID:* <orderId>\n*에러:* <error>"
}
}]
}
EventBridge vs 대안 비교
EventBridge vs SNS
| 항목 | EventBridge | SNS |
|---|---|---|
| 패턴 매칭 | 복잡한 JSON 필터링 | 간단한 속성 필터링 |
| 타겟 수 | 최대 5개/룰 | 무제한 구독자 |
| 타겟 타입 | 20+ AWS 서비스 | Lambda, SQS, HTTP, Email |
| 아카이브/리플레이 | ✓ | ✗ |
| 크로스 계정 | ✓ | ✓ |
| 가격 | $1.00/million | $0.50/million |
| 사용 사례 | 복잡한 라우팅 | 간단한 Fan-out |
선택 기준:
- EventBridge: 복잡한 이벤트 라우팅, AWS 서비스 통합, 아카이브 필요
- SNS: 간단한 pub-sub, 대량 구독자, 낮은 비용
EventBridge vs SQS
| 항목 | EventBridge | SQS |
|---|---|---|
| 패턴 | Pub-Sub + Routing | Point-to-Point Queue |
| 소비자 | 여러 타겟 동시 | 하나의 소비자 |
| 순서 보장 | ✗ | ✓ (FIFO) |
| 메시지 지연 | 밀리초 | 최대 15분 |
| 메시지 보관 | 아카이브 (선택) | 최대 14일 |
| 사용 사례 | 이벤트 라우팅 | 작업 큐 |
선택 기준:
- EventBridge: 여러 서비스에 동시 전달, 이벤트 기반 아키텍처
- SQS: 순차 처리, 작업 큐, 백프레셔 제어
실전 경험에서 배운 것
1. S3 → EventBridge가 S3 Event Notification보다 낫다
이유:
- 여러 Lambda에 동시 전달 가능
- 복잡한 필터링 (파일 크기, 확장자 조합)
- 아카이브 & 리플레이로 실패한 이벤트 재처리
- DLQ 자동 지원
2. Custom Event Bus 분리하자
1
2
3
4
5
6
7
8
9
10
11
# BAD: 모든 이벤트를 default bus에
default event bus
├─ 주문 이벤트
├─ 결제 이벤트
├─ 재고 이벤트
└─ AWS 서비스 이벤트 (섞임!)
# GOOD: 도메인별 Custom Bus 분리
order-event-bus → 주문 관련 이벤트만
payment-event-bus → 결제 관련 이벤트만
inventory-event-bus → 재고 관련 이벤트만
장점:
- 이벤트 관리 쉬움
- 권한 분리 (IAM 정책)
- 아카이브 정책 분리
3. 재시도 정책 + DLQ는 필수
1
2
3
4
5
6
7
Targets:
- Arn: !GetAtt ProcessLambda.Arn
RetryPolicy:
MaximumRetryAttempts: 2
MaximumEventAge: 3600 # 1시간
DeadLetterConfig:
Arn: !GetAtt ProcessDLQ.Arn
실패한 이벤트를 DLQ에 저장하고, 나중에 분석 & 재처리.
4. Input Transformation으로 Lambda 코드 단순화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Before: Lambda에서 이벤트 파싱
Lambda:
const orderId = event.detail.orderId;
const amount = event.detail.totalAmount;
// ...
# After: EventBridge에서 변환
InputTransformer:
InputPathsMap:
orderId: "$.detail.orderId"
amount: "$.detail.totalAmount"
InputTemplate: '{"orderId": "<orderId>", "amount": <amount>}'
Lambda:
const { orderId, amount } = event; // 바로 사용
비용 최적화
실제 프로젝트 비용 분석
이미지 처리 시스템 (월 100만 이벤트):
1
2
3
4
5
6
7
8
9
10
EventBridge:
- 커스텀 이벤트: 1,000,000 × $1.00 = $1.00
- AWS 서비스 이벤트: 무료
- 총합: $1.00/월
SNS로 구현했다면:
- SNS 발행: 1,000,000 × $0.50 = $0.50
- 총합: $0.50/월
차이: EventBridge가 $0.50 더 비쌈
하지만 EventBridge의 가치:
- 복잡한 필터링 (SNS는 간단한 속성 필터만)
- 아카이브 & 리플레이 (데이터 복구)
- 20+ AWS 서비스 통합
- 크로스 계정 이벤트
결론: 단순 Fan-out은 SNS, 복잡한 라우팅은 EventBridge
마무리
EventBridge는 이벤트 기반 아키텍처의 핵심임. 느슨한 결합, 확장성, 중앙 집중식 이벤트 관리를 제공하며, AWS 서비스와의 네이티브 통합으로 복잡한 워크플로우를 쉽게 구축할 수 있음.
사용 권장 사항:
- S3 이벤트 → EventBridge (여러 타겟 필요 시)
- 복잡한 이벤트 라우팅 → EventBridge
- 간단한 pub-sub → SNS
- 순차 처리 작업 큐 → SQS
최근 프로젝트에서 S3 Event Notification 대신 EventBridge를 사용해 여러 Lambda를 병렬로 트리거하고, 실패한 이벤트를 아카이브에서 재처리할 수 있어 매우 만족스러웠음.