AWS 기술 블로그

자비스앤빌런즈의 Amazon DynamoDB 도입기 – 외부 연계 데이터 저장과 약관 서비스 개선

이 블로그 포스트는 자비스앤빌런즈의 강병규님, 김성신님과 함께 작성되었습니다.

개요

자비스앤빌런즈는 텍스테크 스타트업으로, 세금 신고·환급 도움 서비스 ‘삼쩜삼’을 기반으로 누구나 쉽고 간편하게 세무 도움을 받을 수 있도록 서비스를 제공하고 있습니다. 삼쩜삼은 고객의 세무 정보를 안전하게 확보하고 최적화된 계산을 통해 최대의 환급을 이끌어내며, 현재 누적 가입자 2,400만 명, 누적 환급액 1조 6천억 원을 달성하는 등 꾸준히 신뢰받는 플랫폼으로 성장하고 있습니다.

삼쩜삼은 서비스 특성상 종합소득세 신고 기간 등 특정 시기에 트래픽이 집중적으로 몰려 RDBMS 환경만으로는 급격한 부하를 효과적으로 처리하는데 어려움이 있었습니다. 이로 인해 동시 접속자 수가 급증 할 때 서비스 지연이나 자원 소모 문제가 발생하곤 했으며, 실제로 신고 기간에 시스템 부하가 크게 증가하는 현상이 매년 반복 되었습니다. 이 문제를 해결하기 위해 AWS와 함께 Amazon Aurora MySQL 사용 패턴을 진단하기 위한 운영 효율화 워크샵(DCAD for Aurora MySQL)을 진행했고, RDBMS만으로 증가하는 트래픽을 처리하는 것보다 몇가지 서비스의 저장소를 목적에 맞는 Key-Value 데이터베이스로의 변경을 결과로 도출했습니다.

이 글에서는 자비스앤빌런즈의 ‘외부 연계 데이터 저장 서비스’와 ‘약관 서비스’에서 운영이 되던 Aurora MySQL을 서비스 목적에 맞게 Amazon DynamoDB로 적용해 위 문제들을 해결한 경험을 공유합니다.

외부 연계 데이터 저장 서비스

배경

외부 연계 데이터 저장 서비스는 Aurora MySQL을 이용해 외부 연계 시스템에서 요청/응답 (이하 입력/출력) 데이터를 수집하고, JSON 형식으로 저장해왔습니다. 이 데이터는 월 수억 건에 달하며 메타 정보와 입력/출력 데이터의 형태로 구성 되었습니다.

  • 메타 정보: success, created_at, error_message 등
  • 입력/출력 데이터: input (JSON), output (JSON)

외부 연계 시스템의 데이터 구조는 데이터 저장량과 서비스 트래픽이 증가하면서 여러 문제점을 야기했습니다. 입출력용 JSON 데이터가 컬럼당 최대 2.5MB까지 저장되면서, 서비스 트래픽 증가로 인한 데이터베이스 I/O 부하가 커졌습니다. 이는 저장, 조회, 백업 등 모든 작업의 성능 저하를 초래했고, Aurora 스토리지 비용도 지속적으로 증가하는 결과를 가져왔습니다.

특히 Aurora MySQL에서 이진 로그(Binary log)를 통해 내부 분석 서비스와 테이블을 동기화하는 과정에서 문제가 발생했습니다. JSON 컬럼이 포함된 대용량 행(Row)이 증가하면서 처리 지연과 과도한 리소스 소모가 발생했고, 이러한 부하는 동일 클러스터를 사용하는 모든 서비스에 영향을 미쳤습니다.

개선 방향

이 문제를 해결하기 위해 기존의 Aurora MySQL만을 사용하는 구조에서, 아래 표와 같이 메타 정보를 DynamoDB에 저장하고 JSON 데이터를 Amazon S3에 저장하는 구조로 변경을 결정했습니다.

 

AS-IS (Aurora MySQL) TO-BE (DynamoDB + S3)
success Column Attribute
created_at Column Attribute
error_message Column Attribute
service_code Column Attribute
input Column (JSON 타입) S3에 Object로 저장
output Column (JSON 타입) S3에 Object로 저장
input_path Attribute (저장된 Input S3 key)
output_path Attribute (저장된 Output S3 key)

저희는 아래와 같이 세가지 원칙으로 데이터 저장 구조를 분리 했습니다.

  • 메타 정보는 기존 Aurora MySQL과 동일한 구조로 DynamoDB의 어트리뷰트(Attribute)로 저장
  • Input/Output JSON 데이터는 별도 파일로 분리하여 S3에 저장
  • S3에 저장된 오브젝트들의 key 값을 DynamoDB의 input_path/output_path에 어트리뷰트로 저장

JSON 데이터의 저장 위치를 DynamoDB와 S3 중 고민 했었습니다. 우선 DynamoDB 항목(Item)의 최대 400KB 크기 제약을 확인했고, 둘째로 외부 연계 입력/출력 데이터의 크기는 언제든 400KB를 초과할 수 있었고, 셋째로 그렇지 않더라도 DynamoDB의 WCU (Write capacity unit)/RCU(Read capacity unit)와 S3의 PUT/GET API 비용을 고려했을 때 비용적 이점도 함께 활용하기 위해 S3로 결정했습니다.

아래의 그림은 사용자의 외부 데이터 조회 요청 시 사용되는 개선된 데이터 흐름입니다.

서비스 트래픽 변화 (2024 5 vs 2025 5)

외부 연계 서비스의 트래픽은 1년 만에 약 150% 가까이 증가하며, 월 수억 건 수준으로 빠르게 증가하고 있습니다. 하지만 이번 저장 구조 전환을 통해 성능은 일정하게 유지됐고, 향후 트래픽 증가에도 유연하게 대응할 수 있는 확장성 있는 구조를 갖출 수 있었습니다.

비용 변화

저장 구조 전환 과정에서 기존 Aurora MySQL 비용이 일시적으로 약 수천 달러 상승했습니다. 이는 일부 테이블 정리 지연과 I/O 최적화 설정에 따른 영향이었습니다. 또한, DynamoDB와 S3 기반 아키텍처로의 전환으로 인해 신규 비용 역시 수천 달러 정도 추가 발생했습니다. 특히 S3에 요청/응답 데이터를 개별 저장한 부분이 주요한 비용 증가 요인이었습니다.

향후 개선 방향 비용 최적화 가능성

현재 구조에서도 추가적인 최적화 여지가 남아 있습니다. 우선, Aurora MySQL 내부의 테이블 정리와 I/O 모드 조정을 통해 월 수천 달러 수준의 비용 절감이 가능할 것으로 예상하고 있습니다. 또한, S3 저장 방식을 개선해 요청/응답 JSON 파일을 하나로 합쳐 저장할 경우, 약 50% 수준의 비용 절감 효과도 기대하고 있습니다. 이러한 개선 작업을 통해 구조의 효율성을 더욱 높이고, 비용 역시 지속적으로 최적화해 나갈 계획입니다.

약관 서비스

배경

메시징 서비스에서 관리하던 사용자별 알림 수신 동의 기능을 별도의 ‘약관 서비스’로 이전해야 하는 요구사항이 발생했습니다. 새로 개발될 약관 서비스는 ‘서비스 알림 수신 동의’ 기능을 제공하며, 관련 데이터 관리의 주체가 되어야 했습니다. 이에 따라 메시징 서비스는 알림 발송 시마다 약관 서비스에서 사용자의 동의 여부를 확인해야 하는 새로운 프로세스가 필요해졌습니다.

이러한 요구사항을 충족하기 위해 메시징 서비스와 약관 서비스 간의 연동 방안을 다각도로 검토했고, 최종적으로 다음 세 가지 방안으로 압축할 수 있었습니다.

  • REST API를 통한 연동
  • 데이터 동기화 방식
  • DynamoDB를 이용한 데이터 공유

신중한 검토 끝에, DynamoDB를 활용한 데이터 공유 방식이 가장 적합한 솔루션이라고 판단했습니다. 이 방식은 사용자별 알림 수신 동의 데이터를 효율적으로 처리할 수 있을 뿐만 아니라, 메시징 서비스와의 연동도 최적화할 수 있다는 장점이 있었습니다. DynamoDB를 최종 선택한 구체적인 이유는 다음과 같습니다.

  1. API 연계 방식 (REST API)의 한계 :
    메시징 서비스에서 알림 발송 시 매번 수신 동의 여부 API를 조회하는 방식은 성능과 비용 측면에서 모두 비효율적이라고 판단했습니다. 대량의 알림 발송 시마다 API 호출이 빈번하게 발생하여 시스템 부하가 증가하고, 그에 따른 비용 부담도 커질 수 있기 때문입니다.
  2. 데이터 동기화 방식의 한계 :
    서비스 알림 수신 동의 데이터가 변경될 때마다 메시징 서비스에 동기화하는 방식은 성능 상 이점을 가질 수 있습니다. 하지만 이 경우 중복 데이터가 발생하고, 두 시스템 간의 데이터 일관성을 유지하기 위한 관리 포인트가 증가한다는 단점이 있습니다. 이는 시스템의 복잡성을 가중시키고 유지보수 비용을 높일 수 있습니다.

DynamoDB를 선택한 구체적인 이유

이번 시스템의 핵심 요구사항은 사용자 동의 여부를 모든 스케일에서 일정한 지연시간으로 자주 조회해야 하는 읽기 중심의 패턴입니다. DynamoDB는 Key-Value 기반 구조로 설계되어 있어, 특정 사용자 ID를 기준으로 한 데이터 조회가 매우 빠르고 효율적입니다. 특히 수백만 명 이상의 사용자 데이터를 처리해야 하는 상황에서, DynamoDB의 온디맨드 처리량 모드를 통해 읽기/쓰기 용량을 즉시 유연하게 조정할 수 있다는 점이 큰 장점이었습니다.

또한 이번 데이터는 메시징, 알림 등 여러 서비스에서 공통으로 사용되는 만큼, 데이터 공유의 효율성이 중요했습니다. DynamoDB는 AWS 내 여러 서비스와의 연동이 용이하고, 단일 테이블에서 빠른 조회가 가능합니다. 데이터 구조도 사용자 ID에 따른 동의 여부만 저장하면 되는 단순한 형태여서, 복잡한 관계형 DB의 스키마나 조인 없이 DynamoDB의 Key-Value 저장 방식이 최적의 선택이었습니다.

향후 지속적인 데이터 증가에 대비한 확장성도 중요한 고려사항이었습니다. DynamoDB는 데이터나 트래픽 증가에 따라 자동으로 확장되어, 별도의 메인터넌스나 인프라 관리 부담 없이 안정적인 운영이 가능합니다. 결과적으로, 단순한 Key 조회, 비용 효율성, 확장성, 운영 편의성이라는 프로젝트의 핵심 요구사항을 모두 만족하는 최적의 솔루션이 DynamoDB였습니다.

현재 서비스는 다음과 같은 데이터 흐름으로 운영되고 있습니다:

  • 약관 서비스에서 사용자 수신 동의 정보를 DynamoDB에 저장
  • 메시징 서비스와 기타 연계 서비스들은 DynamoDB에서 해당 정보를 조회

DynamoDB는 테이블명만 공유하면 타 서비스에서도 쉽게 접근할 수 있어, 별도의 복잡한 연결 설정이 필요 없습니다. 이러한 구조는 운영 측면에서도 매우 효율적이었고, 서비스 간 연동을 단순화하는 데 큰 도움이 되었습니다.

[대체 솔루션 검토: ElastiCache 사용을 고려하지 않은 이유]

  • 캐시 중심 구조로 장기 저장에 부적합: ElastiCache는 본래 캐시용 데이터 저장소로서 설계되었습니다. Time to live (TTL)을 설정하지 않고 장기간 데이터를 저장하게 되면, 캐시의 목적을 벗어나 메모리 기반의 영구 저장소처럼 사용되는 비정상적인 형태가 됩니다. 이는 ElastiCache의 핵심 강점인 빠른 접근 속도를 활용하기보다, 비용 효율성이 낮은 저장소로 전락시킬 수 있다고 판단했습니다.
  • 메모리 기반으로 인한 비용 비효율성: ElastiCache는 모든 데이터를 메모리 상에서 처리합니다. 따라서 데이터량이 증가할수록 필요한 메모리 자원이 늘어나고, 이는 비용 증가로 이어집니다. 특히 수백만 명 규모의 사용자 수신 동의와 같이 대규모 데이터를 장기간 보관해야 하는 시나리오에서는, 디스크 기반의 데이터베이스에 비해 비용 효율성이 현저하게 낮아질 수밖에 없습니다.
  • 데이터 유실 가능성: ElastiCache for Redis OSS/Valkey는 매번 Primary failover가 일어날 때마다 primary에서 replica로 아직 복제되지 않은 데이터는 유실되는 특성으로 인해, 데이터 정합성이 중요한 이번 서비스의 목적과는 맞지 않다고 생각했습니다.
  • 수동 확장으로 인한 운영 부담: 데이터 크기가 증가하거나 서비스 트래픽이 증가할 경우, 우리는 서버리스가 아닌 자체 설계된 ElastiCache를 사용하기 때문에 직접 클러스터 구성, 샤딩 수동적인 확장 작업이 필요합니다. 이는 서비스 규모가 커질수록 운영 부담을 증가시키고, 확장 과정에서 발생할 수 있는 서비스 장애나 데이터 일관성 문제 등 안정성 확보에도 어려움을 초래할 수 있습니다. 반면, DynamoDB는 버전이 없어 별도의 메인터넌스 시간으로 인한 중단이 필요없고, 이런 확장 작업을 자동으로 처리해주므로 운영 효율성이 훨씬 높습니다. 결국, 이번 프로젝트의 핵심 요구사항인 단순한 Key-Value 조회, 비용 효율성, 확장성, 운영 효율성을 종합적으로 만족하는 최적의 선택지는 DynamoDB였습니다.

결과

사용자 알림 수신 동의 기능을 Aurora MySQL에서 DynamoDB로 이관한 결과, 기존 Aurora 클러스터의 읽기 부하가 약 30~40% 감소했고 메시지 발송 시 동의 여부를 확인하는 쿼리가 제거되면서 메시지 처리 속도는 최대 5배 개선되었습니다. 또한, 부하 구간에서 발생하던 메시지 전송 실패율도 약 70% 가까이 감소하며 전체 메시지 처리 성능이 눈에 띄게 향상되었습니다. 마지막으로 약관 서비스는 DynamoDB를 이용해 모든 스케일에서 일정한 지연시간으로 다양한 서비스들에서 빠른 동의 여부 조회가 가능해졌습니다.

서비스 운영 관점 주요 포인트

DynamoDB의 Warm throughput 조정

삼쩜삼 서비스는 매년 5월에 트래픽이 집중되는 구조를 가지고 있어 내부적으로 3~4월에 사전 성능 테스트를 진행합니다. 작년 대비 올해 5월을 예측한 부하만큼 테스트했고, 이때 사용된 WCU/RCU 값을 운영 환경에 상향 적용하여 기간 내 집중적인 처리량을 안정적으로 소화할 수 있었습니다. 요약하면, 서비스에 필요한 처리량보다 Warm throughput이 충분하지 않으면 대량의 쓰로틀링이 발생 가능하고, 이는 서비스의 성능 저하 및 사용자 경험 악화로 이어질 수 있기 때문에 이벤트 일정 전 예상 트래픽만큼 충분한 처리량을 Warm throughput 기능을 이용해 증가시키는 것이 필요합니다. 올해 경험을 바탕으로 내년부터 DynamoDB를 사용하는 서비스들의 5월 이벤트를 위한 준비 기간이 줄어들 것을 예상합니다.

DynamoDB GSI (Global Secondary Index) 이용한 백오피스 운영 효율화

DynamoDB로 전환하면서 가장 생소하게 느껴졌던 부분이 바로 인덱스였습니다. 서비스 워크로드 환경에서는 메인 테이블의 파티션키/정렬키 만으로도 충분히 서비스가 가능했지만, 백오피스와 같은 어드민 환경에서는 다양한 조건으로 데이터를 조회해야 했습니다. 이에 따라 어드민에서 필요한 요구사항을 도출하고, 그 내용을 바탕으로 필요한 GSI를 설계하게 되었습니다.

  1. 관리자는 회원 ID를 기준으로 데이터를 조회할 수 있어야 한다.
  2. 관리자는 실패한 요청들을 서비스 코드 별로 조회할 수 있어야 한다.

첫 번째 요구사항인 회원 ID로의 조회는 모든 회원에 대해 해당 값이 저장되어야 하므로, 메인 테이블과 동일한 항목 수 만큼 데이터가 저장되는 GSI로 구성했습니다. 두 번째로, 실패한 요청을 서비스 코드 별로 조회할 수 있어야 했는데, 이를 위해 success (성공 여부)와 service_code (서비스 코드)라는 두 가지 어트리뷰트를 조합해 failed_service_code라는 어트리뷰트을 만들었습니다. 그리고 success가 ‘N’인 경우에만 이 어트리뷰트의 값이 채워지도록 설정한 뒤, 이를 파티션 키로 가지는 GSI를 생성했습니다. 이러한 방식을 희소 인덱스(Sparse index)라고 하며, 이를 통해 실패한 건들만 GSI에 저장되어 메인 테이블보다 적은 수의 항목이 인덱스에 포함되게 되어 인덱스 크기와 비용을 줄이고 실패 조건에 해당하는 데이터만 빠르게 조회할 수 있어 성능 또한 향상 되었습니다.

DynamoDB의 쓰기 직후 읽기 안정성 확보

DynamoDB는 기본적으로 Eventually consistent read (EC read)를 사용하고, 두 가지 읽기 일관성 옵션을 API 파라메터로 제공합니다.
즉, 데이터를 저장한 직후 바로 읽기를 시도하면, 아직 모든 복제본에 쓰기 전파가 완료되지 않아 쓰기 전파 지연 (Write propagation delay)이 발생할 수 있습니다. 저희 서비스에서 실제 운영 중 [Put → Kafka pub] → [Kafka sub → Get] 흐름에서, 쓰기 직후의 Get 요청에서 데이터가 조회되지 않는 상황이 발생했습니다. 이는 데이터가 누락된 것이 아니라, 쓰기 전파가 아직 완료되지 않았기 때문에 발생한 정상적인 현상입니다.

이러한 특성을 고려하여 다음과 같은 방어 로직을 적용했고, 이 방식을 통해 데이터 정합성을 보장하면서도, DynamoDB의 Eventual consistency 특성에 유연하게 대응할 수 있었습니다.

  1. 최초 조회는 기본 설정 (ConsistentRead = false)으로 시도
  2. 조회 결과가 없을 경우, ConsistentRead = true로 재조회하여 최신 데이터 확보

AWS와의 협업

이번 자비스앤빌런즈와의 협업은 AWS가 운영 중인 DCAD (Database Clinic in A Day) 프로그램의 Aurora MySQL과 DynamoDB 두 가지 데이터베이스 엔진이 모두 지원된 첫 사례입니다. 아래 그림과 같이 DCAD for Aurora MySQL을 통해 현재 Aurora MySQL 클러스터를 진단해 권장 사항을 도출했고, 동시에 DynamoDB로 변경이 필요한 워크로드들을 선별해 DCAD for DynamoDB로 연계해 고객의 데이터베이스 현대화를 도왔습니다.

  • DCAD for Aurora MySQL: 현재 Aurora MySQL을 사용하고 있는 고객을 대상으로 진행됩니다. Aurora MySQL의 운영 교육을 진행 후, 고객의 실제 Aurora MySQL 클러스터를 진단하는 DCAD 워크샵으로 진행되는 프로그램입니다.
  • DCAD for DynamoDB: DynamoDB 도입을 원하는 고객에게 DynamoDB Immersion Day 교육을 통해 DynamoDB의 러닝 커브를 줄여드리고, 실제 고객 워크로드를 기반으로 DynamoDB 키 디자인과 서비스 아키텍처를 함께 리뷰하는 DCAD 워크샵으로 진행되는 프로그램입니다.

이 글을 읽고 계신 AWS 고객분들 중에도 Aurora MySQL을 운영하며, 이번 사례와 같이 데이터베이스 현대화가 필요하신 분들은 함께 일하고 계신 AWS 팀에 DCAD를 요청해주십시오. 두 가지 엔진을 함께 혹은 별도로 요청 하실 수 있습니다.

결론

이번 프로젝트를 통해 우리는 DynamoDB와 S3를 활용한 구조로 외부 연계 데이터와 사용자 수신 동의 데이터를 효율적으로 저장하고 최적화 할 수 있었습니다. 작년 대비 요청량이 150% 이상 증가했음에도 시스템 성능은 안정적으로 유지됐고, 확장성도 충분히 확보할 수 있었습니다. 운영 측면에서도 사전 Warm throughput 조정과 DynamoDB의 ConsistentRead 옵션을 활용한 방어 로직을 적용해, 안정적이고 신뢰성 높은 환경을 구축했습니다. 또한, 앞으로 추가적인 최적화를 통해 약 20~30% 수준의 추가 비용 절감 가능성도 남아 있어 장기적인 비용 효율성 측면에서도 긍정적인 효과를 기대하고 있습니다.

김성신

김성신

자비스앤빌런즈 테크코어팀을 리드하고 있습니다. 회원/인증/결제/메시징/스크래핑 등 회사 전반에 사용되는 공통 서비스를 설계 및 운영하며, 대용량 트래픽 환경에서 안정적이고 확장 가능하게 제공하고, 이를 전사 서비스에 유기적으로 활용될 수 있도록 지원하고 있습니다.

강병규

강병규

자비스앤빌런즈의 CTO로서, 클라우드 기반의 기술 전략과 서비스 아키텍처를 총괄하고 있습니다. 안정적이면서도 빠르게 확장 가능한 기술 환경을 설계하고, 개발자들이 자율과 책임을 바탕으로 성장할 수 있는 문화를 만들어가고 있습니다. 기술을 비즈니스 성장의 핵심 동력으로 삼아, 변화에 강한 조직을 구축하는 데 집중하고 있습니다.

Hyuk Lee

Hyuk Lee

Hyuk Lee is a DynamoDB Solutions Architect based in South Korea. He loves helping customer modernize their architecture using the capabilities of Amazon DynamoDB.

Daeheon Oh

Daeheon Oh

오대헌 테크니컬 어카운트 매니저는 데이터베이스에 대한 경험을 바탕으로 Enterprise support 고객에게 데이터베이스를 안정적으로 서비스 될 수 있도록 고객과 함께 효율적인 아키텍처와 운영 방식을 구성하는 역할을 수행하고 있습니다.

Crystal Han

Crystal Han

AWS의 Senior Technical Account Manager 으로서 Enterprise Support 고객사의 시각과 경험을 AWS 내부에 전달하여 더 나은 서비스로 개선되도록 Flywheel 을 지속합니다.