문제

사내에서 Node - ES 서버의 부하 테스트를 진행하였습니다.

 

테스트 중 Node 데이터를 넣는  python 코드에서 에러가 발생했습니다.

 

OSError: [Errno 99] Cannot assign requested address

 

 

상황은

1. 에러 발생 직후 실행이 되지 않으나, 시간이 지나면 다시 실행이 가능하였고

2. 요청을 보내는 코드(python requesets)에서만 에러가 발생하였고

3. 받는 서버(node express)에는 어떠한 요청도 수신이 안 되었습니다.

 

Node서버에는 문제가 없고 python코드가 문제인 것을 확인하였습니다.

ES에서 생성된 문서의 개수는 매 실행시마다 28,231개까지 생성되었습니다.

 

문제 확인

 

문서가 계속 같은 개수로 생성되는 것을 바탕으로 검색하던 중 아래의 글을 발견했습니다.

 

https://github.com/JoeDog/siege/issues/127

 

Socket address is unavailable error · Issue #127 · JoeDog/siege

When trying to run siege in a simple stress testing case, I am met by a socket error after 20-30 seconds (number varies): [error] socket: 192370432 address is unavailable.: Cannot assign requested ...

github.com

 

위 글대로 포트 범위를 확인하니 문서가 생성된 개수와 맞았습니다.

즉 포트 개수로 인한 오류인 것을 확인했습니다.

 

cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999

 

문제는 포트개수에 있고, 서버측에는 요청자체가 가지를 않으니, 요청을 보내는 파이썬코드를 수정하기로 했습니다.

 

기존의 코드의 로직은 아래와 같습니다.

각 프로세는 문서마다 요청을 보내는 방식으로 작성되어 있습니다.

 

 

 


원인

 

원인은 클라이언트측에서 요청을 보낼 포트의 개수를 전부 소진하여서 요청자체를 보내지 못하였던 것이었습니다.

 

현재 진행중인 테스트는 초당 대략 800개, 총 360,000개의 데이터를 전송하려 하였고,

 

서버의 할당 가능한 포트의 개수는

/proc/sys/net/ipv4/ip_local_port_range 을 조회 시 ephemeral port의 범위가 32768 - 60999으로

60,999 - 32,768 = 28,231개의 포트가 할당 가능했습니다.

 

여기서 클라이언트 요청에도 포트가 할당되는 것을 알게되었습니다.

 

서버의 경우 실행시 지정된 포트에 할당이 되며,

클라이언트는 새로운 연결을 만들 때마다 클라이언트의 포트에 임의(ephemeral port)로 바인딩됩니다.

클라이언트는 서버가 바인딩된 포트로 요청을 보내면 클라이언트는 서버의 소켓과 연결이 됩니다.

포트는 연결이 종료되어도 안정성을 위해 1분간 TIME_WAIT상태로 있습니다.

 

테스트의 경우 1분에 사용되는 포트의 개수는 48,000개로(1분 동안 요청 개수가 800 * 60)

현재 서버에서 할당 가능한 포트의 개수인 28,231개보다 많습니다.

결국 포트가 재할당되기전에 요청을 보내려고 해서 발생한 에러였습니다.

 


 

해결

포트의 개수 문제이고, 요청을 보낼때 각 요청마다 포트를 할당받는 문제임을 확인했습니다.

 

그래서 http요청시 keep-alive 헤더를 넣어서 이를 해결하고자 했습니다.

 

클라이언트(python)에서 사용중인 모듈인 'requests'에서 세션을 사용하면 되는 것을 공식문서를 보고 확인했습니다.

 

requests keep alive 설명

 

기존의 코드를 프로세스마다 session을 열어서 요청을 하도록 변경했습니다.

 

 

 

 

코드 변경 후 정상적으로 작동했습니다.

 


 

추가

python -> Node의 통신에서는 client(python)에서 session을 열어 port사용을 제한했는데 (port 개수 == 프로세스 개수),

Node -> ES의 통신을 확인 결과 생성한 문서만큼 포트를 사용하는 것을 확인했습니다.

Node -> ES 역시 keep-alive를 적용하려고 하였는데, 생각보다 잘 되지 않았습니다.

client의 agent를 설정하여서 keep alive를 활성화시켰는데도 불구하고 문서수가 상당히 많은 것을 확인했습니다.


elasticsearch-js client를 확인하기 위해 github issue를 찾던 중 하나의 글을 발견했습니다.

 

자동으로 잘 관리해준다는 답변을 찾았습니다.

https://github.com/elastic/elasticsearch-js/issues/1698

 

Current documentation on using keepAlive · Issue #1698 · elastic/elasticsearch-js

I can't seem to figure out if I should or how I should use keepAlive when creating the Client. I found lots of bugs and old issues mentioning it, and lots of results for keepAlive when searchin...

github.com

 


번외

소켓은 <protocol>, <src addr>, <src port>, <dest addr>, <dest port> 이 5개 값이 유니크하게 구성된다.

할당 가능한 소캣 개수 확인

$ sysctl fs.file-max
fs.file-max = 26355452

 

현재의 소켓 상태를 확인

$ ss -ant | awk '{print $1}' | grep -v '[a-z]' | sort | uniq -c

 

특정 호스트의 TIME_WAIT port를 확인하는 명령어

$ netstat -n -t | grep 'TIME_WAIT' | sort | uniq -c | grep 0.0.0.0 | wc -l

 

패킷 확인

$ tcpdump -i eth0 -n dst *.*.*.* and port 8080

 

 


참고자료

https://meetup.toast.com/posts/54

 

리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기 - 2편 : NHN Cloud Meetup

리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기 - 2편

meetup.toast.com

https://docs.likejazz.com/time-wait/

 

TIME_WAIT 상태란 무엇인가 · The Missing Papers

TIME_WAIT 상태란 무엇인가 14 Jun 2015 TIME_WAIT 상태가 늘어나면 서버의 소켓이 고갈되어 커넥션 타임아웃이 발생한다는 얘기를 한다. 이 말이 올바른 얘기인지, TIME_WAIT은 어떠한 경우에 발생하고 어

docs.likejazz.com

 

 

# 2022-09-28 수정