본문 바로가기
node.js

[nodejs] nodejs와 nginx timeout

by 박연호의 개발 블로그 2023. 3. 21.

이글에서 자주 등장하는 upstream은 node 서버를 의미한다.

 

nginx를 프록시 서버로 사용할 때 발생할 수 있는 에러중에,  502 Bad Gateway, 504 Gateway Time-out 가 있다. 

 

보통 502, 504에러?면 nginx의 책임(?)으로 몰아가는 경우가 있다. 사실 nginx 문제일 수도 있고, 아닐수도 있다. 아닐 수 있는 이유는 upstream 서버에서도 timeout이 존재하기 때문이다. 

 

일단은 502, 504에러를 알아보기 전에 upstream에서 timeout이 어떻게 동작하고 있는지 확인해보자.


nodejs timeout

테스트로 사용하는 node버전은 v19.7.0 이며, 아래의 글은 해당 버전을 기준으로 한다.

 

express로 다음과 같이 서버를 만들었다고 해보자.  

const server = app.listen(3489, () => {
  console.log("server running 3489 port");
});

 

여기서 listen 메서드가 반환하고 있는 것은 http모듈의 Server 클래스 이다.

listen(port: number, hostname: string, callback?: () => void): http.Server;

여기서 Server 클래스를 타고 들어가보면 다음과 같은 프로퍼티와 메서드가 있다.

여기서 주목해야 할 것은 server.timeout와 server.setTimeout 메서드 이다.

 

server.timeout은 0이며, 0은 timeout 제한이 없다는 것을 의미하며 timeout을 걸지 않고 서버에서 오래 걸리는 작업을 하게 되면 그 작업이 끝날때 까지 계속 기다린다는 것을 말한다.

server.timeout값은 server.setTimeout 메서드로 변경할 수 있다. 또한 timeout되었을 때 명시적으로 어떤 작업을 수행하고 싶으면 setTimeout의 콜백함을 작성하면 된다. 이때 콜백함수의 인자는 timeout된 socket이 된다.

지금까지는 서버의 기본 timeout을 설정하였고, 이는 서버의 모든 요청이 server.timeout값을 따른다는 것을 의미한다. 하지만 endpoint별로 timeout을 다르게 지정하고 싶을 수 있다. 이 경우 다음과 같이 timeout을 설정할 수 있다.

app.post("/test", async (req, res) => {
  req.setTimeout(2000);
  res.send("test");
});

그렇다면 req 저 녀석의 정체는 뭘까 ? 한번 파보자. req는 express에서 제공하고 있지만, 결국에 까보면 nodejs api를 위에서 구현된 녀석들이다.

setTimeout 메서드는 http 모듈내에 정의되어 있으며 IncomingMessage 클래스 내부에 존재한다.

 

그렇다면 IncomingMessage이 뭔지 node 공식문서를 한번 보자.

그렇다 우리가 찾으려고 하는 req의 정체, 즉 express 미들웨어에서 첫번째 인자값은 IncomingMessage이다. 사실 두번째 인자도 IncomingMessage이다. 

 

위에서 req.setTimeout 메서드를 호출하는 것은 결국에 IncomingMessage의 setTimeout 메서드를 호출하는 것이다.

또한 setTimeout 메서드를 호출하는 것은 IncomingMessage의 프로퍼티인 socket의 setTimeout 메서드를 호출하는 것이다.

 

socket 프로퍼티는 nodejs api Net 모듈의 socket 클래스 이다.

 

Net 모듈의 Socket 클래스가 정확히 무엇인지는 아래의 한 문장으로 설명할 수 있다.

 

A net.Socket can be created by the user and used directly to interact with a server. For example, it is returned by net.createConnection(), so the user can use it to talk to the server.

 

즉 사용자가 서버에 연결되었을 때 만들어지며, 서버와 통신하기 위해 사용되어 진다.


여기까지 정리하면 우리가 만든 서버는 global timeout이 존재하며, 이와는 별개로 각각의 요청마다 timeout을 수정할 수 있다.

이를 위해 express에서 첫번째 인자는 setTimeout메서드를 제공하고 있으며, 이는 결국 Net 모듈의 Socket.setTimeout을 호출하는 것이다.

 

global로 timeout을 걸었음에도 불구하고, 미들웨어에서 요청(socket) 단위로 timeout을 걸어주면 미들웨어에서 설정한 timeout이 걸리게 된다.

 

위의 사진에서 global time(5초)가 아닌, socket timeout(3초)가 적용된다.


timeout 누구 잘못일까 ?

지금까지 nodejs에서 timeout이 어떻게 동작하는지 알아봤다. 만약 서버와 클라이언트 사이에 nginx 즉, 리버스 프록시가 존재하는 경우 timeout은 어떻게 동작할까 ?

 

사실 구글에 nginx timeout으로 검색하면 많은 자료가 나온다. nginx를 사용하면서 5xx 상태코드와 함께 timeout문제가 발생하면  nginx문제라고 생각할 수 있는데, 실제로 nginx문제인지, upstream 문제인지 확인해봐야 한다.

 

위에서 설명했지만, nginx의 경우 proxy_read_timeout이라는 directive가 있다. 이는 upstream 서버로부터 응답 데이터를 읽는 시간, timeout을 의미하며 기본 timeout은 60s이다. 

예를 들어 upstream에서 5초가 걸리는 I/O 작업 후 바로 응답한다고 가정해보자.

 

upstream timeout : 4s

nginx timeout : 6s

위 경우는 nginx timeout은 여유롭다. 5초가 걸리는 I/O작업 후 응답해도 전혀 문제가 없다. 하지만 문제는 upstream이 5초가 걸리는 I/O작업을 기다리지 못하고, 4초만에 timeout을 해버린 것이다. 

 

nginx : 나 시간 여유로운데(7s), 왜 timeout 해버림? 아무튼 내잘못은 아님. 니가 timeout 해버린거임.

-> 502 Bad Gateway

 

upstream timeout : 6s

nginx timeout : 4s

반대로 위의 경우는 보자. upstream의 timeout은 6s이기 때문에 I/O 작업을 충분히 기다려줄 수 있다. 5초가 지난 후, nginx server에 응답해주면 된다. 하지만 nginx는 성격이 급해서, 이 시간을 기다리지 못한다.

 

nginx: upstream아, 나 4초 안에 응답 줘야해..아니면 나 timeout 되버린다고..

upstream : 나 5초뒤에 응답 줄 수 있는데...?

nginx : .... timeout

-> 504 Gateway Timeout

 

nginx의 error.log를 보면 502, 504에러에 따라 로그가 달라지는 것을 확인할 수 있다.

- 504

... upstream time out (60: Operation timed out) while reading response header from upstream ...

 

- 502

... upstream prematurely closed connection while reading response header from upstrea ...

'node.js' 카테고리의 다른 글

[node.js] single thread and libuv  (0) 2021.09.24
[node.js] fs.write()로 알아보는 node.js  (0) 2021.09.17