누락된 연결 고리: 레거시 데이터베이스에서 AWS DMS로 Serverless 이벤트 트리거
Source: Dev.to
우리는 모든 것이 이벤트‑드리븐이 되길 원하는 세상에 살고 있습니다. SQL 데이터베이스에 새로운 사용자가 등록되면 즉시:
- SES를 통해 환영 이메일을 발송하고,
- API를 통해 CRM을 업데이트하고,
- Step Functions 워크플로우를 시작해야 합니다.
DynamoDB를 사용해 새로 구축한다면(DynamoDB Streams) 이는 쉽습니다. 하지만 데이터가 레거시 MySQL 모놀리식, 온‑프레미스 Oracle DB, 혹은 일반 PostgreSQL 인스턴스에 있다면 어떨까요?
**Change Data Capture (CDC)**가 필요합니다—변경 사항을 클라우드로 스트리밍해야 합니다.
당연히 **AWS DMS (Database Migration Service)**를 살펴보게 됩니다. 데이터 이동에는 완벽하지만, 곧 벽에 부딪히게 됩니다:
문제
AWS DMS는 AWS Lambda 함수를 직접 대상으로 지정할 수 없습니다.
단순히 “테이블 X에 행이 삽입될 때 함수 Y를 호출한다”는 작업을 구성할 수 없습니다.
그렇다면 “구시대”(SQL)와 “신시대”(서버리스) 사이의 격차를 어떻게 메울 수 있을까요? 많은 사람들이 Kinesis를 제안하지만, 가장 견고하고 비용 효율적인 답은 Amazon S3입니다.
아래는 레거시 백‑엔드를 재작성 없이 현대화하기 위해 제가 사용하는 아키텍처 패턴입니다.
아키텍처: “S3 Drop” 패턴
- Source – DMS가 레거시 데이터베이스에 연결하여 트랜잭션 로그를 통해 변경 사항(INSERT/UPDATE/DELETE)을 캡처합니다.
- Target – DMS가 해당 변경 사항을 JSON 파일로 S3 버킷에 기록합니다.
- Trigger – S3가 새로운 파일을 감지하고 이벤트 알림을 발생시킵니다.
- Compute – Lambda 함수가 이벤트를 수신하고 파일을 읽어 비즈니스 로직을 처리합니다.

왜 Kinesis 또는 Airbyte 대신 S3를 사용하나요?
왜 Kinesis Data Streams를 사용하지 않나요?
- 비용 – 레거시 DB가 조용할 때, 프로비저닝된 Kinesis 스트림보다 S3가 훨씬 저렴합니다.
- 가시성 – 버킷에 파일로 변경 사항을 직접 확인할 수 있어 디버깅이 10배 쉬워집니다.
- 배치 처리 – DMS가 S3에 배치로 기록하므로, 대규모 쓰기 급증 시 Lambda 호출이 자연스럽게 제한됩니다.
왜 Airbyte 또는 Fivetran을 사용하지 않나요?
- 해당 도구들은 ELT 파이프라인(예: 15–60 분마다 Snowflake에 데이터 로드)에 강점이 있습니다.
- 우리의 목표는 이벤트‑드리븐 처리—가능한 한 “실시간”에 가깝게 Lambda를 트리거하는 것입니다.
- AWS DMS는 연속 CDC를 제공하여 배치 기반 ELT 도구가 놓치기 쉬운 세밀한 이벤트 스트림을 전달합니다.
- 100 % AWS‑네이티브 환경을 유지하면 엄격한 기업 환경에서 IAM 거버넌스를 단순화할 수 있습니다.
Implementation Guide
DMS Endpoint Settings
DMS에서 대상 엔드포인트(S3)를 만들 때 기본값에 의존하지 마세요. 출력이 Lambda에 친화적이도록 다음 Extra Connection Attributes를 사용합니다:
dataFormat=json;
datePartitionEnabled=true;
dataFormat=json– DMS는 기본적으로 CSV를 사용합니다; JSON이 Lambda에서 파싱하기 훨씬 쉽습니다.datePartitionEnabled=true– 파일을 날짜별로 정리합니다(/2023/11/02/...). 이렇게 하면 하나의 폴더에 수백만 개의 객체가 쌓이는 것을 방지할 수 있습니다.
Understanding the Event Structure
전형적인 DMS‑생성 파일은 다음과 같습니다(라인 구분 JSON, NDJSON라고도 함):
{
"data": { "id": 101, "username": "jdoe", "status": "active" },
"metadata": { "operation": "insert", "timestamp": "2023-11-02T10:00:00Z" }
}
{
"data": { "id": 102, "username": "asmith", "status": "pending" },
"metadata": { "operation": "update", "timestamp": "2023-11-02T10:05:00Z" }
}
각 라인에는 operation(insert, update, delete)과 payload(data)가 깔끔하게 포함됩니다.
Lambda Logic
DMS가 NDJSON을 기록하기 때문에 파일 전체에 json.loads()를 한 번에 호출할 수 없습니다. 라인 단위로 반복해야 합니다.
아래는 파일을 올바르게 처리하는 Python 보일러플레이트입니다:
import boto3
import json
s3 = boto3.client('s3')
def handler(event, context):
# 1️⃣ S3 이벤트에서 버킷과 키 추출
for record in event['Records']:
bucket = record['s3']['bucket']['name']
key = record['s3']['object']['key']
print(f"Processing file: s3://{bucket}/{key}")
# 2️⃣ DMS가 생성한 파일 가져오기
obj = s3.get_object(Bucket=bucket, Key=key)
content = obj['Body'].read().decode('utf-8')
# 3️⃣ NDJSON 파싱 (라인 구분 JSON)
for line in content.splitlines():
if not line.strip():
continue # 빈 라인 건너뛰기
row = json.loads(line)
# 4️⃣ operation 타입에 따라 필터링 / 처리
operation = row.get('metadata', {}).get('operation')
if operation == 'insert':
user_data = row.get('data')
# TODO: insert에 대한 비즈니스 로직 추가
print(f"INSERT: {user_data}")
elif operation == 'update':
user_data = row.get('data')
# TODO: update에 대한 비즈니스 로직 추가
print(f"UPDATE: {user_data}")
elif operation == 'delete':
# 필요 시 delete 처리
print("DELETE operation received")
핵심 포인트
- 전체 파일에
json.loads(content)를 사용하지 말 것. content.splitlines()로 라인별로 반복하면서 각각을 파싱할 것.- 로직 라우팅을 위해
metadata.operation필드를 활용할 것.
TL;DR
- Capture CDC를 기존 레거시 RDBMS에서 AWS DMS로 캡처합니다.
- 변경 사항을 JSON 파일로 S3에 (날짜별 파티션) 저장합니다.
- S3 이벤트 알림을 통해 Lambda를 Trigger합니다.
- NDJSON 페이로드를 한 줄씩 Parse하고 이벤트 기반 비즈니스 로직을 구현합니다.
이 “S3 Drop” 패턴은 구식 데이터베이스와 최신 서버리스 워크플로우 사이에 저비용, 가시성 확보, 완전한 AWS‑네이티브 브리지를 제공합니다. 🚀
print(f"New User Detected: {user_data['username']}")
# trigger_welcome_email(user_data)
elif operation == 'update':
print(f"User Updated: {row['data']['id']}")
Summary
서버리스의 이점을 얻기 위해 전체 레거시 데이터베이스를 리팩터링할 필요는 없습니다. AWS DMS를 사용해 데이터를 추출하고 S3를 신뢰할 수 있는 버퍼로 활용하면, 20‑년 된 데이터베이스에서도 최소한의 마찰로 최신 Lambda 워크플로를 트리거할 수 있습니다. 이 패턴은 순수한 속도보다 안정성과 가시성을 우선시하며 – 기업 마이그레이션에서 보통 가치가 있는 트레이드‑오프입니다.