어떤 문제가 있었는가?
캐시워크의 일대일 메시징 서비스인 캐시톡의 사용자 증가로 인해 API 서버 비용이 한 달 만에 약 2배 증가하는 문제가 발생했습니다.
이는 CPU 사용률, 네트워크 트래픽, 그리고 데이터베이스 쿼리 증가로 인한 것이었습니다.
문제 발견 및 원인 분석
문제 징후
- CPU 사용률 지속적 상승
- 네트워크 인바운드/아웃바운드 트래픽 약 2배 증가
- API 서버 인스턴스 수 증가 (2배 증가)
- DynamoDB 읽기 요청 급증
원인 분석 과정
문제를 해결하기 위해 Datadog과 AWS CloudWatch 지표를 분석하여 다음과 같은 원인을 파악했습니다.
- 비효율적인 데이터베이스 쿼리 패턴:
- 캐시톡 관련 API에서 친구 사용자 정보를 조회할 때, 일괄 처리(batch) 대신 개별 쿼리를 반복적으로 수행하고 있었습니다.
- 예를 들어, 사용자가 100명의 친구를 가진 경우 친구 사용자 정보 조회를 위해 100번의 개별 DB 쿼리를 실행하고 있었습니다.
친구 한 명당 DDB Query 한 번씩 발생
-
DynamoDB Throttle: 위 친구들의 사용자 정보를 담고 있는
cashwalk.user
테이블과 관련 인덱스가 증가한 트래픽을 처리하기에 충분한 Capacity 를 가지고 있지 않아 Throttle 이 발생했습니다.유저 테이블 Throttle 발생
-
불필요한 데이터 조회: 일부 API에서 필요하지 않은 정보를 추가로 쿼리하고 있었습니다.
-
비효율적인 네트워크 연결 관리: DynamoDB API 호출마다 새로운 연결을 생성하고 있어 리소스를 낭비하고 있었습니다.
해결 방안 설계 및 구현
문제 해결을 위해 다음과 같은 최적화 작업을 설계하고 구현했습니다:
1. 우선적으로 DynamoDB 용량 확장 (병목 해소 목적)
cashwalk.user
테이블의 phone-index 읽기 용량: 500 → 3,500으로 증가친구_관계
테이블의 읽기 용량: 1,000 → 3,500으로 증가
2. 코드 최적화
-
배치 프로세싱 도입: 친구 정보 조회 시 개별 쿼리 대신
batchGetItem
(한 번에 100명)을 사용하여 단일 요청으로 여러 항목을 조회하도록 개선했습니다.유저 테이블 BatchGetItem 도입
-
유저 정보 캐싱: 자주 요청되는 친구 정보에 대해 Redis 캐싱을 도입했습니다. [간략한 코드]
Redis 에 유저 정보 캐싱
-
DynamoDB Connection 재사용: HTTP keepAlive 옵션을 활성화하여 DynamoDB API 호출 시 연결을 재사용하도록 했습니다.
const httpsAgent = new https.Agent({
maxSocket: 256,
keepAlive: true,
rejectUnauthorized: true,
lookup: cacheable.lookup,
});
AWS.config.update({
region: config.AWSRegion,
httpOptions: { agent: httpsAgent },
accessKeyId: config.AWSAccessKeyId,
secretAccessKey: config.AWSSecretAccessKey,
});
- 불필요한 쿼리 제거: 특히
GET /친구_인원_수
API에서 불필요한 친구 정보를 조회하는 쿼리를 제거하였습니다. [불필요하게 친구 유저 정보를 조회하는 쿼리를 제거]
성과 및 결과
구현한 최적화 작업을 통해 다음과 같은 가시적인 성과를 달성했습니다:
성능 개선
- CPU 사용률: 35% → 20% (약 43% 감소)
- 메모리 사용량: 800 MiB → 280 MiB (약 65% 감소)
- API 응답 시간:
- GET /v2/친구_목록: 360ms → 260ms (약 28% 개선)
- GET /v1/친구_목록: 750ms → 247ms (약 67% 개선)
- GET /v2/추천_친구_목록: 600ms → 350ms (약 42% 개선)
비용 절감
- API 서버 인스턴스: 평균 약 8대 감소 (기존 약 16대)
- DynamoDB 사용량:
- 불필요하게 쿼리하던 인덱스의 RCU가 1 미만으로 감소 (불필요한 쿼리 제거로 인한)
쿼리할 필요가 없었던 SNS 계정 인덱스 쿼리 제거
- 유저 데이터 캐싱으로 RCU 1/4 감소
- 불필요하게 쿼리하던 인덱스의 RCU가 1 미만으로 감소 (불필요한 쿼리 제거로 인한)
유저 테이블 RCU 감소
- 전체 서버 비용의 약 50% 절감 효과
배운 점 및 시사점
이번 프로젝트를 통해 얻은 가장 중요한 교훈은 다음과 같습니다.
- 모니터링의 중요성:
- 비용 증가와 성능 저하 문제를 조기에 발견할 수 있었던 것은 적절한 모니터링 덕분이었습니다.
- 특히 비용 모니터링과 성능 모니터링을 함께 진행하는 것이 중요함을 배웠습니다.
- 데이터 접근 패턴 설계의 중요성:
- 애플리케이션의 성능은 데이터베이스 접근 패턴에 크게 영향을 받습니다.
- 특히 NoSQL 데이터베이스인 DynamoDB를 사용할 때는 쿼리 패턴을 사전에 잘 설계하는 것이 중요합니다.
- 팀 협업의 중요성:
- 이번 문제는 백엔드 개발자, 인프라 담당자, 제품 관리자 간의 긴밀한 협력을 통해 해결할 수 있었습니다.
- 특히 기술적 문제를 비즈니스 관점에서 설명하고 우선순위를 설정하는 과정이 매우 중요했습니다.
- 레버리지가 큰 부분부터 최적화:
- 모든 코드를 최적화하는 것보다, 비용 대비 효과가 큰 부분(레버리지가 큰)을 선별적으로 개선하는 전략이 중요함을 배웠습니다.
결론
이 작업은 사용자 경험을 저하시키지 않으면서도 서버 비용을 대폭 절감할 수 있음을 보여준 사례라고 생각합니다.
비효율적인 데이터베이스 접근 패턴을 개선하고, 적절한 캐싱과 HTTP Connection 관리를 통해 서비스 성능을 향상시킬 수 있었습니다.
특히 이번 경험은 서비스 규모가 성장함에 따라 초기 설계가 미치는 영향이 얼마나 큰지를 보여주었습니다.
성능 최적화는 단순히 기술적 과제가 아니라 비즈니스 가치를 창출하는 중요한 활동임을 다시 한번 확인할 수 있었습니다.