개인 공부용/loadbalancer

apache jmeter로 부하테스트 해보기

pon9 2025. 1. 25. 16:45

2025.01.23 - [개인 공부용 프로젝트/loadbalancer.project] - 로드밸런서의 동시성 문제 해결을 위한 각종 플랜

ㄴ이 글에서 이어집니다

 

이전 부하테스트가 왜 실패했는가를 생각해보았는데 그건 부하테스트가 아니었다

초당 100건으로 부하테스트라니 내가 생각해도 웃기긴 하다

그래서 오늘 상당한 과부하를 일으킬거기 때문에 내 노트북에서 테스트할 순 없고 이놈이 희생해야겠다

도커에다 로드밸런서를 올리고,

도커내부의 같은 네트워크 안에서 실행되기에 서버 주소도 로컬호스트에서 도커내부의 주소로 바꿔주었다

인식이 잘 된다 바로 부하테스트를 진행해보자

 

오늘은 모두 같은 플랜으로 하기보다는, 부하를 점점 늘려서 한계치를 테스트 해볼 계획이다.

우선 우리 도커가 튼튼하리라 믿고 초당 10만의 사용자를 보내보자

10초간 보낼거니까 총 100만이다

오호 너 이정도는 버티는구나

근데 100만건정도는 돼야 유의미한 테스트가 나오는구나 싶었다 엊그젠 10초만에 끝난 테스트가 5분이 넘었는데 안 끝난다

티켓팅이 왜이렇게 힘든지 알수있는 시간이다..

조져지는게 내 노트북이 아니라서 참 다행인 순간이다

라고 생각했는데 100만건을 3번 반복하니까 노트북이 굉음을 내면서 꺼졌다

ㅋㅋ

서버도 4개로 낮추고, 요청도 초당 1만건으로 낮춰서 테스트 해보자 ㅎ

물론 db가 없어서 실제 서비스랑 괴리감이 크지만 단순한 코드의 성능만 관찰하기에도 서버 10개에다 100만건은..

내 컴퓨터가 버티기에는 턱없이 많은 데이터였구나 싶다..

 

혹시나 이 글을 보는 나같은 개초보 개발자가 있다면은

당신 로컬 컴퓨터가 아니라 도커로 실행해도 과도한 요청은 컴퓨터가 터지니까 자제해주시길 ..

 

적은 요청에서 많은 요청으로 점차적으로 늘려야한다는 교훈을 얻었다.

 

 

테스트 방법

스샷이 잘못되었는데 3500이아니라 10000이다

한계치로 테스트하려다 포기했다. 그러다간 노트북이 임종을 맞이할듯

사용한 툴은 apache jmeter이다. gui에서 이렇게 테스트플랜을 작성해줄 수 있다.

요청은 초당 1만건, 20회 보내도록 했다.

gui에서 저장한 테스트플랜을 이용해(-t) cli로 부하테스트를 진행할 수 있다.
logs경로에 csv로 로그가 저장되도록(-l) 해서 스프레드시트로 결과를 분석할 수 있도록 해주자.

(로깅기능이 워낙 잘 되어있어서 내가 작성한 aop로깅은 쓸모없음을 느낌;;)

결과가 너무 많다고? 몇만개의 행을 어케 사람이 보냐고?

얘가 해줌

 

 

실행결과

성공률 기준 내림차순 정렬했다.

웃기게도 가장 "비효율적" 이라고 알려진 synchronized의 성공률이 가장 높고 안정적이었다.

물론 초당 처리량과 평균 응답시간이 다른 전략에 비해서는 한참 떨어지지만..

이는 synchronized가 모든 요청을 직렬로 처리하기 때문에, 병렬성을 활용하지 못하고 락 경합이 발생하기 때문이다.

 

병렬 처리가 가능한 concurrentHashMap이나 threadLocal은 보통 성능이 좋다고 알려져 있지만,

테스트 환경에 따라 오히려 락 경합이나 캐시 미스 등의 이유로 성능이 저하되고 성공률이 낮아질 수 있음을 확인할 수 있었다.

 

이론적으로는 "비효율적"이라고 알려진 synchronized가 실제 환경에서는 생각보다 안정적인 결과를 보여주고 있다.

테스트 환경에 따라 결과가 크게 달라질 수 있음을 의미한다.

spring에서 병렬 처리 방식을 선택할 때는 구체적인 환경을 분석하고 이를 바탕으로 적절한 방식을 선택하는 것이 중요하다는거다.

 

병렬처리 성능을 올바르게 최적화하려면, spring이 제공하는 도구들과 운영체제의 자원 관리 원리를 각각 이해해야 할 거다.

스프링에서는 thread pool과 같은 비동기 처리전략을 적절히 활용해 병렬성을 극대화하고 락 경합을 줄여야 할 거고,

운영체제 레벨에서는 cpu 스케줄링, 메모리 캐싱 원리를 활용하여 jvm의 성능을 보완할 수 있다.

글씨보소

 

 

가중치 기반 로드밸런서는 어떨까?

초반 가중치는 properties파일에서 관리할 수 있게 해뒀다.

라운드로빈 방식과 가중치 방식도 비교를 해보고싶어서 같은 조건으로 부하테스트를 진행했다.

내 가중치 기반 로드밸런서는 concurrentHashMap으로 동시성 문제를 해결했기에 해당 전략이랑만 비교를 하면 된다.

private void startWeightUpdater() {
    scheduler.scheduleAtFixedRate(() -> {
        List<String> healthyServers = healthCheckService.getHealthyServers();
        healthyServers.forEach(server -> {
            int responseTime = healthCheckService.getResponseTime(server);
            updateWeight(server, responseTime);
        });
    }, 0, 10, TimeUnit.SECONDS);
}

private void updateWeight(String server, int responseTime) {
    int newWeight = Math.max(1, 10 - (responseTime / 100));
    serverWeights.put(server, newWeight);
}

responseTime에 따라 weight값을 설정하고, 그에 따라 요청을 서버에 분배해준다.

평균 응답시간과 초당 처리량은 내려갔지만 성공률이 눈에띄게 올라온 걸 볼 수 있었다.

요청을 더 적절한 서버로 분배하느라, 좀 느리지만 과부하를 방지하는 데에 성공했다고 생각이 된다

 

재밌다... 뭘 하나 바꿀때마다 이렇게나 결과가 다양하다

 

++여담

로드밸런서의 서버 호출 코드를 httpUrlConnection(동기 방식) 에서 webclient(webflux, 비동기 방식) 으로 바꾸니 미친 성능을 보여주었다.

성공률이 무려 50%나 오르는 쾌거를..

++어 그렇다면 서버 호출부에선 webclient를 사용하고 로드밸런싱 알고리즘에선 synchronized를 사용하면 어떨까? 해서 또 진행한 부하테스트 결과(synchronized는 라운드로빈)

엄..........

...........왜 속도도 빠랄지고 성공률도 오른 ..
짧게 추측해보자면 일단 킨지 얼마 안된 노트북으로 테스트 한거고, 로드밸런싱의 메인이라 볼수있는 서버호출부는 비동기로 빠르게, 로드밸런싱 알고리즘은 단일스레드로 안정적으로 돌려서 이런 결과가 나온 것 같다..

추가로, 지금 synchronized, concurrentHashMap이 처리단위가 굉장히 작아서

synchronized만으로도 충분히 커버가능한 범위를

굳이 여러 세그먼트로 나누고 스레드 내부적으로 컨텍스트 스위칭이 일어나는 해시맵을 사용한 것이 성능 저하에 영향을 준 것 같다. 

새삼 정말 다양한 조건을 따져봐야한다는걸 깨달았다