프롤로그: 5월의 도전
종합소득세 환급을 진행하는 삼쩜삼 서비스에게 5월은 언제나 긴장되는 시기입니다. 종합소득세 신고 시즌이 다가오면 수많은 사용자들이 한꺼번에 몰려들어, 트래픽이 급격하게 증가하게 됩니다. 특히 신고 시즌 초기에는 접속자 수가 폭발적으로 늘어나고, 시스템은 큰 부하를 받게 되죠.
작년에도 비슷한 상황을 겪었고, 예상치 못한 트래픽 급증으로 인해 많은 어려움을 겪었습니다. 이러한 상황을 극복하기 위해 우리는 다양한 해결책을 고민했고, 그 대안으로 성능 테스트를 진행하기로 결정했습니다.
1. 준비 단계: 성능 테스트 전략 수립
우리는 먼저 5월을 대비하기 위한 성능 테스트 전략을 수립하고 성능 테스트를 효과적으로 수행하기 위해 다음과 같은 고민을 했습니다.
- 성능 테스트 목표는 무엇인가?
- 어떤 지표가 필요한가?
- 지표는 어떻게 수집 할 것인가?
- 부하를 어떤 방식으로 줄 것인가?
- 부하를 받는 서버를 새로 구축 할 것인가?
- 외부기관과 연계는 어떻게 할 것인가?
- 모니터링은 어떻게 할 것인가?
목표 설정
성능 테스트에서 가장 중요한 단계는 명확한 목표를 설정하는 것입니다. 이번 5월에 중점적으로 목표를 설정한 부분은 예상되는 TPS와 동시 접속자 수를 기준으로 시스템이 이를 처리할 수 있는지 여부를 확인하고, 이에 맞게 성능을 튜닝하는 것이었습니다. 이를 위해 다양한 데이터를 기반으로 목표를 산정하였습니다.
- APM 데이터 분석: 최대 TPS와 동시 접속자 수를 중점적으로 분석하여 이번 테스트의 목표 데이터를 마련했습니다. 이는 서비스 성능 측정에 중요한 지표로 활용되었습니다.
- 신규 가입자 및 DAU 증가 분석: 전년 대비 신규 가입자가 큰 폭으로 증가했고, 앱의 일일 활성 사용자(DAU)가 200만 명까지 상승했습니다. 이를 기반으로 향후 트래픽 증가율을 예측했습니다.
- 서비스 성장 반영: 세무 서비스가 다양화되면서 서비스 이용 대상이 확대되었습니다. 이는 TPS에 큰 영향을 끼치고, 그에 따른 TPS 목표치를 상향 조정했습니다.
이를 기반으로 올해는 사용자 증가 추이를 고려하여 전년 5월 대비 TPS는 3배, 동시접속자 수는 2.5배 수치를 목표로 설정하였습니다.
핵심 지표 정의
성능 테스트 대상 애플리케이션은 Spring Boot 기반으로, Actuator와 Micrometer가 이미 적용되어 있었습니다. 이를 통해 애플리케이션의 상태와 성능을 모니터링할 수 있었습니다.
애플리케이션에서 수집하는 지표는 매우 광범위합니다. 그럴 경우 수집하려는 데이터 양이 증가하고 이는 지표 수집 자체에 영향을 줄 수 있습니다. 이를 해결하고자 중점적으로 볼 지표를 등록하고 해당 지표만 수집하는 방식으로 변경했습니다. 여기서 중점적으로 볼 지표는 다음과 같이 선정했습니다.
- Embedded Server
- Custom Thread
- HikariCP
- HTTP Client (RestTemplate, Feign Client)
- JVM
또한, Kafka는 대용량 데이터 처리와 실시간 스트리밍에 핵심적인 역할을 하기 때문에 성능 테스트에서 주목해야 할 요소입니다. Kafka에 대한 수집 지표는 아래와 같이 선정하였습니다:
- Lag Count
- Consumer 처리량
마지막으로, 부하 테스트 결과를 상세히 분석하기 위해 K6 HTTP Metrics도 수집 대상에 포함하였습니다.
- 요청 수
- 응답 시간
- 에러율
지표 수집
애플리케이션 지표 모니터링을 위해 지표를 기록하는 과정은 AWS CloudWatch를 활용했습니다. 모든 애플리케이션이 AWS에서 운영되고 있으므로, 가장 빠르게 지표를 확인할 수 있는 방법은 CloudWatch로 데이터를 전송하는 것입니다. 기존에 수집하던 데이터 외에도, 추가로 식별한 커스텀 메트릭을 CloudWatch에 전송하여 기록했습니다.
CloudWatch를 활용한 지표 수집은 애플리케이션에서 생성된 메트릭 데이터를 CloudWatch로 전송하는 방식(push)으로 이루어집니다. CloudWatch에서는 미리 정의된 기준치가 존재합니다. 예를 들어, 특정 기준치(예: TPS, 초당 트랜잭션 수)를 초과하는 경우 오류가 발생합니다.
CloudWatchMeterRegistry에는 해당 수치를 제어하기 위한 옵션을 제공하며 우리는 해당 값을 다양한 서버에서 쉽게 적용할 수 있도록 공통 라이브러리로 만들어 제공했습니다. 또한, 특정 기준치를 초과할 경우 수정이 필요한 설정은 서버 별로 조정할 수 있도록 커스텀 설정을 제공했습니다.
CloudWatch의 기준치가 미리 정해져 있고, 데이터를 애플리케이션에서 직접 HTTP 통신으로 전달한다는 단점 때문에 Prometheus 방식을 고려했으나, Prometheus 방식의 ECS Service Discovery 연계가 준비되지 않아 최종적으로 CloudWatch를 선택하게 되었습니다. Prometheus와의 통합을 진행하기에 앞서 준비가 부족한 상황에서, 이미 안정적으로 사용할 수 있는 CloudWatch를 통해 메트릭 수집 및 모니터링을 진행하기로 결정했습니다.
K6를 사용한 성능 테스트의 HTTP 관련 메트릭은 InfluxDB로 수집하도록 하였습니다. InfluxDB는 K6와의 통합이 용이하며, 대규모 부하 테스트에서도 성능 지표를 빠르고 효율적으로 수집할 수 있었습니다.
Kafka와 관련된 메트릭은 Kafka Exporter를 활용해 Prometheus로 수집했습니다. Kafka Exporter는 Consumer 처리량, Topic별 lag 등을 Prometheus에서 실시간으로 모니터링할 수 있게 해주어, Kafka 클러스터의 성능 상태를 지속적으로 파악할 수 있도록 도와줍니다.
부하 방식
부하를 주는 방식으로는 사용자들의 실제 앱 사용 시나리오를 분석하여 이를 기반으로 스크립트를 작성했습니다. 이를 통해 실제 운영 환경과 유사한 부하를 시뮬레이션할 수 있었으며, 특히 5월의 실제 트래픽 양과 동일한 수준으로 부하를 설정하고자 했습니다. 이러한 목적에 가장 적합한 테스트 도구로 K6를 활용했습니다. K6는 JavaScript 기반으로 스크립트를 작성할 수 있어 개발자들이 쉽게 사용할 수 있고, 부하량을 조절하는 것도 간편하여 사용자 친화적입니다. 이로써 다양한 부하 시나리오를 손쉽게 구현할 수 있었고 시스템이 예상되는 트래픽을 어떻게 처리하는지 모니터링하고 분석할 수 있었으며, 실제 운영 환경에서 발생할 수 있는 이슈를 미리 파악하는 데 용이했습니다.
성능 테스트 환경
성능 테스트를 위한 별도의 AWS 환경도 구축했습니다. 실서비스에 영향을 주지 않으면서 실제와 유사한 환경에서 테스트를 진행할 수 있도록, 테스트 환경은 프로덕션과 최대한 유사하게 구성되었습니다. 이를 통해 성능 테스트 결과의 신뢰성을 확보할 수 있었습니다.
외부 기관 연계 방안
카카오 로그인이나 스크래핑과 같이 외부 기관과 연계되는 애플리케이션의 성능 테스트 시, 외부 API에 직접 부하를 줄 수 없기 때문에 Mock 서버를 구축해 대체했습니다. Mock 서버는 외부 API의 동작을 모방하여 실제와 유사한 데이터를 제공함으로써, 성능 테스트 환경에서 외부 시스템과의 통신을 시뮬레이션할 수 있었습니다. 특히, 스크래핑처럼 운영 환경에서 응답 시간이 긴 API의 경우 Mock API의 응답 시간을 실제와 동일하게 설정했습니다. 이를 통해 실제 서비스에 영향을 주지 않으면서도 외부 API 연계와 관련된 성능을 신뢰성 있게 테스트할 수 있었습니다.
준비를 마친 후, 성능 테스트를 수행했습니다. 이제 가장 중요한 작업은 성능 테스트 결과를 확인하는 것입니다. 이를 위해 시스템을 실시간으로 모니터링하고, 각 지표를 분석하여 병목 지점을 확인해야 합니다. 성능 저하가 발생한 부분을 식별한 후, 해당 지점을 튜닝하여 성능을 최적화하는 작업이 남아 있습니다. 이러한 과정은 시스템의 안정성과 효율성을 높이는 데 필수적인 단계입니다.
2. 성능 테스트 지표 분석과 모니터링
API 선정 및 분석
성능 테스트를 시작하기 전에 테스트 대상 API를 신중하게 선정하는 과정이 있었습니다. 모바일 환경에서 발생하는 다양한 API 호출 중 사용자에게 가장 큰 영향을 미칠 수 있는 API를 선별하는 것이 핵심이었습니다. 이를 위해 APM과 OpenSearch를 활용하여 3~4월 동안의 사용자 트래픽을 분석했고, 그 결과 트래픽이 가장 높고 성능 저하 가능성이 있는 상위 10개의 API를 선정했습니다. 이러한 API들은 사용자 경험에 직접적인 영향을 주기 때문에, 정상적인 작동 여부를 검증하는 것이 중요했습니다.
Micrometer 및 AWS CloudWatch를 활용한 지표 수집
부하 테스트 중 각 애플리케이션의 성능 지표는 Micrometer 라이브러리를 통해 AWS CloudWatch로 자동 전송되었습니다. 초기에는 AWS Console에서 지표를 확인하며 모니터링을 진행했지만, 다양한 대시보드를 구성하고 지표를 시각적으로 분석하는 데는 한계가 있었습니다.
Grafana를 활용한 지표 모니터링
AWS CloudWatch에 수집된 데이터를 기반으로, Grafana 대시보드를 체계적으로 구축했습니다. Grafana는 뛰어난 시각화 기능을 제공하여, 부하 테스트 과정에서 수집된 메트릭을 더욱 직관적으로 분석할 수 있도록 도와주었습니다. 이를 통해 실시간으로 성능 상태를 모니터링하고, 필터링 기능을 활용해 특정 API나 시간대별 성능 변화를 분석할 수 있게 되었습니다.
특히 Grafana를 활용한 대시보드는 팀이 성능 이슈가 발생한 구간을 더 빠르고 쉽게 파악할 수 있도록 해주었고, 이후 문제 해결 과정에서도 큰 도움이 되었습니다. 이러한 노력 덕분에 AWS Console에서의 기본적인 모니터링을 넘어, 더욱 심층적이고 시각적인 분석을 통해 애플리케이션의 성능 최적화에 기여할 수 있었습니다.
APM 및 OpenSearch를 통한 병목현상 분석
애플리케이션의 성능 지표를 면밀히 분석하기 위해 APM과 OpenSearch를 활용하여 성능 저하와 병목 현상이 발생하는 구간을 상세히 확인했습니다. 부하 테스트로 수집된 CPU 사용량, 메모리 소비, API 응답 시간, 트랜잭션 처리 속도 등의 지표를 종합적으로 분석하여 병목 현상이 발생하는 부분을 정확히 파악했습니다.
APM은 애플리케이션의 트랜잭션 흐름을 세부적으로 추적하여 성능이 저하되는 지점을 식별하는 데 활용되었습니다. 요청 경로상의 지연 구간이나 데이터베이스 쿼리 실행 시간이 오래 걸리는 부분을 찾아내는 데 특히 유용했습니다. OpenSearch는 대규모 로그 데이터를 효율적으로 검색하고 분석하여 특정 시간대의 오류나 지연 문제를 구체적으로 파악하는 데 도움이 되었습니다.
성능 최적화
이후에는 병목 현상이 확인된 구간을 바탕으로 튜닝 작업을 수행해야 합니다. 이 과정에서는 데이터베이스 쿼리 최적화, 캐시 도입, API 구조 개선, 또는 애플리케이션 코드 레벨에서의 수정 등 다양한 최적화 전략이 필요할 수 있습니다. 이를 통해 성능 저하를 유발하는 부분을 제거하고, 전반적인 시스템의 응답 속도와 처리 성능을 향상시키는 것이 목표입니다. 이러한 튜닝 과정을 거치면 애플리케이션이 높은 부하 환경에서도 안정적이고 빠르게 동작할 수 있게 되어, 사용자 경험이 한층 개선될 것입니다.
이제 성능 테스트를 통해 발견한 애플리케이션의 주요 튜닝 포인트에 대해 설명하겠습니다.
3. 성능 튜닝: 효율적인 리소스 관리
성능 테스트 결과를 바탕으로 우리는 시스템의 여러 설정을 최적화하기로 했습니다.주요하게 생각한 최적화 대상은 다음과 같습니다.
- HikariCP
- HTTP Client
- Kafka Partition 및 Consumer
- Cache 도입
HikariCP
Spring Boot JPA를 사용 할 경우 기본적으로 데이터베이스 연결 관리는 HikariCP를 사용하게 됩니다. 데이터베이스와의 연결을 효율적으로 재사용 하여, 새로운 연결을 생성하는데 드는 오버헤드를 줄이고, 응답 속도를 개선했습니다. 성능을 최적화 하기 위해 풀 크기 조정, 연결시간 제한, 최대 수명 설정 등을 조정해 가며 최적화 작업을 했습니다.
HTTP Client
특히, HTTP Client에 대한 튜닝도 중요한 부분이었습니다. 우리는 RestTemplate, OpenFeign, WebClient를 사용하고 있었기 때문에, 각각의 클라이언트에 대한 성능 최적화가 필요했습니다.
- RestTemplate: 기존에 사용하던 기본 설정에서 커넥션 타임아웃과 읽기 타임아웃을 적절히 조정하고, 커넥션 풀을 설정하여 고성능 요청 처리가 가능하도록 튜닝했습니다.
- OpenFeign: Feign 클라이언트의 경우, 기존에 사용하던 OkHttp를 Apache HTTP Client로 통일했습니다. 이 변경을 통해 성능과 호환성을 개선했으며, Apache HTTP Client의 강력한 커넥션 풀 관리 기능을 활용해 효율성을 높였습니다. 또한, 프로젝트마다 다르게 설정되어 있던 HTTP 클라이언트 설정을 표준화하여 모든 프로젝트에 적용했습니다. 이를 통해 일관된 성능과 유지보수성을 확보할 수 있었습니다.
- WebClient: 마찬가지로 WebClient도 커넥션 풀 및 타임아웃 설정을 통해 성능을 극대화했습니다.
Kafka Partition 및 Consumer
Kafka는 메시지 스트리밍 플랫폼으로, 파티션과 컨슈머의 설정이 성능에 중요한 영향을 미칩니다. 파티션 수를 늘리면 병렬 처리가 가능해지고, 이를 처리할 수 있는 컨슈머의 수를 적절하게 늘리면 메시지 처리 속도가 개선됩니다. 최적의 파티션 및 컨슈머 수를 설정하여 메시지 처리의 병목을 줄이는 작업을 했습니다.
Redis Cache를 활용한 DB 부하 최소화
우리 시스템에서는 Redis를 이미 사용하고 있으며, 이를 통해 성능 최적화를 더욱 강화했습니다. 특히, API 호출 중 데이터 변경이 빈번하지 않은 경우에는 매번 데이터베이스를 조회하는 대신, Redis 캐시를 활용해 성능을 극대화했습니다. 이를 통해 API 호출 시 불필요한 DB 조회를 최소화하고, 캐시된 데이터를 빠르게 반환함으로써 응답 속도와 시스템 처리 효율을 크게 향상시킬 수 있었습니다. 이 최적화 작업을 통해 전체 시스템 부하가 줄어들고, 사용자 경험 또한 눈에 띄게 개선되었습니다.
4. 테스트 결과: 더 단단해진 시스템
다양한 최적화 작업들이 단계별로 수행되면서, 급증하는 트래픽 상황에서도 시스템이 안정적으로 동작할 수 있는 기반이 확고해졌습니다. 특히, 우리 회사의 환경과 요구사항에 맞춘 최적의 설정을 찾아내어 이를 표준화함으로써, 시스템 관리와 유지보수가 더욱 효율적으로 이루어졌습니다. 표준화된 설정 덕분에 다양한 팀이 일관된 방식으로 시스템을 관리할 수 있게 되었고, 새로운 트래픽 변화나 서비스 요구 사항에도 신속하게 대응할 수 있는 체계가 마련되었습니다.
이러한 표준화 작업은 성능 최적화뿐만 아니라 운영 효율성 측면에서도 큰 이점을 제공했습니다. 반복적인 성능 테스트와 튜닝 작업은 시스템의 처리 성능을 전반적으로 향상시켰으며, 더 많은 트래픽과 높은 부하에도 문제없이 작동할 수 있는 견고한 구조를 구축했습니다. 이에 따라 시스템 응답 속도가 빨라지고, 리소스 사용 효율이 높아지면서 사용자 경험이 대폭 향상되었습니다. 앞으로도 다양한 트래픽 패턴과 비즈니스 요구에 유연하게 대응할 수 있는, 준비된 시스템으로 발전하게 되었습니다.
에필로그: 5월, 그리고 그 이후
5월의 급격한 트래픽 증가를 성공적으로 처리한 경험은 우리 시스템의 성능과 안정성에 새로운 전환점을 마련해 주었습니다. 트래픽이 가장 많이 몰리는 시점에도 성능 저하 없이 안정적인 서비스를 제공할 수 있었던 것은, 각 단계에서 최적화를 성공적으로 수행한 결과였습니다. 성능 테스트와 튜닝 작업은 단순한 문제 해결을 넘어서, 시스템의 구조적 개선과 확장성을 강화하는 데 큰 기여를 했습니다.
5월 이후에도 우리는 지속적으로 시스템을 모니터링하고, 새로운 트래픽 패턴에 유연하게 대응할 수 있는 체계를 유지하고 있습니다. 성능 테스트를 통해 최적화한 설정들은 표준으로 자리 잡아, 더 복잡한 상황에서도 효과적으로 대응할 수 있게 되었습니다. 이로 인해 우리 시스템은 단순한 안정성을 넘어 급격한 트래픽 증가에도 견딜 수 있는 확장성과 견고함을 갖추게 되었으며, 이는 향후 더 많은 사용자와 기능을 지원할 수 있는 강력한 기반을 마련해 주었습니다.
이번 성과는 회사의 기술적 역량을 크게 끌어올렸고, 앞으로도 이 경험을 바탕으로 더욱 발전할 수 있는 시스템으로 나아가게 될 것입니다.
글 | 강병규, 오용수
디자인 | 박서영
본 콘텐츠의 저작권은 (주)자비스앤빌런즈에게 있으며, 본 컨텐츠에 대한 무단 전재 및 재배포를 금지합니다