nGrinder로 API 부하 테스트를 진행하던 중, 총 요청 수가 약 28,000개를 넘는 테스트부터 에러가 발생했다.

테스트별 총 요청 수는 Vusers × Run Count(100)다. A 그룹은 총 요청 수가 28,000개 이하이고 Err Rate가 0%인 반면, B 그룹은 28,000개 이상이며 Err Rate가 요청 수에 비례해 증가한다.
원래 목적은 코드 수정 전후의 성능 차이를 측정하는 것이였으나 이 에러가 TPS를 훼손해 측정이 불가능했다. 이번 글에선 이 문제를 분석한 내용과 해결한 방법에 대해 기록한다.
구성은 ngrinder agent, ngrinder controller, spring boot 서버가 모두 같은 호스트에 존재했고, spring boot 서버는 컨테이너 환경에서 2GB, 2CPU를 할당받아 실행중이였다.

28,000개 이상의 요청에서 발생한 에러는 전부 Connection reset by peer였다. 상대방이 RST 패킷으로 TCP 연결을 강제 종료했다는 의미임으로, 서버 메트릭부터 확인했다.
결과부터 말하자면, 에러가 발생한 테스트(B 그룹)시간대의 메트릭을 확인했으나 특이사항을 찾을 수 없었다.
로컬 캐시를 활용하고 있기에 어느정도의 메모리 할당이 있긴 하나 전체 메모리 공간을 여유롭게 사용중이였다.

MinorGC만 발생했음으로, GC로 인한 영향이라고도 볼 수 없었다.

HikariCP의 상태를 봤을 때도 Connection 획득 실패 카운트도 없었으며, 커네견 점유시간도 안정적임을 확인할 수 있다.

스레드 풀을 최대치까지 사용 중이었지만 A, B 그룹 모두 동일했으므로 성능저하에 영향이 있다고 보긴 힘들다.

이용률 또한 A, B 모두 비슷한 사용량을 기록했다.

요청은 클라이언트 → 네트워크 → 서버를 거친다. 실패 지점은 이 셋 중 하나다.
서버가 아니다. 에러가 없던 테스트와 에러가 난 테스트 사이에서 서버 메트릭의 차이를 찾지 못했다. 서버가 원인이라면 에러 구간에서 어떤 지표든 달라졌어야 한다.
네트워크가 아니다. 에이전트와 서버는 같은 호스트에서 실행됐다. 트래픽이 네트워크를 거치지 않고 호스트 내부(루프백)에서만 오가는 구조이므로, 외부 네트워크 장비나 경로의 문제는 구조적으로 배제된다.
남는 것은 클라이언트다.
TCP 연결은 소켓 페어 4-tuple로 식별된다.
(src IP, src port, dst IP, dst port)
일반적인 클라이언트-서버 상황에서는 src IP가 다양함으로 커넥션 수 제한을 체감할 일이 없지만, 지금과 같은 단일 클라이언트 테스트 환경에선 src port갯수에 제한이 있다.
(src IP, src port, dst IP, dst port)
에이전트 1개 ← 유일한 변수 서버 1개 8080
[고정] [고정] [고정]
클라이언트가 요청을 보낼때 사용하는 로컬 Port는, connect() 시 커널이 할당해 준다. 이 임시 포트의 범위는 커널 파라미터로 정해져 있다.
$ cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999
32768 ~ 60999 사이의 포트만 요청 시 활용할 수 있는걸 볼 수 있다.
연결을 먼저 닫은 쪽은 TIME_WAIT 상태로60초(리눅스 커널 상수) 동안 해당 4-tuple을 점유한다. 4-tuple을 점유하고 대기하기 때문에, 이후의 동일 서버에 대한 추가 요청은 포트 할당 불가로 인해 실패한다.
이에 따라서, 테스트의 총 수행 시간이 36초로 60초보다 짧아 테스트가 끝날 때까지 단 하나의 포트도 재활용되지 못한 것이다.
1분 이상 지속된 테스트 결과 그래프를 확인해본 결과, 아래와 같이 1분 이후에 성공 케이스가 나오는것을 확인할 수 있다.
ngrinder 는 고급 옵션으로 Connection reset on each test run을 제공한다. 각 테스트 실행에서 매번 새로운 커넥션을 수립하게 만드는 옵션이며, 이를 디폴트로 true로 설정한다. 이로 인해 ngrinder agent는 매번 새로운 TCP 커넥션을 수립했던 것이다.

해당 설정을 해제한 후 테스트 한 결과,

만들어진 커넥션을 재활용하여 5만개 이상의 테스트에서도 Err Rate가 0으로 유지되는 것을 확인할 수 있다.
nGrinder로 1 Agent 환경에서 성능 테스트할땐 반드시 Connection reset on each test run를 해제하자.
클라이언트의 문제임에도 불구하고 왜Connection reset by peer이 나왔는지 의문이다. 이후에 이유를 찾아봐야겠다.
http://ngrinder.373.s1.nabble.com/Connection-reset-on-each-test-run-Connection-refused-td2703.html