들어가며
삼쩜삼은 서비스 특성상 종합소득세 신고 기간 등 특정 시기에 트래픽이 집중적으로 몰리면서 다양한 장애 상황을 직면하게 됩니다.

트래픽 집중으로 인한 다양한 포인트에서 장애 상황을 겪었는데, 그 중 상당 부분을 차지하는 것이 데이터베이스 관련 장애였습니다. 짧은 시간에 I/O가 폭발적으로 증가함에 따라 연산을 위한 CPU가 급격하게 증가하고 이로 인해 처리지연이 발생하고 이는 시스템 지연으로 전파되어 심각한 장애상황으로 이어지게 됩니다.
Aurora MySQL 스케일업 등을 통해 향상된 성능으로 트래픽을 처리하는 것도 좋은 해결방안이 될 수 있지만 장기적으로 원인이 되는 데이터를 RDBMS가 아닌 다른 방식으로 저장하는 것이 현재 운영 중인 RDBMS뿐만 아니라 관련 서비스들의 안정성을 확보하는 데 더 효과적이라고 판단했습니다.
이 글에서는 그중 외부 연계 데이터 저장 서비스와 사용자 알림 수신동의 서비스에서 사용되는 데이터를 Amazon DynamoDB로 전환하여 문제들을 해결한 경험을 공유합니다.
외부 연계 데이터 저장 구조 개선
배경
삼쩜삼은 여러 기관으로부터 수집한 고객의 개인화된 정보를 Aurora MySQL에 안전하게 저장하고 있습니다. 그 중 스크래핑 연계를 위한 요청/응답 데이터를 JSON 형식으로 적재하고 있고, 이 데이터는 일 기준 수천만 건, 월 기준 수억 건에 달하며 다음과 같은 형태로 구성되어 있습니다.
- 메타 정보:
success(성공 여부), created_at(생성 일시), error_message(에러 메시지) 등
- 요청/응답 데이터:
input(요청 JSON), output(응답 JSON)
스크래핑 연계 시스템의 요청/응답 데이터는 고객별로 다양한 크기를 가지며 최대 2.5MB까지 달하는 대용량 데이터입니다. 기존에는 이 데이터를 JSON 타입 컬럼에 각각 저장하고 있었는데, 서비스 트래픽이 급격히 증가하면서 다음과 같은 문제들이 발생했습니다
- 데이터베이스 I/O 부하 증가로 인한 전반적인 성능 저하
- 저장, 조회, 백업 등 모든 DB 연산의 응답시간 증가
- 데이터 누적에 따른 스토리지 비용의 지속적 상승
또한, Aurora MySQL에서 Binary log를 활용해 내부 분석 서비스로 테이블을 동기화하는 과정에서 JSON 컬럼을 포함한 대용량 행이 많아지면서 처리 지연과 리소스 과다 소모가 발생했습니다. 이로 인해 동일 클러스터를 사용하는 모든 서비스에 부하가 전파되었습니다.
개선방향
이 문제를 해결하기 위해 요청/응답을 저장하는 테이블을 아래 표와 같이 메타 정보를 DynamoDB에 저장하고 JSON 데이터를 Amazon S3에 저장하는 구조로 변경하였습니다.
Field | 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 (저장된 input S3 Key) |
저희는 아래와 같이 세가지 원칙으로 데이터 저장 구조를 분리 했습니다.
- 메타 정보는 기존 Aurora MySQL에서 Column으로 관리되는 것과 동일한 구조로 DynamoDB의 Attribute로 저장
- 요청/응답 JSON 데이터는 별도 파일로 분리하여 S3에 저장
- S3에 각각 저장된 요청/응답 오브젝트들의 key 값을 DynamoDB의
input_path/output_path
에 Attribute로 저장
요청/응답 JSON은 왜 S3에 저장했는가?
성능 저하의 큰 원인이 되는 대용량 JSON 데이터를 저장하는 위치를 DynamoDB에 직접 저장할 지, S3에 저장할 지에 대한 고민이 있었습니다. 저희는 그 중 S3에 저장하는 방식을 채택했고 그 이유는 아래와 같습니다.
- DBMS에서 Row와 같은 의미인 DynamoDB의 항목(Item)의 최대 저장 크기 제한이 400KB
- 위에서 언급했듯이 요청/응답 데이터의 크기는 최대 2.5MB가 될 수 있어 Item 최대 저장 크기(400KB)를 초과하는 경우가 존재
- DynamoDB의 저장/조회 비용의 단위를 뜻하는 WCE(Write Capacity Unit)/RCU(Read Capacity Unit)와 S3의 PUT/GET API 비용을 고려했을 때 S3에 저장하는 쪽이 비용적 이점이 존재
아래의 그림은 DynamoDB를 적용한 개선된 외부 데이터 요청 시 데이터 흐름입니다.

서비스 트래픽 변화
올해에도 작년과 다름없이 엄청나게 많은 트래픽이 발생했고 심지어 작년 대비 약 150% 가까이 증가하여 월 수억 건 수준으로 빠르게 증가했습니다. 이번 저장구조를 DynamoDB로 전환하고 그 기준으로 사전 성능테스트 등을 통해 안정적인 성능으로 트래픽을 처리할 수 있었고 이는 향후 트래픽 증가에도 유연하게 대응할 수 있는 확장성있는 구조를 갖출 수 있었습니다.
향후 개선 방향 및 비용 최적화
현재 기준으로도 위 구조에서 추가적인 개선 방향을 적용하고 비용을 최적화할 수 있었습니다. 우선, Aurora MySQL 내부의 많은 스토리지 비용을 차지하는 특정 테이블을 정리하였고 I/O 최적화 모드 조정을 통해 월 수천 달러 수준의 비용 절감 효과를 가져올 수 있었습니다. 또한, 기존에 요청/응답 데이터를 S3에 각각 저장하는 방식을 JSON 파일을 하나로 합쳐 저장하도록 개선하여 PUT API 요청량이 절반으로 줄게 되어 비용면에서 약 50% 수준 절감 효과도 보았습니다. 이러한 개선 작업을 통해 구조의 효율성을 더욱 높이고, 비용 역시 지속적으로 최적화해 나갈 계획입니다.
사용자 알림 수신동의 서비스
배경
기존 메시지 서비스
에서 사용자 서비스 별 알림 동의를 관리하고 있던 부분을 별도 서비스로 구축해야 하는 요구사항이 발생했고, 이 서비스에서 데이터 관리 주체는 신규 개발 될 사용자 알림 수신동의 서비스
였습니다.
고객에게 알림 발송 시 메시지 서비스에서 매번 사용자 동의 여부를 조회해야 하는 유스케이스가 필요해졌습니다. 이 요구사항을 바탕으로 메시지 서비스
와 사용자 알림 수신동의 서비스
간의 연동을 위해 여러 방안을 검토했고, 아래 세 가지 방법으로 좁힐 수 있었습니다.
- REST API를 통한 연동
- 데이터 동기화 방식
- DynamoDB를 이용한 데이터 공유
고민 끝에 DynamoDB를 이용한 데이터 공유 방식이 사용자 별 알림 수신동의 데이터를 효율적으로 처리하고 메시지 서비스와의 연동을 최적화 할 수 있다고 판단했습니다. 판단 이유는 다음과 같습니다.
API 연계 방식(REST API)의 한계
메시지 서비스에서 알림 발송 시 매번 수신동의 여부 API를 조회하는 방식은 성능과 비용 측면에서 모두 비효율적이라고 판단했습니다. 대량의 알림 발송 시마다 API 호출이 빈번하게 발생하여 시스템 부하가 증가하고, 그에 따른 비용 부담도 커질 수 있기 때문입니다.
데이터 동기화 방식의 한계
서비스 알림 수신동의 데이터가 변경될 때마다 메시지 서비스에 동기화하는 방식은 성능 상 이점을 가질 수 있습니다. 하지만 이 경우 중복 데이터가 발생하고, 두 시스템 간의 데이터 일관성을 유지하기 위한 관리 포인트가 증가한다는 단점이 있습니다. 이는 시스템의 복잡성을 가중시키고 유지보수 비용을 높일 수 있습니다.
DynamoDB를 선택한 구체적인 이유
이번 시스템에서 가장 중요한 요구사항은 사용자 동의 여부를 모든 스케일에서 일정한 지연시간으로 자주 조회해야 하는 읽기 중심의 패턴입니다.
DynamoDB는 Key-Value 기반 구조로 설계되어 있어 특정 사용자 ID를 기준으로 데이터를 조회하는데 매우 빠르고 효율적입니다. 또한 수백만 명 이상의 사용자 데이터를 처리하는데 DynamoDB 온디맨드 모드는 읽기/쓰기 용량을 즉시 유연하게 조정할 수 있어 효율적으로 대규모 데이터를 처리할 수 있습니다.
특히 이번 데이터는 메시지, 알림 등 여러 서비스에서 공통으로 사용하는 데이터였기 때문에 DynamoDB와 같이 AWS 내 여러 서비스와 연동이 용이하고, 단일 테이블에서 빠르게 조회할 수 있어 적합하다고 판단했습니다. 또한 데이터 구조 역시 사용자 ID에 따른 동의 여부만 저장하면 되기 때문에 매우 단순하여 복잡한 관계형 DB의 스키마나 조인이 필요하지 않습니다. 이러한 이유들로 DynamoDB의 단순 Key-Value 저장 방식이 최적이라고 판단할 수 있었습니다.
마지막으로 향후 데이터가 계속 늘어날 것을 고려할 때 DynamoDB의 자동 확장 기능은 큰 장점으로 작용하게 됩니다. 데이터나 트래픽 증가에 맞춰 자동으로 확장되는 구조이기 때문에 별도 인프라 관리 부담 없이 안정적으로 운영이 가능합니다.
결국, 기술적으로는 다른 데이터베이스를 사용해도 문제가 없지만 이번 프로젝트의 핵심 요구사항인 단순한 Key 조회, 비용 효율성, 확장성, 운영 편의성을 종합적으로 만족하는 선택지가 바로 DynamoDB였습니다.
DynamoDB를 사용한 구조로 구현된 실제 서비스에서는 아래와 같은 데이터 흐름으로 동작하고 있습니다.

결과
메시지 서비스에서는 모든 스케일에서 일정한 지연시간으로 빠른 동의 여부 조회가 가능해졌고, 기존 메시지 서비스
에서 사용자 알림 수신동의 서비스
역할을 분리함으로써 메시지 처리 성능도 크게 개선할 수 있었습니다.
서비스 운영 관점 주요 포인트
위 두 가지 사례를 통해 우리는 DynamoDB를 성공적으로 도입할 수 있었습니다. 하지만 도입 만큼이나 중요한 것이 운영 유지보수입니다. 저희가 DynamoDB를 도입하고 서비스를 운영하면서 해결했던 주요 운영 포인트 3가지를 소개하겠습니다.
DynamoDB의 Warm Throughput(웜 처리량) 조정
Warm Throughput(웜 처리량)이란?
이를 방지하기 위해 테이블의 처리량 제한을 미리 "예열"해둬서 즉시 높은 처리량을 사용할 수 있게하는 것이 필요합니다.
삼쩜삼 서비스는 매년 5월에 트래픽이 집중되는 구조를 가지고 있어 내부적으로 3~4월에 사전 성능 테스트를 진행합니다. 작년 대비 올해 5월을 예측한 부하만큼 테스트를 진행했고, 이 때 테스트한 부하량만큼 처리량을 운영 환경 테이블에 상향 적용하여 실제 5월 기간 내 집중적인 처리량을 안정적으로 소화할 수 있었습니다. 또한 올해 경험을 바탕으로 내년부터 DynamoDB를 사용하는 서비스들의 5월 집중 트래픽을 위한 준비 기간이 줄어들 것으로 예상됩니다.
GSI(Global Secondary Index)를 이용한 백오피스 운영 효율화
서비스 워크로드 환경에서는 메인 테이블의 파티션키(PK)/정렬키(SK) 만으로도 충분히 서비스가 가능했지만, 백오피스와 같은 어드민 환경에서는 다양한 조건으로 데이터를 조회하는 유스케이스가 있었습니다. 이에 따라 어드민에서 필요한 요구사항을 도출하고, 그 내용을 바탕으로 필요한 GSI를 설계했습니다.
- 관리자는 사용자 ID를 기준으로 외부 연계데이터 이력을 조회할 수 있어야 한다.
- 관리자는 실패 처리된 외부 연계데이터 이력을 서비스 코드별로 조회할 수 있어야 한다.
첫 번째 요구사항인 사용자 ID 기준 조회는 모든 사용자에 대해 데이터가 저장되어야 하므로 user_id
Attribute를 파티션키로 하는 GSI를 구성하여 메인 테이블과 동일한 항목 수 만큼 데이터가 저장되는 Index를 설정하였습니다.
두 번째 요구사항인 실패 처리된 이력을 서비스 코드별로 조회할 수 있어야 했는데, 이를 위해 success(성공 여부)
와 service_code(서비스 코드)
라는 두 가지 Attribute를 조합해 failed_service_code
라는 새로운 Attribute를 정의했고 이를 파티션키로 설정한 GSI를 생성했습니다. 그리고 메인 테이블에 Item이 put 될 때 성공 여부 값이 false인 경우만 해당 Attribute 값이 채워지도록 구현했습니다. GSI 특성 상 설정한 파티션키가 메인 테이블의 Attribute로서 값이 존재할 때만 GSI에 Item이 Put되기 때문에 실패된 요청만 GSI에 데이터가 쌓이게 됩니다. 이러한 방식을 희소 인덱스(Sparse Index)라고 하며, 이를 통해 실패한 건들만 GSI에 저장되어 메인 테이블보다 적은 수의 항목이 인덱스에 포함되게 되어 인덱스 크기와 비용을 줄이고 실패 조건에 해당하는 데이터만 빠르게 조회할 수 있어 성능 또한 향상시킬 수 있었습니다.
DynamoDB의 쓰기 직후 읽기 안정성 확보
DynamoDB는 기본적으로 Eventually Consistent Read(EC Read)를 사용하고, 두 가지 읽기 일관성 옵션을 API 파라미터로 제공합니다. 즉, 데이터를 저장한 직후 바로 읽기를 시도하면 아직 모든 복제본에 쓰기 전파가 완료되지 않아 쓰기 전파 지연 (Write Propagation Delay)이 발생할 수 있습니다.
저희 서비스에서 실제 운영 중
- A 서비스에서 DynamoDB Item을 PUT하고 그 파티션키 정보를 담은 Message를 Kafka Publish
- 1번에서 publish한 message를 B 서비스에서 subscribe한 뒤 파티션키로 Item을 Get
위와 같은 흐름에서 2번의 Get 요청 시 1번에서 Put한 데이터가 조회되지 않는 상황이 발생했습니다. 이는 데이터가 누락된 것이 아니라, 쓰기 전파가 아직 완료되지 않았기 때문에 발생한 정상적인 현상입니다.
이러한 특성을 고려하여 다음과 같은 방어 로직을 적용했고, 이 방식을 통해 데이터 정합성을 보장하면서 DynamoDB의 Eventual Consistency 특성에 유연하게 대응할 수 있었습니다.
- 최초 조회는 기본 설정인 [ConsistentRead = false]로 시도
- 조회 결과가 없는 경우, [ConsistentRead = true]로 재조회하여 최신 데이터 확보
마치며
DynamoDB 도입을 통해 우리는 예상했던 것보다 훨씬 더 큰 변화를 경험할 수 있었습니다. 단순히 데이터를 저장하는 공간이 아닌, 안정적인 성능과 무제한 확장성을 제공하는 진정한 인프라의 기반이 되어주었습니다.
가장 인상적이었던 점은 트래픽이 급증하는 순간에도 일관된 응답 시간을 유지하며, 별도의 복잡한 튜닝 없이도 신뢰성 높은 서비스를 구축할 수 있었다는 것입니다. 이는 개발자가 비즈니스 로직에 더 집중할 수 있는 환경을 만들어주었고, 결과적으로 더 빠른 기능 개발과 안정적인 서비스 운영이 가능했습니다.
무엇보다 이번 경험을 통해 관계형 데이터베이스가 유일한 선택지가 아니라는 점을 명확히 깨달았습니다. 용도와 요구사항에 따라 NoSQL이 더 적합한 솔루션이 될 수 있으며, 특히 현대적인 클라우드 환경에서는 DynamoDB와 같은 관리형 서비스가 제공하는 가치가 상당히 크다는 것을 확인할 수 있었습니다.
앞으로도 데이터 저장소를 선택할 때는 기존의 관습에 얽매이지 않고, 실제 요구사항과 확장성을 고려한 최적의 선택을 해나갈 예정입니다. DynamoDB는 분명히 우리의 기술 스택에서 중요한 축을 담당하게 될 것이며, 더 나은 사용자 경험을 제공하는 데 핵심적인 역할을 계속해 나갈 것입니다.
글 | 강병규, 김성신디자인 | 조재원
본 콘텐츠의 저작권은 (주)자비스앤빌런즈에게 있으며, 본 콘텐츠에 대한 무단 전재 및 재배포를 금지합니다