Post

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

항목EventBridgeSNS
패턴 매칭복잡한 JSON 필터링간단한 속성 필터링
타겟 수최대 5개/룰무제한 구독자
타겟 타입20+ AWS 서비스Lambda, SQS, HTTP, Email
아카이브/리플레이
크로스 계정
가격$1.00/million$0.50/million
사용 사례복잡한 라우팅간단한 Fan-out

선택 기준:

  • EventBridge: 복잡한 이벤트 라우팅, AWS 서비스 통합, 아카이브 필요
  • SNS: 간단한 pub-sub, 대량 구독자, 낮은 비용

EventBridge vs SQS

항목EventBridgeSQS
패턴Pub-Sub + RoutingPoint-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를 병렬로 트리거하고, 실패한 이벤트를 아카이브에서 재처리할 수 있어 매우 만족스러웠음.

This post is licensed under CC BY 4.0 by the author.