Skip to main content

Command Palette

Search for a command to run...

DynamoDB Streams로 서비스 간 데이터 동기화하기

Updated
4 min read

들어가며

이번 글에서는 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 설정

  1. AWS Console 접속

  2. DynamoDB 테이블 선택

  3. 내보내기 및 스트림 탭 > DynamoDB 스트림 세부 정보 켜기

  4. 새 이미지와 이전 이미지 선택 후 스트림 켜기

  5. 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로 데이터를 동기화하는 방식을 소개했다.

추후 개선 사항

  1. 재시도 정책 설정

  2. 배치 처리 최적화

  3. 모니터링 알림 설정

참고자료

More from this blog

오픈소스 기여모임 10기 후기 - 첫 Pr을 올리기까지

개발자라면 누구나 한 번쯤 오픈소스 기여에 대한 환상을 가져본 적 있을 거다. 하지만 막상 시작하려면 어디서부터 해야 할지 막막하고, 괜히 대단한 걸 해야 할 것 같은 부담감에 선뜻 시작하기는 어려운 것 같다. 나 또한 해보고 싶다는 마음만 가지고 계속 미뤄왔다. 그러다 2025년 말 쯤에 오픈채팅방과 글또 슬랙 채널에서 "오픈소스 기여모임" 10기 모집글을 봤다. 2년 넘게 500명 이상의 참가자와 함께 1000개 이상의 PR을 만들어온 커뮤...

Feb 5, 20265 min read

😢 글또 10기 활동 회고 — “글또야, 가지 마…”

들어가며 드디어 글또 10기 활동 회고를 정리해본다.6개월간의 여정을 뒤돌아보니 정말 많은 일들이 있었다. 글또라는 커뮤니티를 8기가 한창 진행되고 있을 때 알았는데 이름부터 인상이 강렬했다. "글쓰는 또라이가 세상을 바꾼다." 유쾌하고 독특한 문구에 피식 웃으며, '여긴 도대체 어떤 사람들이 모이는 곳이지?' 하고 넘겼었다. 재밌는 건 결국, 나도 그 "또라이들" 중 한 명이 되었다는 것이다. 😌 글또는 개발자들이 2주에 한 번 글을 ...

Jul 31, 20255 min read
😢 글또 10기 활동 회고 — “글또야, 가지 마…”

Serverless 환경에서 배포 전 환경변수 검증 자동화하기: TypeBox와 Bitbucket Pipeline 활용기

들어가며 배포 직후, 환경변수가 제대로 설정되지 않아 여러 API가 제대로 작동하지 않는 일이 있었습니다. 다행히 밤에 사용자가 없을 때 문제가 있었던 거라 영향도는 크지 않았지만 앞으로도 계속해서 발생할 수 있는 문제이기 때문에 해결해야 겠다고 생각했습니다. 개발 단계에서 문제가 발견되면 가장 좋겠지만, 현재 팀 상황에서는 백엔드 개발을 혼자 담당하고 있어 코드 리뷰나 검증 프로세스를 갖추기가 쉽지 않았습니다. 그래서 최소한 배포 전에 자동으...

Mar 16, 20254 min read

Cloudflare Tunnel로 포트포워딩 없이 홈서버 운영하기

이 글에서 다루는 내용 포트포워딩이 안 되는 이유 (CGNAT 환경 이해) CGNAT 우회 방법들의 장단점 비교 Cloudflare Tunnel 설정 방법 (MacOS 기준) 외부에서 내 PC로 접근할 수 있도록 허용하는 방법을 생각하면 포트포워딩이 가장 먼저 떠오릅니다. 공유기에서 특정 포트를 열어 외부에서 서버에 접속할 수 있도록 설정하는 방식으로, 마인크래프트 멀티를 해보셨던 분이라면 분명 해보셨을 방법입니다. 😊 작년에 저는 홈서...

Mar 2, 20256 min read
Cloudflare Tunnel로 포트포워딩 없이 홈서버 운영하기

구름고래 공방

48 posts