Connection Reset by Peer 문제 해결

Jeongmin Yeo (Ethan)·2023년 2월 14일
5

문제 해결

목록 보기
1/1

Connection Reset by Peer 문제란 뭘까?

Client 가 요청을 보냈는데 서버쪽에서 연결이 닫혔다고 다시 연결하라는 RST (Reset) 패킷을 보내는 경우에 이 에러가 발생한다.

Client-Server 연결에서 한 쪽만 연결되어있는 좀비 커넥션이 생겼을 때 발생한다.

이 에러가 난 상황은 우리 쪽 Crawler 에서 Straw 에 적재할 때 발생한 에러이다. Straw 쪽에 적재를 하려고 요청을 보냈는데 Straw 쪽에서 연결을 닫은 경우에 발생했다.

  • (사내 문제를 해결한 거라서 용어가 다소 어색할 수 있습니다. Crawler 는 저희 팀이 개발한 어플리케이션이고, Straw 는 여기에 적재할 저장소입니다.)

닫힌 이유 자체는 많을 수 있다.

  • network connection.
  • Firewall and VPN.
  • any proxies and load balancers
    • idle timeout configuration (the connection is closed when there is no incoming data for a certain period of time)
  • the target server.
    • idle timeout (the connection is closed when there is no incoming data for a certain period of time)
    • limit for buffering data in memory
    • multipart exceeds the max file size limit
    • bad request
    • max keep alive requests (the connection is closed when the requests reach the configured maximum number)
  • 참고: [Reactor Netty References]

왜 한쪽만 닫히지?

TCP 의 연결 종료 과정을 보면 다음과 같다.

  • 종료하는 쪽에선 FIN 패킷을 보내면서 종료를 시작한다. 그리고 성공적으로 종료되면 클라이언트-서버 둘 다 CLOSED 상태가 된다. 그러면 한쪽만 닫히면 안되는거 아닌가!!
  • 실제로 현실의 네트워크 상황에서는 FIN 패킷이 전달 안되는 경우도 있다고 한다.
    • 스위치가 강제로 뽑혀졌을 때.
    • Client 와 Sever 간의 네트워크 컴포넌트들이 껴있을 때 FIN 을 전달하지 못할 수 있다. (i.e 로드밸런서, 프록시 등)

실제로 한쪽만 닫히는 경우 사례

  • DSR (Direct Server Return) 구조라고 가정. 클라이언트에서는 로드밸런서로 요청을 보내고, 서버는 클라이언트로 직접 전송.
  • 1) Client 는 Server 와의 TCP 연결을 위해서 Load Balancer 의 VIP 로 요청을 보낸다. 그러면 Load Balancer 는 Server 1 로 요청을 라우팅하고 이 세션 정보를 기억하기 위해서 세션 테이블에 기록한다. 이 과정을 통해서 Client 와 Server 1 는 TCP Handshake 를 통해서 연결이 맺어진다.
    • 이 때문에 Client 는 Load Balancer 로 요청을 보내면 계속 Server 1 과 통신이 가능하다.
  • 2) 근데 Client 에서 한동안 요청을 보내지 않으면 Load Balancer 는 해당 세션 정보를 지워버린다. 이때 중요한 점은 Server 와 Client 의 TCP 연결은 아직 유지되어있다는 점이다.
  • 3) 이렇게 Load Balancer 에서 세션 정보가 지워진 상황에서 Client 가 다시 요청을 보내면 Load Balancer 는 Server 1 이 아닌 Server 2 나 3 에게 요청을 라우팅 할 수 있다. 그러면 해당 서버들은 Client 에게 RST 패킷을 전송하게 된다. TCP 연결 없이 요청을 보냈기 떄문에.

2번에서 이렇게 한동안 요청을 보내지 않으면 연결을 지워버리는 걸 idle Timeout 이라고 한다.

실제론 이것말고도 더 다양한 사례가 있다.

  • 1) Client → Server 로 대용량 데이터 적재.
  • 2) Server 에서 이를 처리하는데 시간이 꽤 걸림.
  • 3) Load Balancer → Server 로 HealthCheck 하는데 실패함. 해당 Server 다운시킴.
  • 4) 서버가 다운되면서 소켓이 닫힘.
  • 5) 이후 해당 소켓으로 요청이 또 들어오면 RST 패킷이 나감.

그럼 우리는 왜 이 문제가 발생했을까?

Straw 도 앞단에 Load Balancer 를 쓰고 있다. 그래서 위의 Load Balancer 사례와 유사하다.

결론적으로 idle Timeout 이 발생할 확률이 높다고 생각했다.

  • Crawler 에서는 Straw 와 여러 커넥션을 맺는다. 그리고 크롤링 한 문서의 상태가 Delete 일 때 문서를 지울려고 Straw 에서 Delete 요청을 보내는데 Bulk Delete 를 지원하지 않아서 parallelStream() 을 써서 단건으로 지우는 요청을 병렬로 보낸다. 이 때문에 유휴 커넥션이 생길 가능성이 높다. (실제로 Netty Debug 모드에서 꽤 보면 생긴다.)
  • 에러가 발생할 당시의 로그를 보면 커넥션을 얻기까지 시간이 3분정도 걸렸었다. (Straw 쪽의 Load Balancer 의 maxIdleTimeout 은 120초라고 한다.)

해결 방법

1) Naive 한 접근. TCP 연결을 유지하지말고, 요청 보낼 때마다 TCP 연결을 하면 되지 않을까?

  • TCP Handshake 의 비용 문제가 발생한다.
  • 연결을 먼저 끊는 쪽에서의 소켓은 TIME_WAIT 라는 상태에 도달하게 된다. 이 상태는 완전히 소켓이 닫히기 전에 30초동안 대기하게 된다. 즉 소켓을 쓸 수 없는 문제가 생겨서 포트 고갈의 문제가 생길 수 있다.

2) TCP Keep-Alive probe 를 통해서 요청이 끊겼는지 계속 조사하는 방법.

  • 연결이 끊겼는지 안끊겼는지 알 수 있는 방법이 없다. 즉 조사를 계속해야한다.
  • TCP Keep-Alive 는 주기적으로 연결이 되어있는지 확인을 하는 패킷을 보내서 연결을 유지해준다.
  • 관련 파라미터
    • TCP_KEEPIDLE(300) : 300 초 동안 유휴 상태가 되어있다면 TCP Keep-Alive 조사를 한다.
    • TCP_KEEPCNT(8) : Keep-Alive 가 실패할 경우 최대 8번 조사를 한다.
    • TCP_KEEPINTVL(60) : 개별 Keep-Alive 조사를 60초 간격으로 한다.

3) Client 의 Connection Pool 에서 설정하는 옵션으로 maxIdleTimeout 을 서버 쪽 maxIdleTimeout 보다 작게 주는 방법.

  • Straw 쪽 Load Balncer 의 maxIdleTimeout 은 120 초라고 한다.
  • 이것보다 Client 쪽 maxIdleTimeout 을 작게 줘서 우리쪽에서도 대응할 수 있도록 하는 방법.

정리하자면 연결을 받는 서버쪽의 maxIdleTimeout 을 미리 알고 있다면 3번의 방법이 가장 나을듯하다. 이걸 알지 못한다면 2번으로 해결해야하고.

  • TCP Keep-Alive 의 해결 방법은 연결을 확인해야하는 패킷을 계속 보내야하니까. 네트워크 대역폭을 차지할 것이니 3번보다 좋지 않은듯.

번외로 Connection 관리에 대한 팁

커넥션을 기본 설정으로 쓰면 최대 개수가 500 개까지 늘어날 수 있다고 하고, 1000 개 까지 커넥션을 얻을려고 시도하는 Pending 상태가 될 수 있다고 한다.

Reference 에서는 high load 가 예상된다면 너무 많은 커넥션이 생기지 않도록 조심하라고 함. 트래픽이 증가해서 커넥션 개수가 늘어났는데, 트래픽이 줄어들면 그만큼 유휴 커넥션이 생길 수 있으니까 그런 듯.

Reactor Netty References
When you expect a high load, be cautious with a connection pool with a very high value for maximum connections. You might experience reactor.netty.http.client.PrematureCloseException exception with a root cause "Connect Timeout" due to too many concurrent connections opened/acquired.

profile
좋은 습관을 가지고 싶은 평범한 개발자입니다.

1개의 댓글

comment-user-thumbnail
2025년 6월 14일

OLMOCR leverages advanced large language models to extract text from any Image or PDF for free, with unparalleled accuracy and intelligence. OLMOCR now supports over 12 major languages, including a wide range of handwritten scripts.
OLMOCR

답글 달기