들어가며
https://geniusjun4663.tistory.com/118
[Infra] Prometheus와 Grafana로 우리 서비스의 개선점과 에러 찾기
들어가며현재 소마를 진행하며 유저를 모으고! 돈도 벌 수 있고, 6개월 동안 애정을 담아 만들 수 있는 서비스를 기획하기 위해 정말 고군분투 중이다. 애정을 담아 만들 서비스의 CPU나 메모리는
geniusjun4663.tistory.com
이전에 서비스의 에러와 개선점을 파악할 수 있는 Prometheus + Granfana 기반의 모니터링 시스템에 대해서 다뤘었다. 너무나 유용한 기능이 많았지만,, 이 방식은 결국 모니터링용 서버가 하나 더 필요하다!
보통 실무에서도 Prometheus와 Grafana 조합으로 서버 지표를 수집하고 시각화한다고 한다.
하지만 아직 서비스 완성도가 낮고, 트래픽이 많지 않은 초기 프로젝트에서 서버 하나를 더 증축하는 것은 여러가지 측면에서 부담스럽다!
그래서 이번 글에서 시도한 챌린지의 목적은 다음과 같다.
🔥 모니터링 서버를 직접 구축하지 않고, 적은 리소스로 부하 테스트를 적용해보고 병목 지점을 확인해보기
마침 우리 서비스도 특정 API의 응답시간과 리소스 비용은 꼭 확인하며 비교해봐야 할 듯 해서, 가벼운 모니터링 환경 구축 연습을 해본다!
이를 위해 k6로 부하 테스트를 걸고, AWS의 Amazon CodeGuru Profiler를 사용해서 애플리케이션 내부에서 어떤 코드가 시간을 많이 잡아먹는지 확인한다.
본문으로
왜 부하테스트를 해야할까?
실습에 들어가기 전 잠깐!
내 로컬 환경에서 10ms 만에 응답하는 API가 있다고 해보자. 그렇다면 이 API는 사용자가 1만 명 동시에 접속해도 똑같이 빠르게 응답할까?
실제 서비스에서는 요청이 동시에 들어오고, DB 커넥션이 몰리고, 특정 로직이 반복 실행되며, 예상하지 못한 병목이 발생한다. 부하 테스트를 하는 이유를 정리해보자면 다음과 같다.
1. 우리 시스템이 어느 정도의 트래픽까지 버틸 수 있는지 확인하기 위해서
-> 개발의 depth가 깊어질수록 어느정도의 트래픽까지 버틸 수 있는지 감이 오기 힘들어진다. 생산속도가 빠른 바이브시대에서는 더더욱 심해질 거라고 생각한다.
2. 내부 병목 지점을 찾기 위해서
-> 스케일 업보다 특정 코드 한줄을 고치는 게 더 효과적일 때가 있다.
어떤 지표를 봐야할까?
부하테스트는 서버가 잘 살아있다 vs 죽었다 정도만 판단하는 것이 아니라, 여러 지표를 보면서 문제점을 찾고 예측까지 할 수 있는 것이다!
- TPS(초당 처리 건수)
- 직관적이다. 초당 처리할 수 있는 것이 많으면 좋지 않겠는가! 그렇다면 TPS가 높다고 무조건 좋은 아키텍처 일까?
- 예를 들어 서버 운영 비용이 100억 원씩 드는데 TPS가 높다면, 그것을 좋은 구조라고 할 수 있을까? 결국 중요한 것은 처리량, 응답 속도, 비용 사이의 균형이다.
- Bottleneck(병목 현상)
- 어떤 아키텍처든 병목이 발생하는 것은 어쩔 수 없다! 중요한 것은 병목이 없다고 착각하는 것이 아니라, 병목이 어디서 발생했는지 빠르게 찾고 적절히 우회하거나 개선하는 능력이다
- Latency(응답 지연 시간)
- 사용자 입장에서는 TPS보다 Latency가 더 직접적으로 체감된다. 아무리 서버가 많은 요청을 처리하더라도, 내 요청 하나가 3초 뒤에 응답된다면 사용자는 느리다고 느낀다.
- Throughput(단위 시간당 처리량)
- TPS와 비슷하게 느껴질 수 있지만, Throughput은 시스템이 일정 시간 동안 실제로 처리한 전체 작업량을 보는 개념에 가깝다. 파일 업로드, 데이터 처리, API 요청 처리 등 다양한 기준으로 볼 수 있다.
평균의 함정에 빠지지 말자
성능 테스트를 할 때 흔히 평균 응답 시간을 먼저 본다. 예를 들어 평균적으로 50ms 안에 들어온다는 지표가 있다. 이정도면 꽤 빨리 들어온다고 느껴지지 않는가??
하지만 평균 응답 시간이 50ms라고 해도, 일부 사용자는 1초 이상 기다리고 있을 수 있다. 특히 금융 서비스나 결제 서비스처럼 안정성이 중요한 서비스에서는 “대부분 괜찮다”만으로는 부족하다!!
그래서 p90(전체 요청 중 90%가 이 시간 안에 응답), p99(전체 요청 중 99%가 이 시간 안에 응답) 같은 지표를 함께 봐야 한다. 예를 들어 아래와 같은 결과가 있다고 생각해보자!
avg: 30ms
p90: 50ms
p95: 300ms
평균만 보면 30ms라서 괜찮아 보인다. 하지만 p95가 300ms라면, 상위 5%의 요청은 꽤 느리게 응답했다는 뜻이다. 이 차이가 크다면 일부 요청이 특정 조건에서 병목을 만나고 있을 가능성이 있다.
(이렇게 버그가 걸린 유저나 해킹시도가 있었다고 판단하기도 한다@!)
좋은 코드는 평균만 빠른 코드가 아니다. 대부분의 사용자에게 안정적으로 비슷한 응답 시간을 제공하는 코드가 더 좋은 코드다.
k6 & aws cli 설치하기
서론이 길었다!! 그래도 왜 부하테스트를 해봐야하고 어떤 지표를 봐야하는지 감이 왔을 것이라고 생각한다.
이제 실습을 위한 준비를 하자! MacOS는 homebrew로 아주 간단하게 설치 가능하다!!(Mac을 기준으로 작성하겠다)


AWS CLI도 설치해주자! (AWS 콘솔은 IAM계정 줄때만 편하지 나머지 응답이 너무 느리다! CLI에 익숙해지도록 하자~)


Amazon CodeGuru 세팅하기
코드그루? 한국말로 하니깐 뭔가 귀엽다 ㅋㅋㅋㅋ.

아마존 공식 사이트에서는 아래와 같이 소개한다.
Amazon CodeGuru Profiler는 애플리케이션의 런타임 성능 데이터를 수집하고, CPU 사용이나 지연 시간에 영향을 주는 비효율적인 코드 지점을 찾는 데 도움을 주는 서비스다. AWS 공식 문서 기준으로 JVM 계열 언어와 Python을 지원한다.
이번 실습을 위해서 CodeGuruFullAccess의 권한만 가진 IAM 계정을 생성해주었다.


IAM계정 만들었으면 aws CLI에 아래와 같이 등록해주기~!

근데 위의 설정 사진 보면 지역이 ap-northeast-1로 설정되어 있는 것을 볼 수 있다. 뭔가 이상한 낌새를 차렸다면 당신은 AWS잘알로 인정하겠습니다👍 아래 사진을 보면 한국은 모니터링 서비스 센터가 없나보다 ㅎㅎ 지원을 안해준다.

그래서 지역 일본으로 설정하고 다시 CodeGuru들어가면 아래 사진처럼 Profiling groups가 보인다!

CodeGuru Profiler Agent를 붙이면, 해당 애플리케이션이 실행되는 동안 수집된 성능 데이터가 이 프로파일링 그룹에 쌓인다.
Profiling group 생성을 하면 아래 사진처럼 의존성 추가하라고 나와있다. 주의할점은 "profilingGroupName:" 뒤에는 자신이 만든 Profiling group 이름을 써야한다. (나는 performance-group으로 만들었기에 그렇게 써주었다)

이정도면 기본 세팅은 끝난다! 어떠한가?
좀 어색할 뿐이지, IAM계정 하나 만들어서 AWS 리소스 하나 생성한다음에 의존성만 추가해주면 끝 아닌가?!?!? 모니터링 서버 띄우고 설정하는 것보단 훨씬 편하다. 그리고 트래픽이 거의 없다면 CodeGuru가 서버 운용하는 것보다 훨씬 싸다!(1000원도 안나옴)
K6로 부하테스트해보기
아래처럼 코드를 작성하고 k6로 부하테스트를 진행했다.
vus(Virtual Users) : 50, duration : 6m -> 이 설정은 50명의 가상 사용자가 6분 동안 계속해서 /heavy/bad API에 요청을 보내는 테스트이다.

/bad의 로직은 아래와 같다.

k6 run (스크립트명) 으로 부하테스트를 진행할 수 있다. 6분동안.. 맥북에서 비행기 이륙하는 소리가 들렸다 ㅎㅎ.

6분동안 기디리니 아래처럼 결과가 뜬다. 여기서 주의해서 봐야할 점은 http_req_duration의 값이다. 최솟값은 350ys으로 매우 작고, 최댓값은 251.85ms이다! 평균 값은 괜찮을지라도 최소와 최대의 차이가 너무 크다!

아까 /bad의 로직은 그렇게 복잡해보이지 않았는데,,, 왜 이렇게 차이가 날까? -> 하지만 이 지점이 바로 부하 테스트를 해봐야 하는 이유다. 로컬에서 한 번 호출했을 때는 잘 보이지 않던 문제가, 여러 사용자가 동시에 요청을 보내는 상황에서는 드러날 수 있다.
그리고 평균 응답 시간만 보면 놓칠 수 있는 문제도 최댓값이나 p90, p95 같은 지표를 보면 더 잘 드러난다.
/bad의 병목이유는 무엇일까?
다시 한번 아래 로직을 봐보자 대체 어디가 문제일까? 그냥 String이 정규표현식과 일치하는지 1000번정도 실행하는 로직인데... 흐음~

문제의 원인은 정규식 처리 방식에 있었다. 아래 로직을 보자! Java에서 문자열에 대해 matches()를 호출하면 내부적으로 정규식을 컴파일하는 과정이 발생한다. (Pattern.compile(regex))

즉, 매 요청마다 같은 정규식을 다시 컴파일하고 있었던 것이다.
작은 트래픽에서는 크게 티가 나지 않을 수 있다. 하지만 요청이 많아지면 이야기가 달라진다. 매번 정규식을 컴파일하고, 객체를 만들고, 다시 버리는 과정이 반복된다. 결국 불필요한 객체 생성과 CPU 사용이 누적된다.
우리가 Java 코테에서 문자열을 반복해서 더할 때 String의 + 연산 대신 StringBuilder를 고려하는 이유와 비슷하다. 작은 코드 한 줄도 반복 횟수가 많아지면 성능에 영향을 줄 수 있다.
CodeGuru Profiler에서 병목 확인하기
우리가 이렇게 부하테스트 결과의 지표를 보고 판단할 수도 있지만, 한눈에 보기 좋게 이쁘게 표로 보여주고 병목지점까지 알려주면 좋지 않겠는가? 그게 바로 CodeGuru다! 정상적으로 의존성 연결해서 부하테스트를 진행했다면 AWS 콘솔의 CodeGuru 화면이 아래와 같이 달라진다!

위의 CPU 시각화 버튼을 눌러보면 아래와 같은 시각화 표를 볼 수 있다. 유독~~ 뭔가 긴 표가 있다. 눌러보면 Pattern.compile()이다 ㅋㅋ...

이 지점이 좋았던 이유는 단순히 “서버가 느리다”가 아니라, 어떤 코드가 느린지를 확인할 수 있었다는 점이다.
Prometheus와 Grafana는 시스템 지표나 애플리케이션 메트릭을 보는 데 강력하지만, CodeGuru Profiler는 코드 레벨에서 어느 부분이 비용을 많이 쓰는지 확인하는 데 도움이 된다.
특히 초기 프로젝트에서 "모니터링 인프라를 크게 구축하기 전에 병목을 한 번 가볍게 확인해보고 싶다"면 꽤 유용하게 사용할 수 있다.

병목지점을 찾았으니! 개선해야지!
문제를 확인했으니 이제 개선해보자. 기존 /bad 로직에서는 요청이 들어올 때마다 정규식이 다시 컴파일되었다. 이를 개선하려면 정규식을 매번 컴파일하지 않고, 미리 Pattern 객체로 만들어 재사용하면 된다.(Static으로 위에 선언해놓음.)

이렇게 하면 매 요청마다 정규식을 새로 컴파일하지 않아도 된다. 즉, 비싼 작업을 요청마다 반복하지 않고 애플리케이션 시작 시 한 번만 수행하게 된다.
-> 우리가 얻어가야 할 점은 비싼 연산의 위치를 찾고 그 부분의 cost를 최대한 줄이는 이 플로우를 얻어가야 한다!!!!
마무리하며
이번 정리와 연습을 통해 "로컬에서 빠른 API가, 실제 트래픽 상황에서도 항상 빠른 것은 아니다" 와 "성능 문제는 생각보다 거대한 아키텍처 문제가 아니라, 작은 코드 한 줄에서 시작될 수도 있다." 라는 교훈을 얻었다.
이번 글처럼 초기 단계에서는 k6와 CodeGuru Profiler를 활용해 가볍게 부하 테스트를 해보고, 트래픽이 늘어나거나 운영 중요도가 높아지는 시점에 Prometheus, Grafana 기반의 모니터링 시스템으로 확장하는 것도 좋은 접근이라고 생각한다.
해결 자체는 요새 AI가 워낙 빠르게 잘하고 앞으로는 더 잘해질거라고 생각한다.
나는 이렇게 테스트를 통한 지표 기반으로 문제를 발견하고 팀원들에게 공유하며, 추가적인 문제까지 고려할 수 있는 해결사가 되고 싶다.

'Infra' 카테고리의 다른 글
| [Infra] Container 딥다이브 - Linux Kernel에서 시작하는 Docker 자세히 알아보기 (0) | 2026.05.28 |
|---|---|
| [Infra] Prometheus와 Grafana로 우리 서비스의 개선점과 에러 찾기 (0) | 2026.05.23 |
| [Infra] 인스턴스 연결성 검사 실패 문제. 메모리가 부족하면 ssh 연결도 막힌다. (0) | 2026.02.21 |
| [Infra] Blue/Green 무중단 배포 (Docker, Nginx, GitHub Actions) 구현해보기 (5) | 2025.11.21 |