DynamoDB Streams로 서비스 간 데이터 동기화하기
들어가며
이번 글에서는 DynamoDB Streams를 사용하여 DynamoDB의 제약사항으로 해결할 수 없었던 문제를 해결한 사례를 공유하려고 한다.
필요한 사전 지식
AWS DynamoDB
AWS Lambda
Serverless Framework (Lambda 설정을 위해 필요)
NodeJS
문제 상황
우리 팀에서는 개발한 한의원 비대면진료 플랫폼은 DynamoDB를 주 데이터베이스로 사용하고 있었다. 그러나 최근에 한의원의 경영 분석 요구사항이 증가하면서, 다양한 관점에서 매출 분석이 필요해졌다. 이때 DynamoDB의 쿼리, 인덱스 제약으로 인해 요구사항을 만족시킬 수 없다는 문제가 발생했다.
새로운 요구사항
한의원의 매출을 분석하여 인사이트를 주기 위해서는 생각보다 다양한 기준에서 분석해야 했다. 초기에 나온 매출 분석 기준만해도 다음과 같다:
일별/월별/연별 매출 추이
이벤트/비이벤트 기간별 매출 추이
진료 항목별 매출 추이
환자 유입 경로별 매출 현황
DynamoDB의 구조적 한계
1. 제한된 쿼리 패턴
DynamoDB 특성상 처음 설계된 쿼리 패턴 외에는 쿼리가 불가능하다는 문제가 있다.
테이블당 하나의 파티션 키와 정렬 키만 사용 가능
미리 정의된 접근 패턴으로만 효율적인 쿼리 가능
유연한 데이터 조인이나 그룹핑에 제한
2. 분석 워크로드 부적합
매출 분석을 위한 다양한 집계 연산이 필요했지만, DynamoDB는 이러한 작업에 최적화되어 있지 않다:
복잡한 집계 쿼리 직접 수행 불가
다차원 분석을 위한 유연한 쿼리 제한
대규모 데이터 스캔 시 성능 저하
3. 확장의 어려움
분석 요구사항은 지속적으로 변화하고 증가하는데 반해, DynamoDB는 이러한 변화에 유연하게 대응하기 어렵다:
새로운 분석 관점마다 데이터 모델 수정 필요
미리 정의되지 않은 새로운 조회 패턴 수용 어려움
이러한 새로운 요구사항과 DynamoDB의 한계로 인해 단순히 인덱스를 추가하는 방식으로는 근본적인 해결이 어려웠다. 따라서 우리는 비대면진료와 매출 분석 서비스를 분리하여, 각각 특성에 맞는 데이터베이스를 사용하는 방향으로 해결책을 모색하게 되었다.
데이터 동기화 아키텍처 구현
분석 서비스로의 데이터 동기화를 위해 DynamoDB Streams와 Lambda를 활용했다. 이 방식은 진료 서비스의 코어 로직을 수정하지 않고도 데이터를 실시간으로 동기화할 수 있다는 장점과 다른 AWS 서비스와 쉽게 연동할 수 있다는 장점이 있다.
이벤트 흐름도
DynamoDB Streams 설정
AWS Console 접속
DynamoDB 테이블 선택
내보내기 및 스트림 탭 > DynamoDB 스트림 세부 정보 켜기
새 이미지와 이전 이미지 선택 후 스트림 켜기
DynamoDB Stream ARN 복사

Lambda 동기화 로직 구현
만약 DynamoDB 테이블 하나에 여러 키 구조가 혼재되어 있다면 원치 않는 데이터가 추가/변경됐을 때도 Lambda가 호출되는 일이 발생할 수 있다.
이를 방지하기 위해 이벤트 필터링을 사용하면 특정 데이터 변경사항만 선택적으로 처리할 수 있다. 예를 들어 진료 기록 중 결제가 완료된 건만 매출 분석 시스템으로 전송하고 싶다면 다음과 같이 설정할 수 있다:
functions:
syncCompletedPayments:
handler: src/handlers/syncToAnalytics.handler
events:
- stream:
type: dynamodb
arn: <복사한 DynamoDB Stream Arn>
enabled: true
filterPatterns:
- eventName: ["INSERT", "MODIFY"]
dynamodb:
- Keys:
pk:
S:
- prefix: "CLINIC#" # 병원 ID
sk:
S:
- prefix: "PAYMENT#" # 결제 기록
NewImage:
status:
S: ["COMPLETED"] # 완료된 결제만
amount:
N: [{"numeric": [">", 0]}] # 금액이 있는 경우만
필터 패턴 (filterPatterns 속성)
eventName: 어떤 DB 작업을 감지할지 지정Keys: DynamoDB의 파티션키/정렬키 기준 필터링NewImage: 변경된 데이터의 특정 필드값으로 필터링
Lambda 함수는 DynamoDB 변경 사항을 PostgreSQL에 적절히 매핑하여 저장한다:
import { type DynamoDBStreamEvent } from 'aws-lambda';
import { unmarshall } from "@aws-sdk/util-dynamodb"; // 역직렬화 유틸
export const handler = async (event: DynamoDBStreamEvent) => {
const records = event.Records.map(record => ({
eventName: record.eventName,
oldImage: unmarshall(record.dynamodb.OldImage || {}),
newImage: unmarshall(record.dynamodb.NewImage || {})
}));
// 배치 처리를 위한 트랜잭션 시작
const client = await pool.connect();
try {
await client.query('BEGIN');
for (const record of records) {
switch (record.eventName) {
case 'INSERT':
case 'MODIFY':
await upsertToPostgres(client, record.newImage);
break;
case 'REMOVE':
await deleteFromPostgres(client, record.oldImage);
break;
}
}
await client.query('COMMIT');
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
};
결론
이번 포스팅에서는 DynamoDB의 쿼리 패턴 제약으로 인해 만족시키지 못하는 요구사항들을 해결하기 위해 DynamoDB Streams와 Lambda를 활용해 PostgreSQL로 데이터를 동기화하는 방식을 소개했다.
추후 개선 사항
재시도 정책 설정
배치 처리 최적화
모니터링 알림 설정


